smbrdr_logon.c revision 12508:edb7861a1533
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/*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26#include <pthread.h>
27#include <string.h>
28#include <strings.h>
29#include <synch.h>
30#include <errno.h>
31#include <sys/types.h>
32#include <sys/socket.h>
33#include <netinet/in.h>
34#include <arpa/inet.h>
35#include <smbsrv/wintypes.h>
36#include <smbsrv/libsmbrdr.h>
37#include <smbsrv/smb.h>
38#include <smbrdr.h>
39
40#define	SMBRDR_ANON_USER	"IPC$"
41
42static int smbrdr_session_setupx(struct sdb_logon *);
43static struct sdb_logon *smbrdr_logon_init(struct sdb_session *, char *,
44    uint8_t *);
45static int smbrdr_authenticate(char *, char *, uint8_t *);
46
47/*
48 * If the username is SMBRDR_ANON_USER, an anonymous session will be
49 * established. Otherwise, an authenticated session will be established
50 * based on the specified credentials.
51 */
52int
53smbrdr_logon(char *domain_controller, char *domain, char *username)
54{
55	uint8_t pwd_hash[SMBAUTH_HASH_SZ];
56	int rc;
57
58	if (username == NULL || *username == 0) {
59		smb_log(smbrdr_log_hdl, LOG_DEBUG, "smbrdr_logon: no username");
60		return (-1);
61	}
62
63	bzero(pwd_hash, SMBAUTH_HASH_SZ);
64	if (smb_strcasecmp(username, SMBRDR_ANON_USER, 0) != 0) {
65		smb_ipc_get_passwd(pwd_hash, SMBAUTH_HASH_SZ);
66		if (*pwd_hash == 0) {
67			smb_log(smbrdr_log_hdl, LOG_DEBUG,
68			    "smbrdr_logon: no password");
69			return (-1);
70		}
71	}
72
73	if (smbrdr_negotiate(domain_controller, domain) != 0) {
74		smb_log(smbrdr_log_hdl, LOG_DEBUG,
75		    "smbrdr_logon: negotiate failed");
76		return (-1);
77	}
78
79	rc = smbrdr_authenticate(domain_controller, username, pwd_hash);
80	return ((rc == AUTH_USER_GRANT) ? 0 : -1);
81
82}
83
84/*
85 * Get the user session key from an already open named pipe.
86 * The RPC library needs this.  See ndr_rpc_get_ssnkey()
87 *
88 * Returns zero (success) or an errno.
89 */
90int
91smbrdr_get_ssnkey(int fid, unsigned char *ssn_key, size_t key_len)
92{
93	struct sdb_logon *logon;
94	struct sdb_session *session;
95	struct sdb_netuse *netuse;
96	struct sdb_ofile *ofile;
97
98	if (ssn_key == NULL || key_len < SMBAUTH_SESSION_KEY_SZ)
99		return (EINVAL);
100
101	ofile = smbrdr_ofile_get(fid);
102	if (ofile == NULL)
103		return (EBADF);
104
105	netuse = ofile->netuse;
106	session = netuse->session;
107	logon = &session->logon;
108
109	if (key_len > SMBAUTH_SESSION_KEY_SZ)
110		bzero(ssn_key, key_len);
111	bcopy(logon->ssn_key, ssn_key,
112	    SMBAUTH_SESSION_KEY_SZ);
113
114	smbrdr_ofile_put(ofile);
115	return (0);
116}
117
118/*
119 * smbrdr_authenticate
120 *
121 * This is the entry point for logging  a user onto the domain. The
122 * session structure should have been obtained via a successful call
123 * to smbrdr_smb_connect. We allocate a logon structure to hold the
124 * user details and attempt to logon using smbrdr_session_setupx.
125 * Note that we expect the password fields to have been encrypted
126 * before this call.
127 *
128 * On success, the logon structure will be returned. Otherwise a null
129 * pointer will be returned.
130 */
131static int
132smbrdr_authenticate(char *server, char *username, uint8_t *pwd)
133{
134	struct sdb_session *session;
135	struct sdb_logon *logon;
136	struct sdb_logon old_logon;
137	int ret;
138
139	session = smbrdr_session_lock(server, SDB_SLCK_WRITE);
140	if (session == NULL) {
141		smb_log(smbrdr_log_hdl, LOG_DEBUG,
142		    "smbrdr_authenticate: %s: no session with %s",
143		    username, server);
144		return (-1);
145	}
146
147	bzero(&old_logon, sizeof (struct sdb_logon));
148
149	logon = &session->logon;
150	if (logon->type != SDB_LOGON_NONE) {
151		if (strcasecmp(logon->username, username) == 0) {
152			/* The requested user has already been logged in */
153			smbrdr_session_unlock(session);
154			return ((logon->type == SDB_LOGON_GUEST)
155			    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT);
156		}
157
158		old_logon = *logon;
159	}
160
161	logon = smbrdr_logon_init(session, username, pwd);
162
163	if (logon == NULL) {
164		smb_log(smbrdr_log_hdl, LOG_DEBUG,
165		    "smbrdr_authenticate: %s: %s", username, strerror(errno));
166		smbrdr_session_unlock(session);
167		return (-1);
168	}
169
170	if (smbrdr_session_setupx(logon) < 0) {
171		free(logon);
172		smbrdr_session_unlock(session);
173		return (-1);
174	}
175
176
177	ret = (logon->type == SDB_LOGON_GUEST)
178	    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT;
179
180	session->logon = *logon;
181	free(logon);
182
183	if (old_logon.type != SDB_LOGON_NONE)
184		(void) smbrdr_logoffx(&old_logon);
185
186	smbrdr_session_unlock(session);
187	return (ret);
188}
189
190
191/*
192 * smbrdr_session_setupx
193 *
194 * Build and send an SMB session setup command. This is used to log a
195 * user onto the domain. See CIFS section 4.1.2.
196 *
197 * Returns 0 on success. Otherwise returns a -ve error code.
198 */
199static int
200smbrdr_session_setupx(struct sdb_logon *logon)
201{
202	struct sdb_session *session;
203	smb_hdr_t smb_hdr;
204	smbrdr_handle_t srh;
205	smb_msgbuf_t *mb;
206	char *native_os;
207	char *native_lanman;
208	unsigned short data_bytes;
209	unsigned short guest;
210	unsigned long capabilities;
211	unsigned short null_size;
212	size_t (*strlen_fn)(const char *s);
213	DWORD status;
214	int rc;
215	int64_t lmlevel;
216
217	/*
218	 * Paranoia check - we should never get this
219	 * far without a valid session structure.
220	 */
221	if ((session = logon->session) == NULL)
222		return (-1);
223
224	if (session->remote_caps & CAP_UNICODE) {
225		strlen_fn = smb_wcequiv_strlen;
226		null_size = sizeof (smb_wchar_t);
227		session->smb_flags2 |= SMB_FLAGS2_UNICODE;
228	} else {
229		strlen_fn = strlen;
230		null_size = sizeof (char);
231	}
232
233	if (smbrdr_sign_init(session, logon) < 0) {
234		smb_log(smbrdr_log_hdl, LOG_DEBUG,
235		    "smbrdr_session_setupx: smbrdr_sign_init failed");
236		return (-1);
237	}
238
239	status = smbrdr_request_init(&srh, SMB_COM_SESSION_SETUP_ANDX,
240	    session, 0, 0);
241
242	if (status != NT_STATUS_SUCCESS) {
243		smbrdr_sign_fini(session);
244		smb_log(smbrdr_log_hdl, LOG_DEBUG, "smbrdr_session_setupx: %s",
245		    xlate_nt_status(status));
246		return (-1);
247	}
248	mb = &srh.srh_mbuf;
249
250	/*
251	 * Regardless of the server's capabilities or what's
252	 * reported in smb_flags2, we should report our full
253	 * capabilities.
254	 */
255	capabilities = CAP_UNICODE | CAP_NT_SMBS | CAP_STATUS32;
256
257	/*
258	 * Compute the BCC for unicode or ASCII strings.
259	 */
260	data_bytes  = logon->auth.ci_len + logon->auth.cs_len + null_size;
261	data_bytes += strlen_fn(session->native_os) + null_size;
262	data_bytes += strlen_fn(session->native_lanman) + null_size;
263
264	if (logon->type == SDB_LOGON_ANONYMOUS) {
265		/*
266		 * Anonymous logon: no username or domain name.
267		 * We still need to include two null characters.
268		 */
269		data_bytes += (2 * null_size);
270
271		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllwlu.u.",
272		    13,				/* smb_wct */
273		    0xff,			/* AndXCommand (none) */
274		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
275		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
276		    1,				/* MaxMpxCount */
277		    0,				/* VcNumber */
278		    0,				/* SessionKey */
279		    1,				/* CaseInsensitivePassLength */
280		    0,				/* CaseSensitivePassLength */
281		    0,				/* Reserved */
282		    capabilities,		/* Capabilities */
283		    data_bytes,			/* smb_bcc */
284		    0,				/* No user or domain */
285		    session->native_os,		/* NativeOS */
286		    session->native_lanman);	/* NativeLanMan */
287	} else {
288		data_bytes += strlen_fn(logon->username) + null_size;
289		data_bytes += strlen_fn(session->domain) + null_size;
290
291		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllw#c#cuuu.u.",
292		    13,				/* smb_wct */
293		    0xff,			/* AndXCommand (none) */
294		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
295		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
296		    1,				/* MaxMpxCount */
297		    session->vc,		/* VcNumber */
298		    session->sesskey,		/* SessionKey */
299		    logon->auth.ci_len,		/* CaseInsensitivePassLength */
300		    logon->auth.cs_len,		/* CaseSensitivePassLength */
301		    0,				/* Reserved */
302		    capabilities,		/* Capabilities */
303		    data_bytes,			/* smb_bcc */
304		    logon->auth.ci_len,		/* ci length spec */
305		    logon->auth.ci,		/* CaseInsensitivePassword */
306		    logon->auth.cs_len,		/* cs length spec */
307		    logon->auth.cs,		/* CaseSensitivePassword */
308		    logon->username,		/* AccountName */
309		    session->domain,		/* PrimaryDomain */
310		    session->native_os,		/* NativeOS */
311		    session->native_lanman);	/* NativeLanMan */
312	}
313
314	if (rc <= 0) {
315		smb_log(smbrdr_log_hdl, LOG_DEBUG,
316		    "smbrdr_session_setupx: encode failed");
317		smbrdr_handle_free(&srh);
318		smbrdr_sign_fini(session);
319		return (-1);
320	}
321
322	status = smbrdr_exchange(&srh, &smb_hdr, 0);
323
324	if (status != NT_STATUS_SUCCESS) {
325		smb_log(smbrdr_log_hdl, LOG_DEBUG, "smbrdr_session_setupx: %s",
326		    xlate_nt_status(status));
327
328		if (status == NT_STATUS_INVALID_PARAMETER) {
329			rc = smb_config_getnum(SMB_CI_LM_LEVEL, &lmlevel);
330			if (rc != SMBD_SMF_OK || lmlevel > 2)
331				smb_log(smbrdr_log_hdl, LOG_DEBUG,
332				    "If the DC is running Windows Server 2008: "
333				    "apply hotfix KB 957441");
334				smb_log(smbrdr_log_hdl, LOG_DEBUG,
335				    "If the DC is running Windows Server 2008 "
336				    "R2: do not apply the hotfix but update "
337				    "the registry as described in KB 957441");
338		}
339
340		smbrdr_handle_free(&srh);
341		smbrdr_sign_fini(session);
342		return (-1);
343	}
344
345	rc = smb_msgbuf_decode(mb, "5.w2.u", &guest, &native_os);
346
347	/*
348	 * There was a problem in decoding response from
349	 * a Samba 2.x PDC. This server sends strings in ASCII
350	 * format and there is one byte with value 0 between
351	 * native_os and native_lm:
352	 *
353	 *				 FF 53 4D 42 73 00		.SMBs.
354	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
355	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 01 ......d.........
356	 * 00 1C 00 55 6E 69 78 00 53 61 6D 62 61 20 32 2E ...Unix.Samba.2.
357	 * 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00    2.8a.SAMBA_DOM.
358	 *
359	 * The byte doesn't seem to be padding because when change in
360	 * native OS from Unix to Unix1 the 0 byte is still there:
361	 *
362	 *				 FF 53 4D 42 73 00		.SMBs.
363	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
364	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 00 ......d.........
365	 * 00 1D 00 55 6E 69 78 31 00 53 61 6D 62 61 20 32 ...Unix1.Samba.2
366	 * 2E 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00 .2.8a.SAMBA_DOM.
367	 */
368	if (rc > 0) {
369		if (session->remote_caps & CAP_UNICODE)
370			rc = smb_msgbuf_decode(mb, "u", &native_lanman);
371		else
372			rc = smb_msgbuf_decode(mb, ".u", &native_lanman);
373	}
374
375	if (rc <= 0) {
376		smb_log(smbrdr_log_hdl, LOG_DEBUG,
377		    "smbrdr_session_setupx: decode failed");
378		smbrdr_handle_free(&srh);
379		smbrdr_sign_fini(session);
380		return (-1);
381	}
382
383	session->remote_os = smbnative_os_value(native_os);
384	session->remote_lm = smbnative_lm_value(native_lanman);
385	session->pdc_type  = smbnative_pdc_value(native_lanman);
386
387	logon->uid = smb_hdr.uid;
388	if (guest)
389		logon->type = SDB_LOGON_GUEST;
390
391	smbrdr_handle_free(&srh);
392	smbrdr_sign_unset_key(session);
393
394	logon->state = SDB_LSTATE_SETUP;
395
396	return (0);
397}
398
399/*
400 * smbrdr_logoffx
401 *
402 * Build and send an SMB session logoff (SMB_COM_LOGOFF_ANDX) command.
403 * This is the inverse of an SMB_COM_SESSION_SETUP_ANDX. See CIFS
404 * section 4.1.3. The logon structure should have been obtained from a
405 * successful call to smbrdr_authenticate.
406 *
407 * Returns 0 on success. Otherwise returns a -ve error code.
408 */
409int
410smbrdr_logoffx(struct sdb_logon *logon)
411{
412	struct sdb_session *session;
413	smbrdr_handle_t srh;
414	smb_hdr_t smb_hdr;
415	DWORD status;
416	int rc;
417
418	if (logon->state != SDB_LSTATE_SETUP) {
419		/* No user to logoff */
420		bzero(logon, sizeof (struct sdb_logon));
421		return (0);
422	}
423
424	if ((session = logon->session) == 0) {
425		bzero(logon, sizeof (struct sdb_logon));
426		return (0);
427	}
428
429	logon->state = SDB_LSTATE_LOGGING_OFF;
430	smbrdr_netuse_logoff(logon->uid);
431
432	if ((session->state != SDB_SSTATE_NEGOTIATED) &&
433	    (session->state != SDB_SSTATE_DISCONNECTING)) {
434		bzero(logon, sizeof (struct sdb_logon));
435		return (0);
436	}
437
438	status = smbrdr_request_init(&srh, SMB_COM_LOGOFF_ANDX,
439	    session, logon, 0);
440
441	if (status != NT_STATUS_SUCCESS) {
442		logon->state = SDB_LSTATE_SETUP;
443		smb_log(smbrdr_log_hdl, LOG_DEBUG, "smbrdr_logoffx: %s: %s",
444		    logon->username, xlate_nt_status(status));
445		return (-1);
446	}
447
448	rc = smb_msgbuf_encode(&srh.srh_mbuf, "bbbww", 2, 0xff, 0, 0, 0);
449	if (rc < 0) {
450		logon->state = SDB_LSTATE_SETUP;
451		smbrdr_handle_free(&srh);
452		smb_log(smbrdr_log_hdl, LOG_DEBUG,
453		    "smbrdr_logoffx: %s: encode failed", logon->username);
454		return (rc);
455	}
456
457	status = smbrdr_exchange(&srh, &smb_hdr, 0);
458	if (status != NT_STATUS_SUCCESS) {
459		smb_log(smbrdr_log_hdl, LOG_DEBUG, "smbrdr_logoffx: %s: %s",
460		    logon->username, xlate_nt_status(status));
461		rc = -1;
462	} else {
463		rc = 0;
464	}
465
466	bzero(logon, sizeof (struct sdb_logon));
467	smbrdr_handle_free(&srh);
468	return (rc);
469}
470
471
472/*
473 * smbrdr_logon_init
474 *
475 * Find a slot for account logon information. The account information
476 * is associated with a session so we need a valid session slot before
477 * calling this function. If we already have a record of the specified
478 * account, a pointer to that record is returned. Otherwise we attempt
479 * to allocate a new one.
480 */
481static struct sdb_logon *
482smbrdr_logon_init(struct sdb_session *session, char *username, uint8_t *pwd)
483{
484	struct sdb_logon *logon;
485	int64_t smbrdr_lmcompl;
486	int rc;
487
488	logon = (struct sdb_logon *)malloc(sizeof (sdb_logon_t));
489	if (logon == 0)
490		return (0);
491
492	bzero(logon, sizeof (struct sdb_logon));
493	logon->session = session;
494
495	(void) smb_config_getnum(SMB_CI_LM_LEVEL, &smbrdr_lmcompl);
496
497	if (strcmp(username, "IPC$") == 0) {
498		logon->type = SDB_LOGON_ANONYMOUS;
499		logon->auth.ci_len = 1;
500		*(logon->auth.ci) = 0;
501		logon->auth.cs_len = 0;
502	} else {
503		logon->type = SDB_LOGON_USER;
504		rc = smb_auth_set_info(username, 0, pwd,
505		    session->domain, session->challenge_key,
506		    session->challenge_len, smbrdr_lmcompl, &logon->auth);
507
508		/* Generate (and save) the session key. */
509		if (rc == 0) {
510			rc = smb_auth_gen_session_key(&logon->auth,
511			    logon->ssn_key);
512		}
513
514		if (rc != 0) {
515			free(logon);
516			return (0);
517		}
518	}
519
520	(void) strlcpy(logon->username, username, MAX_ACCOUNT_NAME);
521	logon->state = SDB_LSTATE_INIT;
522	return (logon);
523}
524