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