diff options
Diffstat (limited to 'usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c')
-rw-r--r-- | usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c b/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c new file mode 100644 index 0000000000..10026d7418 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c @@ -0,0 +1,529 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <strings.h> +#include <synch.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <thread.h> +#include <pwd.h> +#include <smbsrv/libsmb.h> + +#define SMB_PASSWD "/var/smb/smbpasswd" +#define SMB_OPASSWD "/var/smb/osmbpasswd" +#define SMB_PASSTEMP "/var/smb/ptmp" +#define SMB_PASSLCK "/var/smb/.pwd.lock" + +#define SMB_PWD_DISABLE "*DIS*" +#define SMB_PWD_BUFSIZE 256 + +#define S_WAITTIME 15 + +typedef enum { + SMB_PWD_NAME = 0, + SMB_PWD_UID, + SMB_PWD_LMHASH, + SMB_PWD_NTHASH, + SMB_PWD_NARG +} smb_pwdarg_t; + +static struct flock flock = { + 0, /* l_type */ + 0, /* l_whence */ + 0, /* l_start */ + 0, /* l_len */ + 0, /* l_sysid */ + 0 /* l_pid */ + }; + +static pid_t lck_pid = 0; /* process's pid at last lock */ +static thread_t lck_tid = 0; /* thread that holds the lock */ +static int fildes = -1; +static mutex_t lck_lock = DEFAULTMUTEX; + +typedef struct smb_pwbuf { + char *pw_name; + smb_passwd_t *pw_pwd; +} smb_pwbuf_t; + +static int smb_pwd_lock(void); +static int smb_pwd_unlock(void); +static int smb_pwd_flck(void); +static int smb_pwd_fulck(void); + +static smb_pwbuf_t *smb_pwd_fgetent(FILE *, smb_pwbuf_t *, char *, size_t); +static int smb_pwd_fputent(FILE *, smb_pwbuf_t *); +static int smb_pwd_chgpwent(smb_passwd_t *, const char *, int); +static int smb_pwd_update(const char *, const char *, int); + +/* + * smb_pwd_get + * + * Returns a smb password structure for the given user name. + * smbpw is a pointer to a buffer allocated by the caller. + * + * Returns NULL upon failure. + */ +smb_passwd_t * +smb_pwd_getpasswd(const char *name, smb_passwd_t *smbpw) +{ + char buf[SMB_PWD_BUFSIZE]; + boolean_t found = B_FALSE; + smb_pwbuf_t pwbuf; + int err; + FILE *fp; + + err = smb_pwd_lock(); + if (err != SMB_PWE_SUCCESS) + return (NULL); + + if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) { + (void) smb_pwd_unlock(); + return (NULL); + } + + pwbuf.pw_name = NULL; + pwbuf.pw_pwd = smbpw; + + while (smb_pwd_fgetent(fp, &pwbuf, buf, sizeof (buf)) != NULL) { + if (strcmp(name, pwbuf.pw_name) == 0) { + if ((smbpw->pw_flags & (SMB_PWF_LM | SMB_PWF_NT))) + found = B_TRUE; + break; + } + } + + (void) fclose(fp); + (void) smb_pwd_unlock(); + + if (!found) { + bzero(smbpw, sizeof (smb_passwd_t)); + return (NULL); + } + + return (smbpw); +} + +/* + * smb_pwd_set + * + * Update/add the given user to the smbpasswd file. + */ +int +smb_pwd_setpasswd(const char *name, const char *password) +{ + return (smb_pwd_update(name, password, 0)); +} + +/* + * smb_pwd_setcntl + * + * Change the account state. This can be making the account + * disable/enable or removing its LM hash. + */ +int +smb_pwd_setcntl(const char *name, int control) +{ + if (control == 0) + return (SMB_PWE_SUCCESS); + + return (smb_pwd_update(name, NULL, control)); +} + +static int +smb_pwd_update(const char *name, const char *password, int control) +{ + struct stat64 stbuf; + FILE *src, *dst; + int tempfd; + char buf[SMB_PWD_BUFSIZE]; + int err = SMB_PWE_SUCCESS; + smb_pwbuf_t pwbuf; + smb_passwd_t smbpw; + boolean_t newent = B_TRUE; + boolean_t user_disable = B_FALSE; + char uxbuf[1024]; + struct passwd uxpw; + int lm_level; + char *lm_str; + + err = smb_pwd_lock(); + if (err != SMB_PWE_SUCCESS) + return (err); + + if (stat64(SMB_PASSWD, &stbuf) < 0) { + err = SMB_PWE_STAT_FAILED; + goto passwd_exit; + } + + if ((tempfd = open(SMB_PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { + err = SMB_PWE_OPEN_FAILED; + goto passwd_exit; + } + + if ((dst = fdopen(tempfd, "wF")) == NULL) { + err = SMB_PWE_OPEN_FAILED; + goto passwd_exit; + } + + if ((src = fopen(SMB_PASSWD, "rF")) == NULL) { + err = SMB_PWE_OPEN_FAILED; + (void) fclose(dst); + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + lm_str = smb_config_getenv(SMB_CI_LM_LEVEL); + if (lm_str) { + lm_level = strtoul(lm_str, 0, 10); + free(lm_str); + } else { + lm_level = 4; + } + + if (lm_level >= 4) + control |= SMB_PWC_NOLM; + + /* + * copy old password entries to temporary file while replacing + * the entry that matches "name" + */ + pwbuf.pw_name = NULL; + pwbuf.pw_pwd = &smbpw; + + while (smb_pwd_fgetent(src, &pwbuf, buf, sizeof (buf)) != NULL) { + if (strcmp(pwbuf.pw_name, name) == 0) { + err = smb_pwd_chgpwent(&smbpw, password, control); + if (err == SMB_PWE_USER_DISABLE) + user_disable = B_TRUE; + err = smb_pwd_fputent(dst, &pwbuf); + newent = B_FALSE; + } else { + err = smb_pwd_fputent(dst, &pwbuf); + } + + if (err != SMB_PWE_SUCCESS) { + (void) fclose(src); + (void) fclose(dst); + goto passwd_exit; + } + } + + if (newent) { + if (getpwnam_r(name, &uxpw, uxbuf, sizeof (uxbuf))) { + pwbuf.pw_name = uxpw.pw_name; + smbpw.pw_flags = 0; + smbpw.pw_uid = uxpw.pw_uid; + (void) smb_pwd_chgpwent(&smbpw, password, control); + err = smb_pwd_fputent(dst, &pwbuf); + } else { + err = SMB_PWE_USER_UNKNOWN; + } + + if (err != SMB_PWE_SUCCESS) { + (void) fclose(src); + (void) fclose(dst); + goto passwd_exit; + } + } + + (void) fclose(src); + if (fclose(dst) != 0) { + err = SMB_PWE_CLOSE_FAILED; + goto passwd_exit; /* Don't trust the temporary file */ + } + + /* Rename temp to passwd */ + if (unlink(SMB_OPASSWD) && access(SMB_OPASSWD, 0) == 0) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + if (link(SMB_PASSWD, SMB_OPASSWD) == -1) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + if (rename(SMB_PASSTEMP, SMB_PASSWD) == -1) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + (void) chmod(SMB_PASSWD, 0400); + +passwd_exit: + (void) smb_pwd_unlock(); + if ((err == SMB_PWE_SUCCESS) && user_disable) + err = SMB_PWE_USER_DISABLE; + + return (err); +} + +/* + * smb_getpwent + * + * Parse the buffer in the passed pwbuf and fill in the + * smb password structure to point to the parsed information. + * The entry format is: + * + * <user-name>:<user-id>:<LM hash>:<NTLM hash> + * + * Returns a pointer to the password structure on success, + * otherwise returns NULL. + */ +static smb_pwbuf_t * +smb_pwd_fgetent(FILE *fp, smb_pwbuf_t *pwbuf, char *buf, size_t bufsize) +{ + char *argv[SMB_PWD_NARG]; + smb_passwd_t *pw; + smb_pwdarg_t i; + int lm_len, nt_len; + + if (fgets(buf, bufsize, fp) == NULL) + return (NULL); + (void) trim_whitespace(buf); + + for (i = 0; i < SMB_PWD_NARG; ++i) { + if ((argv[i] = strsep((char **)&buf, ":")) == 0) { + return (NULL); + } + } + + if ((*argv[SMB_PWD_NAME] == '\0') || (*argv[SMB_PWD_UID] == '\0')) + return (NULL); + + pwbuf->pw_name = argv[SMB_PWD_NAME]; + pw = pwbuf->pw_pwd; + bzero(pw, sizeof (smb_passwd_t)); + pw->pw_uid = strtoul(argv[SMB_PWD_UID], 0, 10); + + if (strcmp(argv[SMB_PWD_LMHASH], SMB_PWD_DISABLE) == 0) { + pw->pw_flags |= SMB_PWF_DISABLE; + (void) strcpy((char *)pw->pw_lmhash, SMB_PWD_DISABLE); + (void) strcpy((char *)pw->pw_nthash, SMB_PWD_DISABLE); + return (pwbuf); + } + + lm_len = strlen(argv[SMB_PWD_LMHASH]); + if (lm_len == SMBAUTH_HEXHASH_SZ) { + (void) hextobin(argv[SMB_PWD_LMHASH], SMBAUTH_HEXHASH_SZ, + (char *)pw->pw_lmhash, SMBAUTH_HASH_SZ); + + pw->pw_flags |= SMB_PWF_LM; + } else if (lm_len != 0) { + return (NULL); + } + + nt_len = strlen(argv[SMB_PWD_NTHASH]); + if (nt_len == SMBAUTH_HEXHASH_SZ) { + (void) hextobin(argv[SMB_PWD_NTHASH], SMBAUTH_HEXHASH_SZ, + (char *)pw->pw_nthash, SMBAUTH_HASH_SZ); + + pw->pw_flags |= SMB_PWF_NT; + } else if (nt_len != 0) { + return (NULL); + } + + return (pwbuf); +} + +static int +smb_pwd_chgpwent(smb_passwd_t *smbpw, const char *password, int control) +{ + if (control & SMB_PWC_DISABLE) { + smbpw->pw_flags |= SMB_PWF_DISABLE; + (void) strcpy((char *)smbpw->pw_lmhash, SMB_PWD_DISABLE); + (void) strcpy((char *)smbpw->pw_nthash, SMB_PWD_DISABLE); + smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); + return (SMB_PWE_SUCCESS); + } else if ((control & SMB_PWC_ENABLE) && + (smbpw->pw_flags & SMB_PWF_DISABLE)) { + *smbpw->pw_lmhash = '\0'; + *smbpw->pw_nthash = '\0'; + smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); + return (SMB_PWE_SUCCESS); + } + + /* No password update if account is disabled */ + if (smbpw->pw_flags & SMB_PWF_DISABLE) + return (SMB_PWE_USER_DISABLE); + + if (control & SMB_PWC_NOLM) { + smbpw->pw_flags &= ~SMB_PWF_LM; + *smbpw->pw_lmhash = '\0'; + } else { + smbpw->pw_flags |= SMB_PWF_LM; + (void) smb_auth_lm_hash((char *)password, smbpw->pw_lmhash); + } + + smbpw->pw_flags |= SMB_PWF_NT; + (void) smb_auth_ntlm_hash((char *)password, smbpw->pw_nthash); + return (SMB_PWE_SUCCESS); +} + +/* + * smb_putpwent + * + * Creates LM and NTLM hash from the given plain text password + * and write them along with user's name and Id to the smbpasswd + * file. + */ +static int +smb_pwd_fputent(FILE *fp, smb_pwbuf_t *pwbuf) +{ + smb_passwd_t *pw = pwbuf->pw_pwd; + char hex_nthash[SMBAUTH_HEXHASH_SZ+1]; + char hex_lmhash[SMBAUTH_HEXHASH_SZ+1]; + int rc; + + if ((pw->pw_flags & SMB_PWF_LM) == SMB_PWF_LM) { + (void) bintohex((char *)pw->pw_lmhash, SMBAUTH_HASH_SZ, + hex_lmhash, SMBAUTH_HEXHASH_SZ); + hex_lmhash[SMBAUTH_HEXHASH_SZ] = '\0'; + } else { + (void) strcpy(hex_lmhash, (char *)pw->pw_lmhash); + } + + if ((pw->pw_flags & SMB_PWF_NT) == SMB_PWF_NT) { + (void) bintohex((char *)pw->pw_nthash, SMBAUTH_HASH_SZ, + hex_nthash, SMBAUTH_HEXHASH_SZ); + hex_nthash[SMBAUTH_HEXHASH_SZ] = '\0'; + } else { + (void) strcpy(hex_nthash, (char *)pw->pw_nthash); + } + + rc = fprintf(fp, "%s:%d:%s:%s\n", pwbuf->pw_name, pw->pw_uid, + hex_lmhash, hex_nthash); + + if (rc <= 0) + return (SMB_PWE_WRITE_FAILED); + + return (SMB_PWE_SUCCESS); +} + +static int +smb_pwd_lock(void) +{ + int res; + + if (smb_pwd_flck()) { + switch (errno) { + case EINTR: + res = SMB_PWE_BUSY; + break; + case EACCES: + res = SMB_PWE_DENIED; + break; + case 0: + res = SMB_PWE_SUCCESS; + break; + } + } else + res = SMB_PWE_SUCCESS; + + return (res); +} + +static int +smb_pwd_unlock(void) +{ + if (smb_pwd_fulck()) + return (SMB_PWE_SYSTEM_ERROR); + + return (SMB_PWE_SUCCESS); +} + +static int +smb_pwd_flck(void) +{ + int seconds = 0; + + (void) mutex_lock(&lck_lock); + for (;;) { + if (lck_pid != 0 && lck_pid != getpid()) { + /* somebody forked */ + lck_pid = 0; + lck_tid = 0; + } + + if (lck_tid == 0) { + if ((fildes = creat(SMB_PASSLCK, 0600)) == -1) + break; + flock.l_type = F_WRLCK; + if (fcntl(fildes, F_SETLK, &flock) != -1) { + lck_pid = getpid(); + lck_tid = thr_self(); + (void) mutex_unlock(&lck_lock); + return (0); + } + (void) close(fildes); + fildes = -1; + } + + if (seconds++ >= S_WAITTIME) { + /* + * For compatibility with the past, pretend + * that we were interrupted by SIGALRM. + */ + errno = EINTR; + break; + } + + (void) mutex_unlock(&lck_lock); + (void) sleep(1); + (void) mutex_lock(&lck_lock); + } + (void) mutex_unlock(&lck_lock); + + return (-1); +} + +static int +smb_pwd_fulck(void) +{ + (void) mutex_lock(&lck_lock); + if (lck_tid == thr_self() && fildes >= 0) { + flock.l_type = F_UNLCK; + (void) fcntl(fildes, F_SETLK, &flock); + (void) close(fildes); + fildes = -1; + lck_pid = 0; + lck_tid = 0; + (void) mutex_unlock(&lck_lock); + return (0); + } + (void) mutex_unlock(&lck_lock); + return (-1); +} |