xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c (revision da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0)
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 2007 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 	int lm_level;
177 	char *lm_str;
178 
179 	err = smb_pwd_lock();
180 	if (err != SMB_PWE_SUCCESS)
181 		return (err);
182 
183 	if (stat64(SMB_PASSWD, &stbuf) < 0) {
184 		err = SMB_PWE_STAT_FAILED;
185 		goto passwd_exit;
186 	}
187 
188 	if ((tempfd = open(SMB_PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
189 		err = SMB_PWE_OPEN_FAILED;
190 		goto passwd_exit;
191 	}
192 
193 	if ((dst = fdopen(tempfd, "wF")) == NULL) {
194 		err = SMB_PWE_OPEN_FAILED;
195 		goto passwd_exit;
196 	}
197 
198 	if ((src = fopen(SMB_PASSWD, "rF")) == NULL) {
199 		err = SMB_PWE_OPEN_FAILED;
200 		(void) fclose(dst);
201 		(void) unlink(SMB_PASSTEMP);
202 		goto passwd_exit;
203 	}
204 
205 	lm_str = smb_config_getenv(SMB_CI_LM_LEVEL);
206 	if (lm_str) {
207 		lm_level = strtoul(lm_str, 0, 10);
208 		free(lm_str);
209 	} else {
210 		lm_level = 4;
211 	}
212 
213 	if (lm_level >= 4)
214 		control |= SMB_PWC_NOLM;
215 
216 	/*
217 	 * copy old password entries to temporary file while replacing
218 	 * the entry that matches "name"
219 	 */
220 	pwbuf.pw_name = NULL;
221 	pwbuf.pw_pwd = &smbpw;
222 
223 	while (smb_pwd_fgetent(src, &pwbuf, buf, sizeof (buf)) != NULL) {
224 		if (strcmp(pwbuf.pw_name, name) == 0) {
225 			err = smb_pwd_chgpwent(&smbpw, password, control);
226 			if (err == SMB_PWE_USER_DISABLE)
227 				user_disable = B_TRUE;
228 			err = smb_pwd_fputent(dst, &pwbuf);
229 			newent = B_FALSE;
230 		} else {
231 			err = smb_pwd_fputent(dst, &pwbuf);
232 		}
233 
234 		if (err != SMB_PWE_SUCCESS) {
235 			(void) fclose(src);
236 			(void) fclose(dst);
237 			goto passwd_exit;
238 		}
239 	}
240 
241 	if (newent) {
242 		if (getpwnam_r(name, &uxpw, uxbuf, sizeof (uxbuf))) {
243 			pwbuf.pw_name = uxpw.pw_name;
244 			smbpw.pw_flags = 0;
245 			smbpw.pw_uid = uxpw.pw_uid;
246 			(void) smb_pwd_chgpwent(&smbpw, password, control);
247 			err = smb_pwd_fputent(dst, &pwbuf);
248 		} else {
249 			err = SMB_PWE_USER_UNKNOWN;
250 		}
251 
252 		if (err != SMB_PWE_SUCCESS) {
253 			(void) fclose(src);
254 			(void) fclose(dst);
255 			goto passwd_exit;
256 		}
257 	}
258 
259 	(void) fclose(src);
260 	if (fclose(dst) != 0) {
261 		err = SMB_PWE_CLOSE_FAILED;
262 		goto passwd_exit; /* Don't trust the temporary file */
263 	}
264 
265 	/* Rename temp to passwd */
266 	if (unlink(SMB_OPASSWD) && access(SMB_OPASSWD, 0) == 0) {
267 		err = SMB_PWE_UPDATE_FAILED;
268 		(void) unlink(SMB_PASSTEMP);
269 		goto passwd_exit;
270 	}
271 
272 	if (link(SMB_PASSWD, SMB_OPASSWD) == -1) {
273 		err = SMB_PWE_UPDATE_FAILED;
274 		(void) unlink(SMB_PASSTEMP);
275 		goto passwd_exit;
276 	}
277 
278 	if (rename(SMB_PASSTEMP, SMB_PASSWD) == -1) {
279 		err = SMB_PWE_UPDATE_FAILED;
280 		(void) unlink(SMB_PASSTEMP);
281 		goto passwd_exit;
282 	}
283 
284 	(void) chmod(SMB_PASSWD, 0400);
285 
286 passwd_exit:
287 	(void) smb_pwd_unlock();
288 	if ((err == SMB_PWE_SUCCESS) && user_disable)
289 		err = SMB_PWE_USER_DISABLE;
290 
291 	return (err);
292 }
293 
294 /*
295  * smb_getpwent
296  *
297  * Parse the buffer in the passed pwbuf and fill in the
298  * smb password structure to point to the parsed information.
299  * The entry format is:
300  *
301  *	<user-name>:<user-id>:<LM hash>:<NTLM hash>
302  *
303  * Returns a pointer to the password structure on success,
304  * otherwise returns NULL.
305  */
306 static smb_pwbuf_t *
307 smb_pwd_fgetent(FILE *fp, smb_pwbuf_t *pwbuf, char *buf, size_t bufsize)
308 {
309 	char *argv[SMB_PWD_NARG];
310 	smb_passwd_t *pw;
311 	smb_pwdarg_t i;
312 	int lm_len, nt_len;
313 
314 	if (fgets(buf, bufsize, fp) == NULL)
315 		return (NULL);
316 	(void) trim_whitespace(buf);
317 
318 	for (i = 0; i < SMB_PWD_NARG; ++i) {
319 		if ((argv[i] = strsep((char **)&buf, ":")) == 0) {
320 			return (NULL);
321 		}
322 	}
323 
324 	if ((*argv[SMB_PWD_NAME] == '\0') || (*argv[SMB_PWD_UID] == '\0'))
325 		return (NULL);
326 
327 	pwbuf->pw_name = argv[SMB_PWD_NAME];
328 	pw = pwbuf->pw_pwd;
329 	bzero(pw, sizeof (smb_passwd_t));
330 	pw->pw_uid = strtoul(argv[SMB_PWD_UID], 0, 10);
331 
332 	if (strcmp(argv[SMB_PWD_LMHASH], SMB_PWD_DISABLE) == 0) {
333 		pw->pw_flags |= SMB_PWF_DISABLE;
334 		(void) strcpy((char *)pw->pw_lmhash, SMB_PWD_DISABLE);
335 		(void) strcpy((char *)pw->pw_nthash, SMB_PWD_DISABLE);
336 		return (pwbuf);
337 	}
338 
339 	lm_len = strlen(argv[SMB_PWD_LMHASH]);
340 	if (lm_len == SMBAUTH_HEXHASH_SZ) {
341 		(void) hextobin(argv[SMB_PWD_LMHASH], SMBAUTH_HEXHASH_SZ,
342 		    (char *)pw->pw_lmhash, SMBAUTH_HASH_SZ);
343 
344 		pw->pw_flags |= SMB_PWF_LM;
345 	} else if (lm_len != 0) {
346 		return (NULL);
347 	}
348 
349 	nt_len = strlen(argv[SMB_PWD_NTHASH]);
350 	if (nt_len == SMBAUTH_HEXHASH_SZ) {
351 		(void) hextobin(argv[SMB_PWD_NTHASH], SMBAUTH_HEXHASH_SZ,
352 		    (char *)pw->pw_nthash, SMBAUTH_HASH_SZ);
353 
354 		pw->pw_flags |= SMB_PWF_NT;
355 	} else if (nt_len != 0) {
356 		return (NULL);
357 	}
358 
359 	return (pwbuf);
360 }
361 
362 static int
363 smb_pwd_chgpwent(smb_passwd_t *smbpw, const char *password, int control)
364 {
365 	if (control & SMB_PWC_DISABLE) {
366 		smbpw->pw_flags |= SMB_PWF_DISABLE;
367 		(void) strcpy((char *)smbpw->pw_lmhash, SMB_PWD_DISABLE);
368 		(void) strcpy((char *)smbpw->pw_nthash, SMB_PWD_DISABLE);
369 		smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT);
370 		return (SMB_PWE_SUCCESS);
371 	} else if ((control & SMB_PWC_ENABLE) &&
372 	    (smbpw->pw_flags & SMB_PWF_DISABLE)) {
373 		*smbpw->pw_lmhash = '\0';
374 		*smbpw->pw_nthash = '\0';
375 		smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT);
376 		return (SMB_PWE_SUCCESS);
377 	}
378 
379 	/* No password update if account is disabled */
380 	if (smbpw->pw_flags & SMB_PWF_DISABLE)
381 		return (SMB_PWE_USER_DISABLE);
382 
383 	if (control & SMB_PWC_NOLM) {
384 		smbpw->pw_flags &= ~SMB_PWF_LM;
385 		*smbpw->pw_lmhash = '\0';
386 	} else {
387 		smbpw->pw_flags |= SMB_PWF_LM;
388 		(void) smb_auth_lm_hash((char *)password, smbpw->pw_lmhash);
389 	}
390 
391 	smbpw->pw_flags |= SMB_PWF_NT;
392 	(void) smb_auth_ntlm_hash((char *)password, smbpw->pw_nthash);
393 	return (SMB_PWE_SUCCESS);
394 }
395 
396 /*
397  * smb_putpwent
398  *
399  * Creates LM and NTLM hash from the given plain text password
400  * and write them along with user's name and Id to the smbpasswd
401  * file.
402  */
403 static int
404 smb_pwd_fputent(FILE *fp, smb_pwbuf_t *pwbuf)
405 {
406 	smb_passwd_t *pw = pwbuf->pw_pwd;
407 	char hex_nthash[SMBAUTH_HEXHASH_SZ+1];
408 	char hex_lmhash[SMBAUTH_HEXHASH_SZ+1];
409 	int rc;
410 
411 	if ((pw->pw_flags & SMB_PWF_LM) == SMB_PWF_LM) {
412 		(void) bintohex((char *)pw->pw_lmhash, SMBAUTH_HASH_SZ,
413 		    hex_lmhash, SMBAUTH_HEXHASH_SZ);
414 		hex_lmhash[SMBAUTH_HEXHASH_SZ] = '\0';
415 	} else {
416 		(void) strcpy(hex_lmhash, (char *)pw->pw_lmhash);
417 	}
418 
419 	if ((pw->pw_flags & SMB_PWF_NT) == SMB_PWF_NT) {
420 		(void) bintohex((char *)pw->pw_nthash, SMBAUTH_HASH_SZ,
421 		    hex_nthash, SMBAUTH_HEXHASH_SZ);
422 		hex_nthash[SMBAUTH_HEXHASH_SZ] = '\0';
423 	} else {
424 		(void) strcpy(hex_nthash, (char *)pw->pw_nthash);
425 	}
426 
427 	rc = fprintf(fp, "%s:%d:%s:%s\n", pwbuf->pw_name, pw->pw_uid,
428 	    hex_lmhash, hex_nthash);
429 
430 	if (rc <= 0)
431 		return (SMB_PWE_WRITE_FAILED);
432 
433 	return (SMB_PWE_SUCCESS);
434 }
435 
436 static int
437 smb_pwd_lock(void)
438 {
439 	int res;
440 
441 	if (smb_pwd_flck()) {
442 		switch (errno) {
443 		case EINTR:
444 			res = SMB_PWE_BUSY;
445 			break;
446 		case EACCES:
447 			res = SMB_PWE_DENIED;
448 			break;
449 		case 0:
450 			res = SMB_PWE_SUCCESS;
451 			break;
452 		}
453 	} else
454 		res = SMB_PWE_SUCCESS;
455 
456 	return (res);
457 }
458 
459 static int
460 smb_pwd_unlock(void)
461 {
462 	if (smb_pwd_fulck())
463 		return (SMB_PWE_SYSTEM_ERROR);
464 
465 	return (SMB_PWE_SUCCESS);
466 }
467 
468 static int
469 smb_pwd_flck(void)
470 {
471 	int seconds = 0;
472 
473 	(void) mutex_lock(&lck_lock);
474 	for (;;) {
475 		if (lck_pid != 0 && lck_pid != getpid()) {
476 			/* somebody forked */
477 			lck_pid = 0;
478 			lck_tid = 0;
479 		}
480 
481 		if (lck_tid == 0) {
482 			if ((fildes = creat(SMB_PASSLCK, 0600)) == -1)
483 				break;
484 			flock.l_type = F_WRLCK;
485 			if (fcntl(fildes, F_SETLK, &flock) != -1) {
486 				lck_pid = getpid();
487 				lck_tid = thr_self();
488 				(void) mutex_unlock(&lck_lock);
489 				return (0);
490 			}
491 			(void) close(fildes);
492 			fildes = -1;
493 		}
494 
495 		if (seconds++ >= S_WAITTIME) {
496 			/*
497 			 * For compatibility with the past, pretend
498 			 * that we were interrupted by SIGALRM.
499 			 */
500 			errno = EINTR;
501 			break;
502 		}
503 
504 		(void) mutex_unlock(&lck_lock);
505 		(void) sleep(1);
506 		(void) mutex_lock(&lck_lock);
507 	}
508 	(void) mutex_unlock(&lck_lock);
509 
510 	return (-1);
511 }
512 
513 static int
514 smb_pwd_fulck(void)
515 {
516 	(void) mutex_lock(&lck_lock);
517 	if (lck_tid == thr_self() && fildes >= 0) {
518 		flock.l_type = F_UNLCK;
519 		(void) fcntl(fildes, F_SETLK, &flock);
520 		(void) close(fildes);
521 		fildes = -1;
522 		lck_pid = 0;
523 		lck_tid = 0;
524 		(void) mutex_unlock(&lck_lock);
525 		return (0);
526 	}
527 	(void) mutex_unlock(&lck_lock);
528 	return (-1);
529 }
530