xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c (revision dc20a3024900c47dd2ee44b9707e6df38f7d62a5)
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 #include <stdlib.h>
29 #include <unistd.h>
30 #include <limits.h>
31 #include <strings.h>
32 #include <synch.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <thread.h>
38 #include <pwd.h>
39 #include <smbsrv/libsmb.h>
40 
41 #define	SMB_PASSWD	"/var/smb/smbpasswd"
42 #define	SMB_OPASSWD	"/var/smb/osmbpasswd"
43 #define	SMB_PASSTEMP	"/var/smb/ptmp"
44 #define	SMB_PASSLCK	"/var/smb/.pwd.lock"
45 
46 #define	SMB_PWD_DISABLE	"*DIS*"
47 #define	SMB_PWD_BUFSIZE 256
48 
49 #define	S_WAITTIME	15
50 
51 typedef enum {
52 	SMB_PWD_NAME = 0,
53 	SMB_PWD_UID,
54 	SMB_PWD_LMHASH,
55 	SMB_PWD_NTHASH,
56 	SMB_PWD_NARG
57 } smb_pwdarg_t;
58 
59 static struct flock flock =	{
60 			0,	/* l_type */
61 			0,	/* l_whence */
62 			0,	/* l_start */
63 			0,	/* l_len */
64 			0,	/* l_sysid */
65 			0	/* l_pid */
66 			};
67 
68 static pid_t lck_pid = 0;	/* process's pid at last lock */
69 static thread_t lck_tid = 0;	/* thread that holds the lock */
70 static int fildes = -1;
71 static mutex_t lck_lock = DEFAULTMUTEX;
72 
73 typedef struct smb_pwbuf {
74 	char *pw_name;
75 	smb_passwd_t *pw_pwd;
76 } smb_pwbuf_t;
77 
78 static int smb_pwd_lock(void);
79 static int smb_pwd_unlock(void);
80 static int smb_pwd_flck(void);
81 static int smb_pwd_fulck(void);
82 
83 static smb_pwbuf_t *smb_pwd_fgetent(FILE *, smb_pwbuf_t *, char *, size_t);
84 static int smb_pwd_fputent(FILE *, smb_pwbuf_t *);
85 static int smb_pwd_chgpwent(smb_passwd_t *, const char *, int);
86 static int smb_pwd_update(const char *, const char *, int);
87 
88 /*
89  * smb_pwd_get
90  *
91  * Returns a smb password structure for the given user name.
92  * smbpw is a pointer to a buffer allocated by the caller.
93  *
94  * Returns NULL upon failure.
95  */
96 smb_passwd_t *
97 smb_pwd_getpasswd(const char *name, smb_passwd_t *smbpw)
98 {
99 	char buf[SMB_PWD_BUFSIZE];
100 	boolean_t found = B_FALSE;
101 	smb_pwbuf_t pwbuf;
102 	int err;
103 	FILE *fp;
104 
105 	err = smb_pwd_lock();
106 	if (err != SMB_PWE_SUCCESS)
107 		return (NULL);
108 
109 	if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) {
110 		(void) smb_pwd_unlock();
111 		return (NULL);
112 	}
113 
114 	pwbuf.pw_name = NULL;
115 	pwbuf.pw_pwd = smbpw;
116 
117 	while (smb_pwd_fgetent(fp, &pwbuf, buf, sizeof (buf)) != NULL) {
118 		if (strcmp(name, pwbuf.pw_name) == 0) {
119 			if ((smbpw->pw_flags & (SMB_PWF_LM | SMB_PWF_NT)))
120 				found = B_TRUE;
121 			break;
122 		}
123 	}
124 
125 	(void) fclose(fp);
126 	(void) smb_pwd_unlock();
127 
128 	if (!found) {
129 		bzero(smbpw, sizeof (smb_passwd_t));
130 		return (NULL);
131 	}
132 
133 	return (smbpw);
134 }
135 
136 /*
137  * smb_pwd_set
138  *
139  * Update/add the given user to the smbpasswd file.
140  */
141 int
142 smb_pwd_setpasswd(const char *name, const char *password)
143 {
144 	return (smb_pwd_update(name, password, 0));
145 }
146 
147 /*
148  * smb_pwd_setcntl
149  *
150  * Change the account state. This can be making the account
151  * disable/enable or removing its LM hash.
152  */
153 int
154 smb_pwd_setcntl(const char *name, int control)
155 {
156 	if (control == 0)
157 		return (SMB_PWE_SUCCESS);
158 
159 	return (smb_pwd_update(name, NULL, control));
160 }
161 
162 static int
163 smb_pwd_update(const char *name, const char *password, int control)
164 {
165 	struct stat64 stbuf;
166 	FILE *src, *dst;
167 	int tempfd;
168 	char buf[SMB_PWD_BUFSIZE];
169 	int err = SMB_PWE_SUCCESS;
170 	smb_pwbuf_t pwbuf;
171 	smb_passwd_t smbpw;
172 	boolean_t newent = B_TRUE;
173 	boolean_t user_disable = B_FALSE;
174 	char uxbuf[1024];
175 	struct passwd uxpw;
176 	int64_t lm_level;
177 
178 	err = smb_pwd_lock();
179 	if (err != SMB_PWE_SUCCESS)
180 		return (err);
181 
182 	if (stat64(SMB_PASSWD, &stbuf) < 0) {
183 		err = SMB_PWE_STAT_FAILED;
184 		goto passwd_exit;
185 	}
186 
187 	if ((tempfd = open(SMB_PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
188 		err = SMB_PWE_OPEN_FAILED;
189 		goto passwd_exit;
190 	}
191 
192 	if ((dst = fdopen(tempfd, "wF")) == NULL) {
193 		err = SMB_PWE_OPEN_FAILED;
194 		goto passwd_exit;
195 	}
196 
197 	if ((src = fopen(SMB_PASSWD, "rF")) == NULL) {
198 		err = SMB_PWE_OPEN_FAILED;
199 		(void) fclose(dst);
200 		(void) unlink(SMB_PASSTEMP);
201 		goto passwd_exit;
202 	}
203 
204 	if (smb_config_getnum(SMB_CI_LM_LEVEL, &lm_level) != SMBD_SMF_OK)
205 		lm_level = 4;
206 
207 	if (lm_level >= 4)
208 		control |= SMB_PWC_NOLM;
209 
210 	/*
211 	 * copy old password entries to temporary file while replacing
212 	 * the entry that matches "name"
213 	 */
214 	pwbuf.pw_name = NULL;
215 	pwbuf.pw_pwd = &smbpw;
216 
217 	while (smb_pwd_fgetent(src, &pwbuf, buf, sizeof (buf)) != NULL) {
218 		if (strcmp(pwbuf.pw_name, name) == 0) {
219 			err = smb_pwd_chgpwent(&smbpw, password, control);
220 			if (err == SMB_PWE_USER_DISABLE)
221 				user_disable = B_TRUE;
222 			err = smb_pwd_fputent(dst, &pwbuf);
223 			newent = B_FALSE;
224 		} else {
225 			err = smb_pwd_fputent(dst, &pwbuf);
226 		}
227 
228 		if (err != SMB_PWE_SUCCESS) {
229 			(void) fclose(src);
230 			(void) fclose(dst);
231 			goto passwd_exit;
232 		}
233 	}
234 
235 	if (newent) {
236 		if (getpwnam_r(name, &uxpw, uxbuf, sizeof (uxbuf))) {
237 			pwbuf.pw_name = uxpw.pw_name;
238 			smbpw.pw_flags = 0;
239 			smbpw.pw_uid = uxpw.pw_uid;
240 			(void) smb_pwd_chgpwent(&smbpw, password, control);
241 			err = smb_pwd_fputent(dst, &pwbuf);
242 		} else {
243 			err = SMB_PWE_USER_UNKNOWN;
244 		}
245 
246 		if (err != SMB_PWE_SUCCESS) {
247 			(void) fclose(src);
248 			(void) fclose(dst);
249 			goto passwd_exit;
250 		}
251 	}
252 
253 	(void) fclose(src);
254 	if (fclose(dst) != 0) {
255 		err = SMB_PWE_CLOSE_FAILED;
256 		goto passwd_exit; /* Don't trust the temporary file */
257 	}
258 
259 	/* Rename temp to passwd */
260 	if (unlink(SMB_OPASSWD) && access(SMB_OPASSWD, 0) == 0) {
261 		err = SMB_PWE_UPDATE_FAILED;
262 		(void) unlink(SMB_PASSTEMP);
263 		goto passwd_exit;
264 	}
265 
266 	if (link(SMB_PASSWD, SMB_OPASSWD) == -1) {
267 		err = SMB_PWE_UPDATE_FAILED;
268 		(void) unlink(SMB_PASSTEMP);
269 		goto passwd_exit;
270 	}
271 
272 	if (rename(SMB_PASSTEMP, SMB_PASSWD) == -1) {
273 		err = SMB_PWE_UPDATE_FAILED;
274 		(void) unlink(SMB_PASSTEMP);
275 		goto passwd_exit;
276 	}
277 
278 	(void) chmod(SMB_PASSWD, 0400);
279 
280 passwd_exit:
281 	(void) smb_pwd_unlock();
282 	if ((err == SMB_PWE_SUCCESS) && user_disable)
283 		err = SMB_PWE_USER_DISABLE;
284 
285 	return (err);
286 }
287 
288 /*
289  * smb_getpwent
290  *
291  * Parse the buffer in the passed pwbuf and fill in the
292  * smb password structure to point to the parsed information.
293  * The entry format is:
294  *
295  *	<user-name>:<user-id>:<LM hash>:<NTLM hash>
296  *
297  * Returns a pointer to the password structure on success,
298  * otherwise returns NULL.
299  */
300 static smb_pwbuf_t *
301 smb_pwd_fgetent(FILE *fp, smb_pwbuf_t *pwbuf, char *buf, size_t bufsize)
302 {
303 	char *argv[SMB_PWD_NARG];
304 	smb_passwd_t *pw;
305 	smb_pwdarg_t i;
306 	int lm_len, nt_len;
307 
308 	if (fgets(buf, bufsize, fp) == NULL)
309 		return (NULL);
310 	(void) trim_whitespace(buf);
311 
312 	for (i = 0; i < SMB_PWD_NARG; ++i) {
313 		if ((argv[i] = strsep((char **)&buf, ":")) == 0) {
314 			return (NULL);
315 		}
316 	}
317 
318 	if ((*argv[SMB_PWD_NAME] == '\0') || (*argv[SMB_PWD_UID] == '\0'))
319 		return (NULL);
320 
321 	pwbuf->pw_name = argv[SMB_PWD_NAME];
322 	pw = pwbuf->pw_pwd;
323 	bzero(pw, sizeof (smb_passwd_t));
324 	pw->pw_uid = strtoul(argv[SMB_PWD_UID], 0, 10);
325 
326 	if (strcmp(argv[SMB_PWD_LMHASH], SMB_PWD_DISABLE) == 0) {
327 		pw->pw_flags |= SMB_PWF_DISABLE;
328 		(void) strcpy((char *)pw->pw_lmhash, SMB_PWD_DISABLE);
329 		(void) strcpy((char *)pw->pw_nthash, SMB_PWD_DISABLE);
330 		return (pwbuf);
331 	}
332 
333 	lm_len = strlen(argv[SMB_PWD_LMHASH]);
334 	if (lm_len == SMBAUTH_HEXHASH_SZ) {
335 		(void) hextobin(argv[SMB_PWD_LMHASH], SMBAUTH_HEXHASH_SZ,
336 		    (char *)pw->pw_lmhash, SMBAUTH_HASH_SZ);
337 
338 		pw->pw_flags |= SMB_PWF_LM;
339 	} else if (lm_len != 0) {
340 		return (NULL);
341 	}
342 
343 	nt_len = strlen(argv[SMB_PWD_NTHASH]);
344 	if (nt_len == SMBAUTH_HEXHASH_SZ) {
345 		(void) hextobin(argv[SMB_PWD_NTHASH], SMBAUTH_HEXHASH_SZ,
346 		    (char *)pw->pw_nthash, SMBAUTH_HASH_SZ);
347 
348 		pw->pw_flags |= SMB_PWF_NT;
349 	} else if (nt_len != 0) {
350 		return (NULL);
351 	}
352 
353 	return (pwbuf);
354 }
355 
356 static int
357 smb_pwd_chgpwent(smb_passwd_t *smbpw, const char *password, int control)
358 {
359 	if (control & SMB_PWC_DISABLE) {
360 		smbpw->pw_flags |= SMB_PWF_DISABLE;
361 		(void) strcpy((char *)smbpw->pw_lmhash, SMB_PWD_DISABLE);
362 		(void) strcpy((char *)smbpw->pw_nthash, SMB_PWD_DISABLE);
363 		smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT);
364 		return (SMB_PWE_SUCCESS);
365 	} else if ((control & SMB_PWC_ENABLE) &&
366 	    (smbpw->pw_flags & SMB_PWF_DISABLE)) {
367 		*smbpw->pw_lmhash = '\0';
368 		*smbpw->pw_nthash = '\0';
369 		smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT);
370 		return (SMB_PWE_SUCCESS);
371 	}
372 
373 	/* No password update if account is disabled */
374 	if (smbpw->pw_flags & SMB_PWF_DISABLE)
375 		return (SMB_PWE_USER_DISABLE);
376 
377 	if (control & SMB_PWC_NOLM) {
378 		smbpw->pw_flags &= ~SMB_PWF_LM;
379 		*smbpw->pw_lmhash = '\0';
380 	} else {
381 		smbpw->pw_flags |= SMB_PWF_LM;
382 		(void) smb_auth_lm_hash((char *)password, smbpw->pw_lmhash);
383 	}
384 
385 	smbpw->pw_flags |= SMB_PWF_NT;
386 	(void) smb_auth_ntlm_hash((char *)password, smbpw->pw_nthash);
387 	return (SMB_PWE_SUCCESS);
388 }
389 
390 /*
391  * smb_putpwent
392  *
393  * Creates LM and NTLM hash from the given plain text password
394  * and write them along with user's name and Id to the smbpasswd
395  * file.
396  */
397 static int
398 smb_pwd_fputent(FILE *fp, smb_pwbuf_t *pwbuf)
399 {
400 	smb_passwd_t *pw = pwbuf->pw_pwd;
401 	char hex_nthash[SMBAUTH_HEXHASH_SZ+1];
402 	char hex_lmhash[SMBAUTH_HEXHASH_SZ+1];
403 	int rc;
404 
405 	if ((pw->pw_flags & SMB_PWF_LM) == SMB_PWF_LM) {
406 		(void) bintohex((char *)pw->pw_lmhash, SMBAUTH_HASH_SZ,
407 		    hex_lmhash, SMBAUTH_HEXHASH_SZ);
408 		hex_lmhash[SMBAUTH_HEXHASH_SZ] = '\0';
409 	} else {
410 		(void) strcpy(hex_lmhash, (char *)pw->pw_lmhash);
411 	}
412 
413 	if ((pw->pw_flags & SMB_PWF_NT) == SMB_PWF_NT) {
414 		(void) bintohex((char *)pw->pw_nthash, SMBAUTH_HASH_SZ,
415 		    hex_nthash, SMBAUTH_HEXHASH_SZ);
416 		hex_nthash[SMBAUTH_HEXHASH_SZ] = '\0';
417 	} else {
418 		(void) strcpy(hex_nthash, (char *)pw->pw_nthash);
419 	}
420 
421 	rc = fprintf(fp, "%s:%d:%s:%s\n", pwbuf->pw_name, pw->pw_uid,
422 	    hex_lmhash, hex_nthash);
423 
424 	if (rc <= 0)
425 		return (SMB_PWE_WRITE_FAILED);
426 
427 	return (SMB_PWE_SUCCESS);
428 }
429 
430 static int
431 smb_pwd_lock(void)
432 {
433 	int res;
434 
435 	if (smb_pwd_flck()) {
436 		switch (errno) {
437 		case EINTR:
438 			res = SMB_PWE_BUSY;
439 			break;
440 		case EACCES:
441 			res = SMB_PWE_DENIED;
442 			break;
443 		case 0:
444 			res = SMB_PWE_SUCCESS;
445 			break;
446 		}
447 	} else
448 		res = SMB_PWE_SUCCESS;
449 
450 	return (res);
451 }
452 
453 static int
454 smb_pwd_unlock(void)
455 {
456 	if (smb_pwd_fulck())
457 		return (SMB_PWE_SYSTEM_ERROR);
458 
459 	return (SMB_PWE_SUCCESS);
460 }
461 
462 static int
463 smb_pwd_flck(void)
464 {
465 	int seconds = 0;
466 
467 	(void) mutex_lock(&lck_lock);
468 	for (;;) {
469 		if (lck_pid != 0 && lck_pid != getpid()) {
470 			/* somebody forked */
471 			lck_pid = 0;
472 			lck_tid = 0;
473 		}
474 
475 		if (lck_tid == 0) {
476 			if ((fildes = creat(SMB_PASSLCK, 0600)) == -1)
477 				break;
478 			flock.l_type = F_WRLCK;
479 			if (fcntl(fildes, F_SETLK, &flock) != -1) {
480 				lck_pid = getpid();
481 				lck_tid = thr_self();
482 				(void) mutex_unlock(&lck_lock);
483 				return (0);
484 			}
485 			(void) close(fildes);
486 			fildes = -1;
487 		}
488 
489 		if (seconds++ >= S_WAITTIME) {
490 			/*
491 			 * For compatibility with the past, pretend
492 			 * that we were interrupted by SIGALRM.
493 			 */
494 			errno = EINTR;
495 			break;
496 		}
497 
498 		(void) mutex_unlock(&lck_lock);
499 		(void) sleep(1);
500 		(void) mutex_lock(&lck_lock);
501 	}
502 	(void) mutex_unlock(&lck_lock);
503 
504 	return (-1);
505 }
506 
507 static int
508 smb_pwd_fulck(void)
509 {
510 	(void) mutex_lock(&lck_lock);
511 	if (lck_tid == thr_self() && fildes >= 0) {
512 		flock.l_type = F_UNLCK;
513 		(void) fcntl(fildes, F_SETLK, &flock);
514 		(void) close(fildes);
515 		fildes = -1;
516 		lck_pid = 0;
517 		lck_tid = 0;
518 		(void) mutex_unlock(&lck_lock);
519 		return (0);
520 	}
521 	(void) mutex_unlock(&lck_lock);
522 	return (-1);
523 }
524