xref: /onnv-gate/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_session.c (revision 5772:237ac22142fe)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * This module provides the netbios and SMB negotiation, connect and
30  * disconnect interface.
31  */
32 
33 #include <unistd.h>
34 #include <syslog.h>
35 #include <synch.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <pthread.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <inttypes.h>
45 #include <netdb.h>
46 
47 #include <smbsrv/libsmbrdr.h>
48 #include <smbsrv/netbios.h>
49 #include <smbsrv/cifs.h>
50 
51 #include <smbsrv/ntstatus.h>
52 #include <smbsrv/mlsvc.h>
53 #include <smbrdr.h>
54 #include <smbrdr_ipc_util.h>
55 
56 
57 static uint16_t smbrdr_ports[] = {
58 	SMB_SRVC_TCP_PORT,
59 	SSN_SRVC_TCP_PORT
60 };
61 
62 static int smbrdr_nports = sizeof (smbrdr_ports) / sizeof (smbrdr_ports[0]);
63 
64 static struct sdb_session session_table[MLSVC_DOMAIN_MAX];
65 static mutex_t smbrdr_screate_mtx;
66 static uint32_t session_id = 0;
67 
68 static struct sdb_session *smbrdr_session_init(smb_ntdomain_t *);
69 static int smbrdr_trnsprt_connect(struct sdb_session *, uint16_t);
70 static int smbrdr_session_connect(smb_ntdomain_t *);
71 static int smbrdr_smb_negotiate(struct sdb_session *);
72 static int smbrdr_echo(struct sdb_session *);
73 static void smbrdr_session_disconnect(struct sdb_session *, int);
74 
75 
76 static void
77 smbrdr_session_clear(struct sdb_session *session)
78 {
79 	bzero(session, sizeof (struct sdb_session) - sizeof (rwlock_t));
80 }
81 
82 /*
83  * Entry pointy for smbrdr initialization.
84  */
85 void
86 smbrdr_init(void)
87 {
88 	smbrdr_ipc_init();
89 }
90 
91 /*
92  * mlsvc_disconnect
93  *
94  * Disconnects the session with given server.
95  */
96 void
97 mlsvc_disconnect(char *server)
98 {
99 	struct sdb_session *session;
100 
101 	session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE);
102 	if (session) {
103 		smbrdr_session_disconnect(session, 0);
104 		smbrdr_session_unlock(session);
105 	}
106 }
107 
108 /*
109  * smbrdr_negotiate
110  *
111  * Negotiate a session with a domain controller in the specified domain.
112  * The domain must be one of values from the smbinfo that indicates the
113  * resource domain or the account domain.
114  *
115  * If a session already exists, we can use that one. Otherwise we create
116  * a new one. This sets up the session key and session security info that
117  * we'll need later to authenticate the user. The session security info
118  * is returned to support the SMB client pass-through authentication
119  * interface.
120  *
121  * Returns 0 on success, otherwise -1.
122  */
123 /*ARGSUSED*/
124 int
125 smbrdr_negotiate(char *domain)
126 {
127 	struct sdb_session *session = 0;
128 	smb_ntdomain_t *di;
129 	int retry = 1;
130 	int res = 0;
131 
132 	if ((di = smb_getdomaininfo(0)) == NULL) {
133 		syslog(LOG_DEBUG, "smbrdr_negotiate: cannot access domain");
134 		return (-1);
135 	}
136 
137 	/*
138 	 * The mutex is to make session lookup and create atomic
139 	 * so we don't end up with two sessions with the same
140 	 * server.
141 	 */
142 	(void) mutex_lock(&smbrdr_screate_mtx);
143 	while (retry > 0) {
144 		session = smbrdr_session_lock(di->server, 0, SDB_SLCK_WRITE);
145 		if (session != 0) {
146 			if (nb_keep_alive(session->sock) == 0) {
147 				/* session is good, use it */
148 				smbrdr_session_unlock(session);
149 				break;
150 			} else {
151 				/* stale session */
152 				session->state = SDB_SSTATE_STALE;
153 				smbrdr_session_unlock(session);
154 			}
155 		}
156 
157 		if (smbrdr_session_connect(di) != 0) {
158 			if (retry > 0) {
159 				di = smb_getdomaininfo(0);
160 				if (di == NULL) {
161 					res = -1;
162 					break;
163 				}
164 				retry--;
165 			}
166 		} else {
167 			/* session is created */
168 			retry = 0;
169 		}
170 	}
171 	(void) mutex_unlock(&smbrdr_screate_mtx);
172 
173 	if (di == NULL)
174 		syslog(LOG_DEBUG, "smbrdr_negotiate: cannot access domain");
175 	return (res);
176 }
177 
178 /*
179  * smbrdr_session_connect
180  *
181  * This is the entry point for establishing an SMB connection to a
182  * domain controller. A session structure is allocated, a netbios
183  * session is set up and the SMB protocol is negotiated. If this is
184  * successful, the returned session structure can be used to logon
185  * to the the domain. A null pointer is returned if the connect fails.
186  */
187 static int
188 smbrdr_session_connect(smb_ntdomain_t *di)
189 {
190 	struct sdb_session *session;
191 	uint16_t port;
192 	int rc = 0;
193 
194 	/*
195 	 * smbrdr_session_init() will lock the session so that it wouldn't
196 	 * be accessible until it's established otherwise another thread
197 	 * might get access to a session which is not fully established.
198 	 */
199 	if ((session = smbrdr_session_init(di)) == NULL) {
200 		syslog(LOG_DEBUG, "smbrdr_session_init failed");
201 		return (-1);
202 	}
203 
204 	for (port = 0; port < smbrdr_nports; ++port) {
205 		syslog(LOG_DEBUG, "smbrdr: trying port %d",
206 		    smbrdr_ports[port]);
207 
208 		rc = smbrdr_trnsprt_connect(session, smbrdr_ports[port]);
209 
210 		if (rc == 0) {
211 			syslog(LOG_DEBUG, "smbrdr: connected port %d",
212 			    smbrdr_ports[port]);
213 			break;
214 		}
215 	}
216 
217 	if (rc < 0) {
218 		smbrdr_session_clear(session);
219 		smbrdr_session_unlock(session);
220 		syslog(LOG_DEBUG, "smbrdr: connect failed");
221 		return (-1);
222 	}
223 
224 	if (smbrdr_smb_negotiate(session) < 0) {
225 		(void) close(session->sock);
226 		smbrdr_session_clear(session);
227 		smbrdr_session_unlock(session);
228 		syslog(LOG_DEBUG, "smbrdr: negotiate failed");
229 		return (-1);
230 	}
231 
232 	smbrdr_session_unlock(session);
233 	return (0);
234 }
235 
236 
237 /*
238  * smbrdr_trnsprt_connect
239  *
240  * Set up the TCP/IP and NETBIOS protocols for a session. This is just
241  * standard socket sutff. The paranoia check for socket descriptor 0
242  * is because we had a problem with this value and the console telnet
243  * interface will lock up if we use and/or close stdin (0).
244  *
245  * Return 0 on success. Otherwise return (-1) to indicate a problem.
246  */
247 static int
248 smbrdr_trnsprt_connect(struct sdb_session *sess, uint16_t port)
249 {
250 	char hostname[MAXHOSTNAMELEN];
251 	struct sockaddr_in sin;
252 	int sock, rc;
253 	mts_wchar_t unicode_server_name[SMB_PI_MAX_DOMAIN];
254 	char server_name[SMB_PI_MAX_DOMAIN];
255 	unsigned int cpid = oem_get_smb_cpid();
256 
257 	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) <= 0) {
258 		syslog(LOG_DEBUG, "smbrdr: socket failed: %s", strerror(errno));
259 		return (-1);
260 	}
261 
262 	bzero(&sin, sizeof (struct sockaddr_in));
263 	sin.sin_family = AF_INET;
264 	sin.sin_addr.s_addr = sess->di.ipaddr;
265 	sin.sin_port = htons(port);
266 
267 	if ((rc = connect(sock, (struct sockaddr *)&sin, sizeof (sin))) < 0) {
268 		syslog(LOG_DEBUG, "smbrdr: connect failed: %s",
269 		    strerror(errno));
270 		if (sock != 0)
271 			(void) close(sock);
272 		return (-1);
273 	}
274 
275 	(void) mts_mbstowcs(unicode_server_name, sess->di.server,
276 	    SMB_PI_MAX_DOMAIN);
277 	rc = unicodestooems(server_name, unicode_server_name,
278 	    SMB_PI_MAX_DOMAIN, cpid);
279 	if (rc == 0) {
280 		syslog(LOG_DEBUG, "smbrdr: unicode conversion failed");
281 		if (sock != 0)
282 			(void) close(sock);
283 		return (-1);
284 	}
285 
286 	/*
287 	 * If we are using NetBIOS, we need to set up a NETBIOS session.
288 	 * This typically implies that we will be using port 139.
289 	 * Otherwise, we're doing NetBIOS-less SMB, i.e. SMB over TCP,
290 	 * which is typically on port 445.
291 	 */
292 	if (port == SSN_SRVC_TCP_PORT) {
293 		if (smb_getnetbiosname(hostname, MAXHOSTNAMELEN) != 0) {
294 			syslog(LOG_DEBUG, "smbrdr: no hostname");
295 			if (sock != 0)
296 				(void) close(sock);
297 			return (-1);
298 		}
299 
300 		rc = nb_session_request(sock,
301 		    server_name, sess->scope, hostname, sess->scope);
302 
303 		if (rc != 0) {
304 			syslog(LOG_DEBUG,
305 			    "smbrdr: NBT session request to %s failed %d",
306 			    server_name, rc);
307 			if (sock != 0)
308 				(void) close(sock);
309 			return (-1);
310 		}
311 	}
312 
313 	sess->sock = sock;
314 	sess->port = port;
315 	syslog(LOG_DEBUG, "smbrdr: connected on port %d", port);
316 	sess->state = SDB_SSTATE_CONNECTED;
317 	return (0);
318 }
319 
320 /*
321  * smbrdr_smb_negotiate
322  *
323  * Negotiate the protocol we are going to use as described in CIFS
324  * section 4.1.1. The only protocol we support is NT LM 0.12, so we
325  * really expect to see dialect 0 in the response. The only other
326  * data gathered is the session key.
327  *
328  * Negotiate using ASCII strings.
329  *
330  * Return 0 on success. Otherwise return a -ve error code.
331  */
332 static int
333 smbrdr_smb_negotiate(struct sdb_session *sess)
334 {
335 	unsigned short dialect;
336 	smbrdr_handle_t srh;
337 	smb_hdr_t smb_hdr;
338 	smb_msgbuf_t *mb;
339 	DWORD status;
340 	int rc;
341 	uint8_t tmp_secmode;
342 	uint8_t tmp_clen;
343 
344 	status = smbrdr_request_init(&srh, SMB_COM_NEGOTIATE, sess, 0, 0);
345 
346 	if (status != NT_STATUS_SUCCESS)
347 		return (-1);
348 
349 	mb = &srh.srh_mbuf;
350 	rc = smb_msgbuf_encode(mb, "bwbs", 0, 12, 0x02, "NT LM 0.12");
351 	if (rc <= 0) {
352 		smbrdr_handle_free(&srh);
353 		return (-1);
354 	}
355 
356 	status = smbrdr_exchange(&srh, &smb_hdr, 0);
357 	if (status != NT_STATUS_SUCCESS) {
358 		syslog(LOG_DEBUG, "smbrdr: negotiate: %s",
359 		    xlate_nt_status(status));
360 		smbrdr_handle_free(&srh);
361 		return (-1);
362 	}
363 
364 	sess->secmode = 0;
365 	sess->sesskey = 0;
366 	sess->challenge_len = 0;
367 
368 	rc = smb_msgbuf_decode(mb,
369 	    "1.(dialect)w(mode)b12.(key)l(cap)l10.(keylen)b2.",
370 	    &dialect, &tmp_secmode, &sess->sesskey, &sess->remote_caps,
371 	    &tmp_clen);
372 
373 	if (rc <= 0 || dialect != 0) {
374 		smbrdr_handle_free(&srh);
375 		return (-1);
376 	}
377 	sess->secmode = tmp_secmode;
378 	sess->challenge_len = tmp_clen;
379 
380 	rc = smb_msgbuf_decode(mb, "#c",
381 	    sess->challenge_len, sess->challenge_key);
382 	if (rc <= 0) {
383 		smbrdr_handle_free(&srh);
384 		return (-1);
385 	}
386 
387 	smbrdr_handle_free(&srh);
388 
389 	if ((sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) &&
390 	    (sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED)) {
391 		sess->sign_ctx.ssc_flags |= SMB_SCF_REQUIRED;
392 		syslog(LOG_DEBUG, "smbrdr: %s: signing required",
393 		    sess->di.server);
394 	}
395 
396 	sess->state = SDB_SSTATE_NEGOTIATED;
397 	return (0);
398 }
399 
400 /*
401  * smbrdr_session_init
402  *
403  * Allocate an available slot in session table for the specified domain
404  * information.
405  *
406  * IMPORTANT! the returned session will be locked caller has to unlock
407  *            it by calling smbrdr_session_unlock() after it's done with
408  *            the pointer.
409  */
410 static struct sdb_session *
411 smbrdr_session_init(smb_ntdomain_t *di)
412 {
413 	struct sdb_session *session = NULL;
414 	int i;
415 
416 	if (di == NULL)
417 		return (NULL);
418 
419 	for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) {
420 		session = &session_table[i];
421 
422 		(void) rw_wrlock(&session->rwl);
423 		if (session->state == SDB_SSTATE_START) {
424 			smbrdr_session_clear(session);
425 			bcopy(di, &session->di, sizeof (smb_ntdomain_t));
426 			(void) utf8_strupr(session->di.domain);
427 			(void) utf8_strupr(session->di.server);
428 
429 			(void) smb_config_getstr(SMB_CI_NBSCOPE, session->scope,
430 			    sizeof (session->scope));
431 
432 			(void) strlcpy(session->native_os,
433 			    "Solaris", SMB_PI_MAX_NATIVE_OS);
434 			(void) strlcpy(session->native_lanman,
435 			    "Windows NT 4.0", SMB_PI_MAX_LANMAN);
436 			session->sock = -1;
437 			session->port = smbrdr_ports[0];
438 			session->smb_flags = SMB_FLAGS_CANONICALIZED_PATHS
439 			    | SMB_FLAGS_CASE_INSENSITIVE;
440 
441 			session->smb_flags2 = SMB_FLAGS2_KNOWS_LONG_NAMES
442 			    | SMB_FLAGS2_KNOWS_EAS;
443 
444 			/*
445 			 * Note that by sending vc=0 server will shutdown all
446 			 * the other connections with NAS if there is any.
447 			 */
448 			session->vc = 0;
449 			session->sid = ++session_id;
450 			if (session->sid == 0)
451 				session->sid = 1;
452 			session->state = SDB_SSTATE_INIT;
453 			return (session);
454 		}
455 		(void) rw_unlock(&session->rwl);
456 	}
457 
458 	syslog(LOG_DEBUG, "smbrdr: no session available");
459 	return (NULL);
460 }
461 
462 /*
463  * smbrdr_session_disconnect
464  *
465  * This is the entry point for disconnecting an SMB connection. Ensure
466  * that all logons and shares associated with this session are
467  * terminated and then free the session.
468  *
469  * if 'cleanup' is 1 it means that only sessions that are not active
470  * should be cleaned up. if 'cleanup' is 0 disconnect the session in any
471  * states.
472  */
473 static void
474 smbrdr_session_disconnect(struct sdb_session *session, int cleanup)
475 {
476 	int state;
477 
478 	if (session == NULL)
479 		return;
480 
481 	state = session->state;
482 	if ((state != SDB_SSTATE_DISCONNECTING) &&
483 	    (state != SDB_SSTATE_CLEANING) &&
484 	    (state != SDB_SSTATE_START)) {
485 		if ((cleanup == 0) || (state == SDB_SSTATE_STALE)) {
486 			/*
487 			 * if session is in stale state it means the connection
488 			 * is lost so no logoff, tdcon, or close can actually
489 			 * be sent, thus only cleanup our side.
490 			 */
491 			session->state = (state == SDB_SSTATE_STALE)
492 			    ? SDB_SSTATE_CLEANING : SDB_SSTATE_DISCONNECTING;
493 			(void) smbrdr_logoffx(&session->logon);
494 			nb_close(session->sock);
495 			smbrdr_session_clear(session);
496 		}
497 	}
498 }
499 
500 /*
501  * smbrdr_session_unlock
502  *
503  * Unlock given session structure.
504  */
505 void
506 smbrdr_session_unlock(struct sdb_session *session)
507 {
508 	if (session)
509 		(void) rw_unlock(&session->rwl);
510 }
511 
512 /*
513  * smbrdr_session_lock
514  *
515  * Lookup the session associated with the specified domain controller.
516  * If a match is found, we return a pointer to the session, Otherwise
517  * we return null. Only sessions in "negotiated" state are checked.
518  * This mechanism is very simple and implies that we
519  * should only ever have one session open to any domain controller.
520  *
521  * IMPORTANT! the returned session will be locked caller has to unlock
522  *            it by calling smbrdr_session_unlock() after it's done with
523  *            the pointer.
524  */
525 struct sdb_session *
526 smbrdr_session_lock(char *server, char *username, int lmode)
527 {
528 	struct sdb_session *session;
529 	int i;
530 
531 	if (server == NULL)
532 		return (NULL);
533 
534 	for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) {
535 		session = &session_table[i];
536 
537 		(lmode == SDB_SLCK_READ) ? (void) rw_rdlock(&session->rwl) :
538 		    (void) rw_wrlock(&session->rwl);
539 
540 		if ((session->state == SDB_SSTATE_NEGOTIATED) &&
541 		    (strcasecmp(session->di.server, server) == 0)) {
542 			if (username) {
543 				if (strcasecmp(username,
544 				    session->logon.username) == 0)
545 					return (session);
546 
547 				(void) rw_unlock(&session->rwl);
548 				return (NULL);
549 			}
550 			return (session);
551 		}
552 
553 		(void) rw_unlock(&session->rwl);
554 	}
555 
556 	return (NULL);
557 }
558 
559 /*
560  * mlsvc_session_native_values
561  *
562  * Given a file id (i.e. a named pipe fid), return the remote native
563  * OS and LM values for the associated session.
564  */
565 int
566 mlsvc_session_native_values(int fid, int *remote_os,
567     int *remote_lm, int *pdc_type)
568 {
569 	struct sdb_session *session;
570 	struct sdb_netuse *netuse;
571 	struct sdb_ofile *ofile;
572 
573 	if (remote_os == NULL || remote_lm == NULL)
574 		return (-1);
575 
576 	if ((ofile = smbrdr_ofile_get(fid)) == 0) {
577 		syslog(LOG_DEBUG,
578 		    "mlsvc_session_native_values: unknown file (%d)", fid);
579 		return (-1);
580 	}
581 
582 	netuse = ofile->netuse;
583 	session = netuse->session;
584 
585 	*remote_os = session->remote_os;
586 	*remote_lm = session->remote_lm;
587 	if (pdc_type)
588 		*pdc_type = session->pdc_type;
589 	smbrdr_ofile_put(ofile);
590 	return (0);
591 }
592 
593 /*
594  * smbrdr_dump_sessions
595  *
596  * Debug function to dump the session table.
597  */
598 void
599 smbrdr_dump_sessions(void)
600 {
601 	struct sdb_session *session;
602 	struct sdb_logon *logon;
603 	char ipstr[16];
604 	int i;
605 
606 	for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) {
607 		session = &session_table[i];
608 
609 		(void) rw_rdlock(&session->rwl);
610 		if (session->state != SDB_SSTATE_START) {
611 			(void) inet_ntop(AF_INET,
612 			    (const void *)(&session->di.ipaddr),
613 			    ipstr, sizeof (ipstr));
614 
615 			syslog(LOG_DEBUG, "session[%d]: state=%d",
616 			    i, session->state);
617 			syslog(LOG_DEBUG, "session[%d]: %s %s (%s)", i,
618 			    session->di.domain, session->di.server, ipstr);
619 			syslog(LOG_DEBUG, "session[%d]: %s %s (sock=%d)", i,
620 			    session->native_os, session->native_lanman,
621 			    session->sock);
622 
623 			logon = &session->logon;
624 			if (logon->type != SDB_LOGON_NONE)
625 				syslog(LOG_DEBUG, "logon[%d]: %s (uid=%d)",
626 				    i, logon->username, logon->uid);
627 		}
628 		(void) rw_unlock(&session->rwl);
629 	}
630 }
631 
632 /*
633  * mlsvc_echo
634  */
635 int
636 mlsvc_echo(char *server)
637 {
638 	struct sdb_session *session;
639 	int res = 0;
640 
641 	if ((session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE)) == 0)
642 		return (1);
643 
644 	if (smbrdr_echo(session) != 0) {
645 		session->state = SDB_SSTATE_STALE;
646 		res = -1;
647 	}
648 
649 	smbrdr_session_unlock(session);
650 	return (res);
651 }
652 
653 /*
654  * smbrdr_echo
655  *
656  * This request can be used to test the connection to the server. The
657  * server should echo the data sent. The server should ignore the tid
658  * in the header, so this request when there are no tree connections.
659  * See CIFS/1.0 section 4.1.7.
660  *
661  * Return 0 on success. Otherwise return a -ve error code.
662  */
663 static int
664 smbrdr_echo(struct sdb_session *session)
665 {
666 	static char *echo_str = "smbrdr";
667 	smbrdr_handle_t srh;
668 	smb_hdr_t smb_hdr;
669 	DWORD status;
670 	int rc;
671 
672 	if ((session->state == SDB_SSTATE_DISCONNECTING) ||
673 	    (session->state == SDB_SSTATE_CLEANING) ||
674 	    (session->state == SDB_SSTATE_STALE)) {
675 		return (-1);
676 	}
677 
678 	status = smbrdr_request_init(&srh, SMB_COM_ECHO, session, 0, 0);
679 	if (status != NT_STATUS_SUCCESS)
680 		return (-1);
681 
682 	rc = smb_msgbuf_encode(&srh.srh_mbuf, "bwws", 1, 1,
683 	    strlen(echo_str), echo_str);
684 	if (rc <= 0) {
685 		smbrdr_handle_free(&srh);
686 		return (-1);
687 	}
688 
689 	status = smbrdr_exchange(&srh, &smb_hdr, 10);
690 	smbrdr_handle_free(&srh);
691 
692 	if (status != NT_STATUS_SUCCESS)
693 		return (-1);
694 
695 	return (0);
696 }
697