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