xref: /onnv-gate/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_logon.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  * SMB session logon and logoff functions. See CIFS section 4.1.
30  */
31 
32 #include <pthread.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <syslog.h>
36 #include <synch.h>
37 #include <sys/errno.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <smbsrv/libsmbrdr.h>
43 #include <smbsrv/ntstatus.h>
44 #include <smbsrv/smb.h>
45 #include <smbrdr_ipc_util.h>
46 #include <smbrdr.h>
47 
48 static int smbrdr_anonymous_logon(char *domain_controller, char *domain_name);
49 static int smbrdr_auth_logon(char *domain_controller, char *domain_name,
50     char *username);
51 static int smbrdr_session_setupx(struct sdb_logon *logon);
52 static boolean_t smbrdr_logon_validate(char *server, char *username);
53 static struct sdb_logon *smbrdr_logon_init(struct sdb_session *session,
54     char *username, unsigned char *pwd);
55 static int smbrdr_logon_user(char *server, char *username, unsigned char *pwd);
56 static int smbrdr_authenticate(char *primary_domain, char *account_name,
57     unsigned char *pwd);
58 
59 /*
60  * mlsvc_logon
61  *
62  * If the username is MLSVC_ANON_USER, an anonymous session will be established.
63  * Otherwise, an authenticated session will be established based on the
64  * specified credentials.
65  */
66 int
67 mlsvc_logon(char *domain_controller, char *domain, char *username)
68 {
69 	int rc;
70 
71 	if (strcmp(username, MLSVC_ANON_USER) == 0)
72 		rc = smbrdr_anonymous_logon(domain_controller, domain);
73 	else
74 		rc = smbrdr_auth_logon(domain_controller, domain, username);
75 
76 	return (rc);
77 }
78 
79 /*
80  * smbrdr_anonymous_logon
81  *
82  * Set up an anonymous session. If the session to the resource domain
83  * controller appears to be okay we shouldn't need to do anything here.
84  * Otherwise we clean up the stale session and create a new one.
85  */
86 static int
87 smbrdr_anonymous_logon(char *domain_controller, char *domain_name)
88 {
89 	if (smbrdr_logon_validate(domain_controller, MLSVC_ANON_USER))
90 		return (0);
91 
92 	if (smbrdr_negotiate(domain_name) != 0) {
93 		syslog(LOG_DEBUG, "smbrdr_anonymous_logon: negotiate failed");
94 		return (-1);
95 	}
96 
97 	if (smbrdr_logon_user(domain_controller, MLSVC_ANON_USER, 0) < 0) {
98 		syslog(LOG_DEBUG, "smbrdr_anonymous_logon: logon failed");
99 		return (-1);
100 	}
101 
102 	return (0);
103 }
104 
105 int
106 mlsvc_user_getauth(char *domain_controller, char *username,
107     smb_auth_info_t *auth)
108 {
109 	struct sdb_session *session;
110 
111 	if (auth) {
112 		bzero(auth, sizeof (smb_auth_info_t));
113 		session = smbrdr_session_lock(domain_controller, username,
114 		    SDB_SLCK_READ);
115 		if (session) {
116 			*auth = session->logon.auth;
117 			smbrdr_session_unlock(session);
118 			return (0);
119 		}
120 	}
121 
122 	return (-1);
123 }
124 
125 /*
126  * smbrdr_auth_logon
127  *
128  * Set up a user session. If the session to the resource domain controller
129  * appears to be okay we shouldn't need to do anything here. Otherwise we
130  * clean up the stale session and create a new one. Once a session is
131  * established, we leave it intact. It should only need to be set up again
132  * due to an inactivity timeout or a domain controller reset.
133  */
134 static int
135 smbrdr_auth_logon(char *domain_controller, char *domain_name, char *username)
136 {
137 	int erc;
138 	unsigned char *pwd_hash = NULL;
139 
140 	if (username == NULL || *username == 0) {
141 		syslog(LOG_DEBUG, "smbrdr_auth_logon: no username");
142 		return (-1);
143 	}
144 
145 	pwd_hash = smbrdr_ipc_get_passwd();
146 	if (!pwd_hash || *pwd_hash == 0) {
147 		syslog(LOG_DEBUG, "smbrdr_auth_logon: no password");
148 		return (-1);
149 	}
150 
151 	if (smbrdr_logon_validate(domain_controller, username))
152 		return (0);
153 
154 	if (smbrdr_negotiate(domain_name) != 0) {
155 		syslog(LOG_DEBUG, "smbrdr_auth_logon: negotiate failed");
156 		return (-1);
157 	}
158 
159 	erc = smbrdr_authenticate(domain_name, username, pwd_hash);
160 	return ((erc == AUTH_USER_GRANT) ? 0 : -1);
161 }
162 
163 /*
164  * smbrdr_authenticate
165  *
166  * Authenticate primary_domain\account_name.
167  *
168  * Returns:
169  * 0	User access granted
170  * 1	Guest access granted
171  * 2	IPC access granted
172  * (<0) Error
173  */
174 static int
175 smbrdr_authenticate(char *primary_domain, char *account_name,
176     unsigned char *pwd)
177 {
178 	smb_ntdomain_t *di;
179 
180 	if (pwd == NULL)
181 		return (AUTH_USER_GRANT | AUTH_IPC_ONLY_GRANT);
182 
183 
184 	if ((di = smb_getdomaininfo(0)) == 0) {
185 		syslog(LOG_DEBUG, "smbrdr_authenticate[%s]: %s", account_name,
186 		    xlate_nt_status(NT_STATUS_CANT_ACCESS_DOMAIN_INFO));
187 		return (-1);
188 	}
189 
190 	/*
191 	 * Ensure that the domain name is uppercase.
192 	 */
193 	(void) utf8_strupr(primary_domain);
194 
195 	/*
196 	 * We can only authenticate a user via a controller in the user's
197 	 * primary domain. If the user's domain name doesn't match the
198 	 * authenticating server's domain, reject the request before we
199 	 * create a logon entry for the user. Although the logon will be
200 	 * denied eventually, we don't want a logon structure for a user
201 	 * in the resource domain that is pointing to a session structure
202 	 * for the account domain. If this happened to be our resource
203 	 * domain user, we would not be able to use that account to connect
204 	 * to the resource domain.
205 	 */
206 	if (strcasecmp(di->domain, primary_domain)) {
207 		syslog(LOG_DEBUG, "smbrdr_authenticate: %s\\%s: invalid domain",
208 		    primary_domain, account_name);
209 		return (-2);
210 	}
211 
212 	return (smbrdr_logon_user(di->server, account_name, pwd));
213 }
214 
215 /*
216  * smbrdr_logon_user
217  *
218  * This is the entry point for logging  a user onto the domain. The
219  * session structure should have been obtained via a successful call
220  * to smbrdr_smb_connect. We allocate a logon structure to hold the
221  * user details and attempt to logon using smbrdr_session_setupx.
222  * Note that we expect the password fields to have been encrypted
223  * before this call.
224  *
225  * On success, the logon structure will be returned. Otherwise a null
226  * pointer will be returned.
227  */
228 static int
229 smbrdr_logon_user(char *server, char *username, unsigned char *pwd)
230 {
231 	struct sdb_session *session;
232 	struct sdb_logon *logon;
233 	struct sdb_logon old_logon;
234 
235 	if ((server == NULL) || (username == NULL) ||
236 	    ((strcmp(username, MLSVC_ANON_USER) != 0) && (pwd == NULL))) {
237 		return (-1);
238 	}
239 
240 	session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE);
241 	if (session == NULL) {
242 		syslog(LOG_DEBUG, "smbrdr_logon_user: %s: no session with %s",
243 		    username, server);
244 		return (-1);
245 	}
246 
247 	bzero(&old_logon, sizeof (struct sdb_logon));
248 
249 	logon = &session->logon;
250 	if (logon->type != SDB_LOGON_NONE) {
251 		if (strcasecmp(logon->username, username) == 0) {
252 			/* The requested user has already been logged in */
253 			smbrdr_session_unlock(session);
254 			return ((logon->type == SDB_LOGON_GUEST)
255 			    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT);
256 		}
257 
258 		old_logon = *logon;
259 	}
260 
261 	logon = smbrdr_logon_init(session, username, pwd);
262 
263 	if (logon == NULL) {
264 		syslog(LOG_DEBUG, "smbrdr_logon_user: %s: %s",
265 		    username, strerror(ENOMEM));
266 		smbrdr_session_unlock(session);
267 		return (-1);
268 	}
269 
270 	if (smbrdr_session_setupx(logon) < 0) {
271 		free(logon);
272 		smbrdr_session_unlock(session);
273 		return (-1);
274 	}
275 
276 	session->logon = *logon;
277 	free(logon);
278 
279 	if (old_logon.type != SDB_LOGON_NONE) {
280 		(void) smbrdr_logoffx(&old_logon);
281 	}
282 
283 	smbrdr_session_unlock(session);
284 	return ((logon->type == SDB_LOGON_GUEST)
285 	    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT);
286 }
287 
288 
289 /*
290  * smbrdr_session_setupx
291  *
292  * Build and send an SMB session setup command. This is used to log a
293  * user onto the domain. See CIFS section 4.1.2.
294  *
295  * Returns 0 on success. Otherwise returns a -ve error code.
296  */
297 static int
298 smbrdr_session_setupx(struct sdb_logon *logon)
299 {
300 	struct sdb_session *session;
301 	smb_hdr_t smb_hdr;
302 	smbrdr_handle_t srh;
303 	smb_msgbuf_t *mb;
304 	char *native_os;
305 	char *native_lanman;
306 	unsigned short data_bytes;
307 	unsigned short guest;
308 	unsigned long capabilities;
309 	unsigned short null_size;
310 	size_t (*strlen_fn)(const char *s);
311 	DWORD status;
312 	int rc;
313 
314 	/*
315 	 * Paranoia check - we should never get this
316 	 * far without a valid session structure.
317 	 */
318 	if ((session = logon->session) == NULL)
319 		return (-1);
320 
321 	if (session->remote_caps & CAP_UNICODE) {
322 		strlen_fn = mts_wcequiv_strlen;
323 		null_size = sizeof (mts_wchar_t);
324 		session->smb_flags2 |= SMB_FLAGS2_UNICODE;
325 	} else {
326 		strlen_fn = strlen;
327 		null_size = sizeof (char);
328 	}
329 
330 	if (smbrdr_sign_init(session, logon) < 0) {
331 		syslog(LOG_DEBUG,
332 		    "smbrdr_session_setupx: smbrdr_sign_init failed");
333 		return (-1);
334 	}
335 
336 	status = smbrdr_request_init(&srh, SMB_COM_SESSION_SETUP_ANDX,
337 	    session, 0, 0);
338 
339 	if (status != NT_STATUS_SUCCESS) {
340 		smbrdr_sign_fini(session);
341 		syslog(LOG_DEBUG, "smbrdr_session_setupx: %s",
342 		    xlate_nt_status(status));
343 		return (-1);
344 	}
345 	mb = &srh.srh_mbuf;
346 
347 	/*
348 	 * Regardless of the server's capabilities or what's
349 	 * reported in smb_flags2, we should report our full
350 	 * capabilities.
351 	 */
352 	capabilities = CAP_UNICODE | CAP_NT_SMBS | CAP_STATUS32;
353 
354 	/*
355 	 * Compute the BCC for unicode or ASCII strings.
356 	 */
357 	data_bytes  = logon->auth.ci_len + logon->auth.cs_len + null_size;
358 	data_bytes += strlen_fn(session->native_os) + null_size;
359 	data_bytes += strlen_fn(session->native_lanman) + null_size;
360 
361 	if (logon->type == SDB_LOGON_ANONYMOUS) {
362 		/*
363 		 * Anonymous logon: no username or domain name.
364 		 * We still need to include two null characters.
365 		 */
366 		data_bytes += (2 * null_size);
367 
368 		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllwlu.u.",
369 		    13,				/* smb_wct */
370 		    0xff,			/* AndXCommand (none) */
371 		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
372 		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
373 		    1,				/* MaxMpxCount */
374 		    0,				/* VcNumber */
375 		    0,				/* SessionKey */
376 		    1,				/* CaseInsensitivePassLength */
377 		    0,				/* CaseSensitivePassLength */
378 		    0,				/* Reserved */
379 		    capabilities,		/* Capabilities */
380 		    data_bytes,			/* smb_bcc */
381 		    0,				/* No user or domain */
382 		    session->native_os,		/* NativeOS */
383 		    session->native_lanman);	/* NativeLanMan */
384 	} else {
385 		data_bytes += strlen_fn(logon->username) + null_size;
386 		data_bytes += strlen_fn(session->di.domain) + null_size;
387 
388 		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllw#c#cuuu.u.",
389 		    13,				/* smb_wct */
390 		    0xff,			/* AndXCommand (none) */
391 		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
392 		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
393 		    1,				/* MaxMpxCount */
394 		    session->vc,		/* VcNumber */
395 		    session->sesskey,		/* SessionKey */
396 		    logon->auth.ci_len,		/* CaseInsensitivePassLength */
397 		    logon->auth.cs_len,		/* CaseSensitivePassLength */
398 		    0,				/* Reserved */
399 		    capabilities,		/* Capabilities */
400 		    data_bytes,			/* smb_bcc */
401 		    logon->auth.ci_len,		/* ci length spec */
402 		    logon->auth.ci,		/* CaseInsensitivePassword */
403 		    logon->auth.cs_len,		/* cs length spec */
404 		    logon->auth.cs,		/* CaseSensitivePassword */
405 		    logon->username,		/* AccountName */
406 		    session->di.domain,		/* PrimaryDomain */
407 		    session->native_os,		/* NativeOS */
408 		    session->native_lanman);	/* NativeLanMan */
409 	}
410 
411 	if (rc <= 0) {
412 		syslog(LOG_DEBUG, "smbrdr_session_setupx: encode failed");
413 		smbrdr_handle_free(&srh);
414 		smbrdr_sign_fini(session);
415 		return (-1);
416 	}
417 
418 	status = smbrdr_exchange(&srh, &smb_hdr, 0);
419 	if (status != NT_STATUS_SUCCESS) {
420 		syslog(LOG_DEBUG, "smbrdr_session_setupx: %s",
421 		    xlate_nt_status(status));
422 		smbrdr_handle_free(&srh);
423 		smbrdr_sign_fini(session);
424 		return (-1);
425 	}
426 
427 	rc = smb_msgbuf_decode(mb, "5.w2.u", &guest, &native_os);
428 
429 	/*
430 	 * There was a problem in decoding response from
431 	 * a Samba 2.x PDC. This server sends strings in ASCII
432 	 * format and there is one byte with value 0 between
433 	 * native_os and native_lm:
434 	 *
435 	 *				 FF 53 4D 42 73 00		.SMBs.
436 	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
437 	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 01 ......d.........
438 	 * 00 1C 00 55 6E 69 78 00 53 61 6D 62 61 20 32 2E ...Unix.Samba.2.
439 	 * 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00    2.8a.SAMBA_DOM.
440 	 *
441 	 * The byte doesn't seem to be padding because when change in
442 	 * native OS from Unix to Unix1 the 0 byte is still there:
443 	 *
444 	 *				 FF 53 4D 42 73 00		.SMBs.
445 	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
446 	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 00 ......d.........
447 	 * 00 1D 00 55 6E 69 78 31 00 53 61 6D 62 61 20 32 ...Unix1.Samba.2
448 	 * 2E 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00 .2.8a.SAMBA_DOM.
449 	 */
450 	if (rc > 0) {
451 		if (session->remote_caps & CAP_UNICODE)
452 			rc = smb_msgbuf_decode(mb, "u", &native_lanman);
453 		else
454 			rc = smb_msgbuf_decode(mb, ".u", &native_lanman);
455 	}
456 
457 	if (rc <= 0) {
458 		syslog(LOG_DEBUG, "smbrdr_session_setupx: decode failed");
459 		smbrdr_handle_free(&srh);
460 		smbrdr_sign_fini(session);
461 		return (-1);
462 	}
463 
464 	session->remote_os = smbnative_os_value(native_os);
465 	session->remote_lm = smbnative_lm_value(native_lanman);
466 	session->pdc_type  = smbnative_pdc_value(native_lanman);
467 
468 	logon->uid = smb_hdr.uid;
469 	if (guest)
470 		logon->type = SDB_LOGON_GUEST;
471 
472 	smbrdr_handle_free(&srh);
473 	smbrdr_sign_unset_key(session);
474 
475 	logon->state = SDB_LSTATE_SETUP;
476 
477 	return (0);
478 }
479 
480 /*
481  * smbrdr_logoffx
482  *
483  * Build and send an SMB session logoff (SMB_COM_LOGOFF_ANDX) command.
484  * This is the inverse of an SMB_COM_SESSION_SETUP_ANDX. See CIFS
485  * section 4.1.3. The logon structure should have been obtained from a
486  * successful call to smbrdr_logon_user.
487  *
488  * Returns 0 on success. Otherwise returns a -ve error code.
489  */
490 int
491 smbrdr_logoffx(struct sdb_logon *logon)
492 {
493 	struct sdb_session *session;
494 	smbrdr_handle_t srh;
495 	smb_hdr_t smb_hdr;
496 	DWORD status;
497 	int rc;
498 
499 	if (logon->state != SDB_LSTATE_SETUP) {
500 		/* No user to logoff */
501 		bzero(logon, sizeof (struct sdb_logon));
502 		return (0);
503 	}
504 
505 	if ((session = logon->session) == 0) {
506 		bzero(logon, sizeof (struct sdb_logon));
507 		return (0);
508 	}
509 
510 	logon->state = SDB_LSTATE_LOGGING_OFF;
511 	smbrdr_netuse_logoff(logon->uid);
512 
513 	if ((session->state != SDB_SSTATE_NEGOTIATED) &&
514 	    (session->state != SDB_SSTATE_DISCONNECTING)) {
515 		bzero(logon, sizeof (struct sdb_logon));
516 		return (0);
517 	}
518 
519 	status = smbrdr_request_init(&srh, SMB_COM_LOGOFF_ANDX,
520 	    session, logon, 0);
521 
522 	if (status != NT_STATUS_SUCCESS) {
523 		logon->state = SDB_LSTATE_SETUP;
524 		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: %s", logon->username,
525 		    xlate_nt_status(status));
526 		return (-1);
527 	}
528 
529 	rc = smb_msgbuf_encode(&srh.srh_mbuf, "bbbww", 2, 0xff, 0, 0, 0);
530 	if (rc < 0) {
531 		logon->state = SDB_LSTATE_SETUP;
532 		smbrdr_handle_free(&srh);
533 		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: encode failed",
534 		    logon->username);
535 		return (rc);
536 	}
537 
538 	status = smbrdr_exchange(&srh, &smb_hdr, 0);
539 	if (status != NT_STATUS_SUCCESS) {
540 		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: %s", logon->username,
541 		    xlate_nt_status(status));
542 		rc = -1;
543 	} else {
544 		rc = 0;
545 	}
546 
547 	bzero(logon, sizeof (struct sdb_logon));
548 	smbrdr_handle_free(&srh);
549 	return (rc);
550 }
551 
552 
553 /*
554  * smbrdr_logon_init
555  *
556  * Find a slot for account logon information. The account information
557  * is associated with a session so we need a valid session slot before
558  * calling this function. If we already have a record of the specified
559  * account, a pointer to that record is returned. Otherwise we attempt
560  * to allocate a new one.
561  */
562 static struct sdb_logon *
563 smbrdr_logon_init(struct sdb_session *session, char *username,
564     unsigned char *pwd)
565 {
566 	struct sdb_logon *logon;
567 	int64_t smbrdr_lmcompl;
568 	int rc;
569 
570 	logon = (struct sdb_logon *)malloc(sizeof (sdb_logon_t));
571 	if (logon == 0)
572 		return (0);
573 
574 	bzero(logon, sizeof (struct sdb_logon));
575 	logon->session = session;
576 
577 	(void) smb_config_getnum(SMB_CI_LM_LEVEL, &smbrdr_lmcompl);
578 
579 	if (strcmp(username, "IPC$") == 0) {
580 		logon->type = SDB_LOGON_ANONYMOUS;
581 		logon->auth.ci_len = 1;
582 		*(logon->auth.ci) = 0;
583 		logon->auth.cs_len = 0;
584 	} else {
585 		logon->type = SDB_LOGON_USER;
586 		rc = smb_auth_set_info(username, 0, pwd,
587 		    session->di.domain, session->challenge_key,
588 		    session->challenge_len, smbrdr_lmcompl, &logon->auth);
589 
590 		if (rc != 0) {
591 			free(logon);
592 			return (0);
593 		}
594 	}
595 
596 	(void) strlcpy(logon->username, username, MAX_ACCOUNT_NAME);
597 	logon->state = SDB_LSTATE_INIT;
598 	return (logon);
599 }
600 
601 /*
602  * smbrdr_logon_validate
603  *
604  * if session is there and it's alive and also the required
605  * user is already logged in don't need to do anything
606  * otherwise clear the session structure.
607  */
608 static boolean_t
609 smbrdr_logon_validate(char *server, char *username)
610 {
611 	struct sdb_session *session;
612 	boolean_t valid = B_FALSE;
613 
614 	session = smbrdr_session_lock(server, username, SDB_SLCK_WRITE);
615 	if (session) {
616 		if (nb_keep_alive(session->sock) == 0) {
617 			valid = B_TRUE;
618 		} else {
619 			session->state = SDB_SSTATE_STALE;
620 			syslog(LOG_DEBUG,
621 			    "smbrdr_logon_validate: stale session");
622 		}
623 
624 		smbrdr_session_unlock(session);
625 	}
626 
627 	return (valid);
628 }
629