pw_user.c revision 285398
1254721Semaste/*- 2254721Semaste * Copyright (C) 1996 3254721Semaste * David L. Nugent. All rights reserved. 4254721Semaste * 5254721Semaste * Redistribution and use in source and binary forms, with or without 6254721Semaste * modification, are permitted provided that the following conditions 7254721Semaste * are met: 8254721Semaste * 1. Redistributions of source code must retain the above copyright 9254721Semaste * notice, this list of conditions and the following disclaimer. 10254721Semaste * 2. Redistributions in binary form must reproduce the above copyright 11254721Semaste * notice, this list of conditions and the following disclaimer in the 12254721Semaste * documentation and/or other materials provided with the distribution. 13254721Semaste * 14254721Semaste * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 15254721Semaste * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16254721Semaste * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17254721Semaste * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 18254721Semaste * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19254721Semaste * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20254721Semaste * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21254721Semaste * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22254721Semaste * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23254721Semaste * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24254721Semaste * SUCH DAMAGE. 25254721Semaste * 26254721Semaste */ 27254721Semaste 28254721Semaste#ifndef lint 29254721Semastestatic const char rcsid[] = 30254721Semaste "$FreeBSD: head/usr.sbin/pw/pw_user.c 285398 2015-07-11 18:09:27Z bapt $"; 31254721Semaste#endif /* not lint */ 32254721Semaste 33254721Semaste#include <ctype.h> 34254721Semaste#include <err.h> 35254721Semaste#include <fcntl.h> 36254721Semaste#include <sys/param.h> 37254721Semaste#include <dirent.h> 38254721Semaste#include <paths.h> 39254721Semaste#include <termios.h> 40254721Semaste#include <sys/types.h> 41254721Semaste#include <sys/time.h> 42254721Semaste#include <sys/resource.h> 43254721Semaste#include <login_cap.h> 44254721Semaste#include <pwd.h> 45254721Semaste#include <grp.h> 46254721Semaste#include <libutil.h> 47254721Semaste#include "pw.h" 48254721Semaste#include "bitmap.h" 49254721Semaste 50254721Semaste#define LOGNAMESIZE (MAXLOGNAME-1) 51254721Semaste 52254721Semastestatic char locked_str[] = "*LOCKED*"; 53254721Semaste 54254721Semastestatic int delete_user(struct userconf *cnf, struct passwd *pwd, 55254721Semaste char *name, int delete, int mode); 56254721Semastestatic int print_user(struct passwd * pwd); 57254721Semastestatic uid_t pw_uidpolicy(struct userconf * cnf, long id); 58254721Semastestatic uid_t pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer); 59254721Semastestatic time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args); 60254721Semastestatic time_t pw_exppolicy(struct userconf * cnf, struct cargs * args); 61254721Semastestatic char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user); 62254721Semastestatic char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell); 63254721Semastestatic char *pw_password(struct userconf * cnf, struct cargs * args, char const * user); 64254721Semastestatic char *shell_path(char const * path, char *shells[], char *sh); 65254721Semastestatic void rmat(uid_t uid); 66254721Semastestatic void rmopie(char const * name); 67254721Semaste 68254721Semastestatic void 69254721Semastecreate_and_populate_homedir(int mode, struct passwd *pwd) 70254721Semaste{ 71254721Semaste char *homedir, *dotdir; 72254721Semaste struct userconf *cnf = conf.userconf; 73254721Semaste 74254721Semaste homedir = dotdir = NULL; 75254721Semaste 76254721Semaste if (conf.rootdir[0] != '\0') { 77254721Semaste asprintf(&homedir, "%s/%s", conf.rootdir, pwd->pw_dir); 78254721Semaste if (homedir == NULL) 79254721Semaste errx(EX_OSERR, "out of memory"); 80254721Semaste asprintf(&dotdir, "%s/%s", conf.rootdir, cnf->dotdir); 81254721Semaste } 82254721Semaste 83254721Semaste copymkdir(homedir ? homedir : pwd->pw_dir, dotdir ? dotdir: cnf->dotdir, 84254721Semaste cnf->homemode, pwd->pw_uid, pwd->pw_gid); 85254721Semaste pw_log(cnf, mode, W_USER, "%s(%u) home %s made", pwd->pw_name, 86254721Semaste pwd->pw_uid, pwd->pw_dir); 87254721Semaste} 88254721Semaste 89254721Semastestatic int 90254721Semasteset_passwd(struct passwd *pwd, bool update) 91254721Semaste{ 92254721Semaste int b, istty; 93254721Semaste struct termios t, n; 94254721Semaste login_cap_t *lc; 95254721Semaste char line[_PASSWORD_LEN+1]; 96254721Semaste char *p; 97254721Semaste 98254721Semaste if (conf.fd == '-') { 99254721Semaste if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { 100254721Semaste pwd->pw_passwd = "*"; /* No access */ 101254721Semaste return (1); 102254721Semaste } 103254721Semaste return (0); 104254721Semaste } 105254721Semaste 106254721Semaste if ((istty = isatty(conf.fd))) { 107254721Semaste if (tcgetattr(conf.fd, &t) == -1) 108254721Semaste istty = 0; 109254721Semaste else { 110254721Semaste n = t; 111254721Semaste n.c_lflag &= ~(ECHO); 112254721Semaste tcsetattr(conf.fd, TCSANOW, &n); 113254721Semaste printf("%s%spassword for user %s:", 114254721Semaste update ? "new " : "", 115254721Semaste conf.precrypted ? "encrypted " : "", 116254721Semaste pwd->pw_name); 117254721Semaste fflush(stdout); 118254721Semaste } 119254721Semaste } 120254721Semaste b = read(conf.fd, line, sizeof(line) - 1); 121254721Semaste if (istty) { /* Restore state */ 122254721Semaste tcsetattr(conf.fd, TCSANOW, &t); 123254721Semaste fputc('\n', stdout); 124254721Semaste fflush(stdout); 125254721Semaste } 126254721Semaste 127254721Semaste if (b < 0) 128254721Semaste err(EX_IOERR, "-%c file descriptor", 129254721Semaste conf.precrypted ? 'H' : 'h'); 130254721Semaste line[b] = '\0'; 131254721Semaste if ((p = strpbrk(line, "\r\n")) != NULL) 132254721Semaste *p = '\0'; 133254721Semaste if (!*line) 134254721Semaste errx(EX_DATAERR, "empty password read on file descriptor %d", 135254721Semaste conf.fd); 136254721Semaste if (conf.precrypted) { 137254721Semaste if (strchr(line, ':') != NULL) 138254721Semaste errx(EX_DATAERR, "bad encrypted password"); 139254721Semaste pwd->pw_passwd = line; 140254721Semaste } else { 141254721Semaste lc = login_getpwclass(pwd); 142254721Semaste if (lc == NULL || 143254721Semaste login_setcryptfmt(lc, "sha512", NULL) == NULL) 144254721Semaste warn("setting crypt(3) format"); 145254721Semaste login_close(lc); 146254721Semaste pwd->pw_passwd = pw_pwcrypt(line); 147254721Semaste } 148254721Semaste return (1); 149254721Semaste} 150254721Semaste 151254721Semasteint 152254721Semastepw_usernext(struct userconf *cnf, bool quiet) 153254721Semaste{ 154254721Semaste uid_t next = pw_uidpolicy(cnf, -1); 155254721Semaste 156254721Semaste if (quiet) 157254721Semaste return (next); 158254721Semaste 159254721Semaste printf("%u:", next); 160254721Semaste pw_groupnext(cnf, quiet); 161254721Semaste 162254721Semaste return (EXIT_SUCCESS); 163254721Semaste} 164254721Semaste 165254721Semastestatic int 166254721Semastepw_usershow(char *name, long id, struct passwd *fakeuser) 167254721Semaste{ 168254721Semaste struct passwd *pwd = NULL; 169254721Semaste 170254721Semaste if (id < 0 && name == NULL && !conf.all) 171254721Semaste errx(EX_DATAERR, "username or id or '-a' required"); 172254721Semaste 173254721Semaste if (conf.all) { 174254721Semaste SETPWENT(); 175254721Semaste while ((pwd = GETPWENT()) != NULL) 176254721Semaste print_user(pwd); 177254721Semaste ENDPWENT(); 178254721Semaste return (EXIT_SUCCESS); 179254721Semaste } 180254721Semaste 181254721Semaste pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 182254721Semaste if (pwd == NULL) { 183254721Semaste if (conf.force) { 184254721Semaste pwd = fakeuser; 185254721Semaste } else { 186254721Semaste if (name == NULL) 187254721Semaste errx(EX_NOUSER, "no such uid `%ld'", id); 188254721Semaste errx(EX_NOUSER, "no such user `%s'", name); 189254721Semaste } 190254721Semaste } 191254721Semaste 192254721Semaste return (print_user(pwd)); 193254721Semaste} 194254721Semaste 195254721Semaste/*- 196254721Semaste * -C config configuration file 197254721Semaste * -q quiet operation 198254721Semaste * -n name login name 199254721Semaste * -u uid user id 200254721Semaste * -c comment user name/comment 201254721Semaste * -d directory home directory 202254721Semaste * -e date account expiry date 203254721Semaste * -p date password expiry date 204254721Semaste * -g grp primary group 205254721Semaste * -G grp1,grp2 additional groups 206254721Semaste * -m [ -k dir ] create and set up home 207254721Semaste * -s shell name of login shell 208254721Semaste * -o duplicate uid ok 209254721Semaste * -L class user class 210254721Semaste * -l name new login name 211254721Semaste * -h fd password filehandle 212254721Semaste * -H fd encrypted password filehandle 213254721Semaste * -F force print or add 214254721Semaste * Setting defaults: 215254721Semaste * -D set user defaults 216254721Semaste * -b dir default home root dir 217254721Semaste * -e period default expiry period 218254721Semaste * -p period default password change period 219254721Semaste * -g group default group 220254721Semaste * -G grp1,grp2.. default additional groups 221254721Semaste * -L class default login class 222254721Semaste * -k dir default home skeleton 223254721Semaste * -s shell default shell 224254721Semaste * -w method default password method 225254721Semaste */ 226254721Semaste 227254721Semasteint 228254721Semastepw_user(int mode, char *name, long id, struct cargs * args) 229254721Semaste{ 230254721Semaste int rc, edited = 0; 231254721Semaste char *p = NULL; 232254721Semaste char *passtmp; 233254721Semaste struct carg *arg; 234254721Semaste struct passwd *pwd = NULL; 235254721Semaste struct group *grp; 236254721Semaste struct stat st; 237254721Semaste struct userconf *cnf; 238254721Semaste char line[_PASSWORD_LEN+1]; 239254721Semaste char path[MAXPATHLEN]; 240254721Semaste FILE *fp; 241254721Semaste char *dmode_c; 242254721Semaste void *set = NULL; 243254721Semaste 244254721Semaste static struct passwd fakeuser = 245254721Semaste { 246254721Semaste "nouser", 247254721Semaste "*", 248254721Semaste -1, 249254721Semaste -1, 250254721Semaste 0, 251254721Semaste "", 252254721Semaste "User &", 253254721Semaste "/nonexistent", 254254721Semaste "/bin/sh", 255254721Semaste 0 256254721Semaste#if defined(__FreeBSD__) 257254721Semaste ,0 258254721Semaste#endif 259254721Semaste }; 260254721Semaste 261254721Semaste cnf = conf.userconf; 262254721Semaste 263254721Semaste if (mode == M_NEXT) 264254721Semaste return (pw_usernext(cnf, conf.quiet)); 265254721Semaste 266254721Semaste if (mode == M_PRINT) 267254721Semaste return (pw_usershow(name, id, &fakeuser)); 268254721Semaste 269254721Semaste /* 270254721Semaste * We can do all of the common legwork here 271254721Semaste */ 272254721Semaste 273254721Semaste if ((arg = getarg(args, 'b')) != NULL) { 274254721Semaste cnf->home = arg->val; 275254721Semaste } 276254721Semaste 277254721Semaste if ((arg = getarg(args, 'M')) != NULL) { 278254721Semaste dmode_c = arg->val; 279254721Semaste if ((set = setmode(dmode_c)) == NULL) 280254721Semaste errx(EX_DATAERR, "invalid directory creation mode '%s'", 281254721Semaste dmode_c); 282254721Semaste cnf->homemode = getmode(set, _DEF_DIRMODE); 283254721Semaste free(set); 284254721Semaste } 285254721Semaste 286254721Semaste /* 287254721Semaste * If we'll need to use it or we're updating it, 288254721Semaste * then create the base home directory if necessary 289254721Semaste */ 290254721Semaste if (arg != NULL || getarg(args, 'm') != NULL) { 291254721Semaste int l = strlen(cnf->home); 292254721Semaste 293254721Semaste if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */ 294254721Semaste cnf->home[--l] = '\0'; 295254721Semaste 296254721Semaste if (l < 2 || *cnf->home != '/') /* Check for absolute path name */ 297254721Semaste errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home); 298254721Semaste 299254721Semaste if (stat(cnf->home, &st) == -1) { 300254721Semaste char dbuf[MAXPATHLEN]; 301254721Semaste 302254721Semaste /* 303254721Semaste * This is a kludge especially for Joerg :) 304254721Semaste * If the home directory would be created in the root partition, then 305254721Semaste * we really create it under /usr which is likely to have more space. 306254721Semaste * But we create a symlink from cnf->home -> "/usr" -> cnf->home 307254721Semaste */ 308254721Semaste if (strchr(cnf->home+1, '/') == NULL) { 309254721Semaste snprintf(dbuf, MAXPATHLEN, "/usr%s", cnf->home); 310254721Semaste if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) { 311254721Semaste chown(dbuf, 0, 0); 312254721Semaste /* 313254721Semaste * Skip first "/" and create symlink: 314254721Semaste * /home -> usr/home 315254721Semaste */ 316254721Semaste symlink(dbuf+1, cnf->home); 317254721Semaste } 318254721Semaste /* If this falls, fall back to old method */ 319254721Semaste } 320254721Semaste strlcpy(dbuf, cnf->home, sizeof(dbuf)); 321254721Semaste p = dbuf; 322254721Semaste if (stat(dbuf, &st) == -1) { 323254721Semaste while ((p = strchr(p + 1, '/')) != NULL) { 324254721Semaste *p = '\0'; 325254721Semaste if (stat(dbuf, &st) == -1) { 326254721Semaste if (mkdir(dbuf, _DEF_DIRMODE) == -1) 327254721Semaste goto direrr; 328254721Semaste chown(dbuf, 0, 0); 329254721Semaste } else if (!S_ISDIR(st.st_mode)) 330254721Semaste errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf); 331254721Semaste *p = '/'; 332254721Semaste } 333254721Semaste } 334254721Semaste if (stat(dbuf, &st) == -1) { 335254721Semaste if (mkdir(dbuf, _DEF_DIRMODE) == -1) { 336254721Semaste direrr: err(EX_OSFILE, "mkdir '%s'", dbuf); 337254721Semaste } 338254721Semaste chown(dbuf, 0, 0); 339254721Semaste } 340254721Semaste } else if (!S_ISDIR(st.st_mode)) 341254721Semaste errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home); 342254721Semaste } 343254721Semaste 344254721Semaste if ((arg = getarg(args, 'e')) != NULL) 345254721Semaste cnf->expire_days = atoi(arg->val); 346254721Semaste 347254721Semaste if ((arg = getarg(args, 'y')) != NULL) 348254721Semaste cnf->nispasswd = arg->val; 349254721Semaste 350254721Semaste if ((arg = getarg(args, 'p')) != NULL && arg->val) 351254721Semaste cnf->password_days = atoi(arg->val); 352254721Semaste 353254721Semaste if ((arg = getarg(args, 'g')) != NULL) { 354254721Semaste if (!*(p = arg->val)) /* Handle empty group list specially */ 355254721Semaste cnf->default_group = ""; 356254721Semaste else { 357254721Semaste if ((grp = GETGRNAM(p)) == NULL) { 358254721Semaste if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) 359254721Semaste errx(EX_NOUSER, "group `%s' does not exist", p); 360254721Semaste } 361254721Semaste cnf->default_group = newstr(grp->gr_name); 362254721Semaste } 363254721Semaste } 364254721Semaste if ((arg = getarg(args, 'L')) != NULL) 365254721Semaste cnf->default_class = pw_checkname(arg->val, 0); 366254721Semaste 367254721Semaste if ((arg = getarg(args, 'G')) != NULL && arg->val) { 368254721Semaste int i = 0; 369254721Semaste 370254721Semaste for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 371254721Semaste if ((grp = GETGRNAM(p)) == NULL) { 372254721Semaste if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) 373254721Semaste errx(EX_NOUSER, "group `%s' does not exist", p); 374254721Semaste } 375254721Semaste if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1) 376254721Semaste cnf->groups[i++] = newstr(grp->gr_name); 377254721Semaste } 378254721Semaste while (i < cnf->numgroups) 379254721Semaste cnf->groups[i++] = NULL; 380254721Semaste } 381254721Semaste 382254721Semaste if ((arg = getarg(args, 'k')) != NULL) { 383254721Semaste if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode)) 384254721Semaste errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir); 385254721Semaste } 386254721Semaste 387254721Semaste if ((arg = getarg(args, 's')) != NULL) 388254721Semaste cnf->shell_default = arg->val; 389254721Semaste 390254721Semaste if ((arg = getarg(args, 'w')) != NULL) 391254721Semaste cnf->default_password = boolean_val(arg->val, cnf->default_password); 392254721Semaste if (mode == M_ADD && getarg(args, 'D')) { 393254721Semaste if (name != NULL) 394254721Semaste errx(EX_DATAERR, "can't combine `-D' with `-n name'"); 395254721Semaste if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { 396254721Semaste if ((cnf->min_uid = (uid_t) atoi(p)) == 0) 397254721Semaste cnf->min_uid = 1000; 398254721Semaste if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid) 399254721Semaste cnf->max_uid = 32000; 400254721Semaste } 401254721Semaste if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { 402254721Semaste if ((cnf->min_gid = (gid_t) atoi(p)) == 0) 403254721Semaste cnf->min_gid = 1000; 404254721Semaste if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid) 405254721Semaste cnf->max_gid = 32000; 406254721Semaste } 407254721Semaste 408254721Semaste if (write_userconfig(conf.config)) 409254721Semaste return (EXIT_SUCCESS); 410254721Semaste err(EX_IOERR, "config udpate"); 411254721Semaste } 412254721Semaste 413254721Semaste if (name != NULL) 414254721Semaste pwd = GETPWNAM(pw_checkname(name, 0)); 415254721Semaste 416254721Semaste if (id < 0 && name == NULL) 417254721Semaste errx(EX_DATAERR, "user name or id required"); 418254721Semaste 419254721Semaste /* 420254721Semaste * Update, delete & print require that the user exists 421254721Semaste */ 422254721Semaste if (mode == M_UPDATE || mode == M_DELETE || 423254721Semaste mode == M_LOCK || mode == M_UNLOCK) { 424254721Semaste 425254721Semaste if (name == NULL && pwd == NULL) /* Try harder */ 426254721Semaste pwd = GETPWUID(id); 427254721Semaste 428254721Semaste if (pwd == NULL) { 429254721Semaste if (name == NULL) 430254721Semaste errx(EX_NOUSER, "no such uid `%ld'", id); 431254721Semaste errx(EX_NOUSER, "no such user `%s'", name); 432254721Semaste } 433254721Semaste 434254721Semaste if (name == NULL) 435254721Semaste name = pwd->pw_name; 436254721Semaste 437254721Semaste /* 438254721Semaste * The M_LOCK and M_UNLOCK functions simply add or remove 439254721Semaste * a "*LOCKED*" prefix from in front of the password to 440254721Semaste * prevent it decoding correctly, and therefore prevents 441254721Semaste * access. Of course, this only prevents access via 442254721Semaste * password authentication (not ssh, kerberos or any 443254721Semaste * other method that does not use the UNIX password) but 444254721Semaste * that is a known limitation. 445254721Semaste */ 446254721Semaste 447254721Semaste if (mode == M_LOCK) { 448254721Semaste if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0) 449254721Semaste errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); 450254721Semaste asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd); 451254721Semaste if (passtmp == NULL) /* disaster */ 452254721Semaste errx(EX_UNAVAILABLE, "out of memory"); 453254721Semaste pwd->pw_passwd = passtmp; 454254721Semaste edited = 1; 455254721Semaste } else if (mode == M_UNLOCK) { 456254721Semaste if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0) 457254721Semaste errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); 458254721Semaste pwd->pw_passwd += sizeof(locked_str)-1; 459254721Semaste edited = 1; 460254721Semaste } else if (mode == M_DELETE) 461254721Semaste return (delete_user(cnf, pwd, name, 462254721Semaste getarg(args, 'r') != NULL, mode)); 463254721Semaste 464254721Semaste /* 465254721Semaste * The rest is edit code 466254721Semaste */ 467254721Semaste if (conf.newname != NULL) { 468254721Semaste if (strcmp(pwd->pw_name, "root") == 0) 469254721Semaste errx(EX_DATAERR, "can't rename `root' account"); 470254721Semaste pwd->pw_name = pw_checkname(conf.newname, 0); 471254721Semaste edited = 1; 472254721Semaste } 473254721Semaste 474254721Semaste if (id > 0 && isdigit((unsigned char)*arg->val)) { 475254721Semaste pwd->pw_uid = (uid_t)id; 476254721Semaste edited = 1; 477254721Semaste if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) 478254721Semaste errx(EX_DATAERR, "can't change uid of `root' account"); 479254721Semaste if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 480254721Semaste warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name); 481254721Semaste } 482254721Semaste 483254721Semaste if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */ 484254721Semaste gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid; 485254721Semaste if (newgid != pwd->pw_gid) { 486254721Semaste edited = 1; 487254721Semaste pwd->pw_gid = newgid; 488254721Semaste } 489254721Semaste } 490254721Semaste 491254721Semaste if ((arg = getarg(args, 'p')) != NULL) { 492254721Semaste if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { 493254721Semaste if (pwd->pw_change != 0) { 494254721Semaste pwd->pw_change = 0; 495254721Semaste edited = 1; 496254721Semaste } 497254721Semaste } 498254721Semaste else { 499254721Semaste time_t now = time(NULL); 500254721Semaste time_t expire = parse_date(now, arg->val); 501254721Semaste 502254721Semaste if (pwd->pw_change != expire) { 503254721Semaste pwd->pw_change = expire; 504254721Semaste edited = 1; 505254721Semaste } 506254721Semaste } 507254721Semaste } 508254721Semaste 509254721Semaste if ((arg = getarg(args, 'e')) != NULL) { 510254721Semaste if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { 511254721Semaste if (pwd->pw_expire != 0) { 512254721Semaste pwd->pw_expire = 0; 513254721Semaste edited = 1; 514254721Semaste } 515254721Semaste } 516254721Semaste else { 517254721Semaste time_t now = time(NULL); 518254721Semaste time_t expire = parse_date(now, arg->val); 519254721Semaste 520254721Semaste if (pwd->pw_expire != expire) { 521254721Semaste pwd->pw_expire = expire; 522254721Semaste edited = 1; 523254721Semaste } 524254721Semaste } 525254721Semaste } 526254721Semaste 527254721Semaste if ((arg = getarg(args, 's')) != NULL) { 528254721Semaste char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val); 529254721Semaste if (shell == NULL) 530254721Semaste shell = ""; 531254721Semaste if (strcmp(shell, pwd->pw_shell) != 0) { 532254721Semaste pwd->pw_shell = shell; 533254721Semaste edited = 1; 534254721Semaste } 535254721Semaste } 536254721Semaste 537254721Semaste if (getarg(args, 'L')) { 538254721Semaste if (cnf->default_class == NULL) 539254721Semaste cnf->default_class = ""; 540254721Semaste if (strcmp(pwd->pw_class, cnf->default_class) != 0) { 541254721Semaste pwd->pw_class = cnf->default_class; 542254721Semaste edited = 1; 543254721Semaste } 544254721Semaste } 545254721Semaste 546254721Semaste if ((arg = getarg(args, 'd')) != NULL) { 547254721Semaste if (strcmp(pwd->pw_dir, arg->val)) 548254721Semaste edited = 1; 549254721Semaste if (stat(pwd->pw_dir = arg->val, &st) == -1) { 550254721Semaste if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0) 551254721Semaste warnx("WARNING: home `%s' does not exist", pwd->pw_dir); 552254721Semaste } else if (!S_ISDIR(st.st_mode)) 553254721Semaste warnx("WARNING: home `%s' is not a directory", pwd->pw_dir); 554254721Semaste } 555254721Semaste 556254721Semaste if ((arg = getarg(args, 'w')) != NULL && conf.fd == -1) { 557254721Semaste login_cap_t *lc; 558254721Semaste 559254721Semaste lc = login_getpwclass(pwd); 560254721Semaste if (lc == NULL || 561254721Semaste login_setcryptfmt(lc, "sha512", NULL) == NULL) 562254721Semaste warn("setting crypt(3) format"); 563254721Semaste login_close(lc); 564254721Semaste pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); 565254721Semaste edited = 1; 566254721Semaste } 567254721Semaste 568254721Semaste } else { 569254721Semaste login_cap_t *lc; 570254721Semaste 571254721Semaste /* 572254721Semaste * Add code 573254721Semaste */ 574254721Semaste 575254721Semaste if (name == NULL) /* Required */ 576254721Semaste errx(EX_DATAERR, "login name required"); 577254721Semaste else if ((pwd = GETPWNAM(name)) != NULL) /* Exists */ 578254721Semaste errx(EX_DATAERR, "login name `%s' already exists", name); 579254721Semaste 580254721Semaste /* 581254721Semaste * Now, set up defaults for a new user 582254721Semaste */ 583254721Semaste pwd = &fakeuser; 584254721Semaste pwd->pw_name = name; 585254721Semaste pwd->pw_class = cnf->default_class ? cnf->default_class : ""; 586254721Semaste pwd->pw_uid = pw_uidpolicy(cnf, id); 587254721Semaste pwd->pw_gid = pw_gidpolicy(args, pwd->pw_name, (gid_t) pwd->pw_uid); 588254721Semaste pwd->pw_change = pw_pwdpolicy(cnf, args); 589254721Semaste pwd->pw_expire = pw_exppolicy(cnf, args); 590254721Semaste pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name); 591254721Semaste pwd->pw_shell = pw_shellpolicy(cnf, args, NULL); 592254721Semaste lc = login_getpwclass(pwd); 593254721Semaste if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) 594254721Semaste warn("setting crypt(3) format"); 595254721Semaste login_close(lc); 596254721Semaste pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); 597254721Semaste edited = 1; 598254721Semaste 599254721Semaste if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 600254721Semaste warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name); 601254721Semaste } 602254721Semaste 603254721Semaste /* 604254721Semaste * Shared add/edit code 605254721Semaste */ 606254721Semaste if ((arg = getarg(args, 'c')) != NULL) { 607254721Semaste char *gecos = pw_checkname(arg->val, 1); 608254721Semaste if (strcmp(pwd->pw_gecos, gecos) != 0) { 609254721Semaste pwd->pw_gecos = gecos; 610254721Semaste edited = 1; 611254721Semaste } 612254721Semaste } 613254721Semaste 614254721Semaste if (conf.fd != -1) 615254721Semaste edited = set_passwd(pwd, mode == M_UPDATE); 616254721Semaste 617254721Semaste /* 618254721Semaste * Special case: -N only displays & exits 619254721Semaste */ 620254721Semaste if (conf.dryrun) 621254721Semaste return print_user(pwd); 622254721Semaste 623254721Semaste if (mode == M_ADD) { 624254721Semaste edited = 1; /* Always */ 625254721Semaste rc = addpwent(pwd); 626254721Semaste if (rc == -1) 627254721Semaste errx(EX_IOERR, "user '%s' already exists", 628254721Semaste pwd->pw_name); 629254721Semaste else if (rc != 0) 630254721Semaste err(EX_IOERR, "passwd file update"); 631254721Semaste if (cnf->nispasswd && *cnf->nispasswd=='/') { 632254721Semaste rc = addnispwent(cnf->nispasswd, pwd); 633254721Semaste if (rc == -1) 634254721Semaste warnx("User '%s' already exists in NIS passwd", pwd->pw_name); 635254721Semaste else 636254721Semaste warn("NIS passwd update"); 637254721Semaste /* NOTE: we treat NIS-only update errors as non-fatal */ 638254721Semaste } 639254721Semaste } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) { 640254721Semaste if (edited) { /* Only updated this if required */ 641254721Semaste rc = chgpwent(name, pwd); 642254721Semaste if (rc == -1) 643254721Semaste errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name); 644254721Semaste else if (rc != 0) 645254721Semaste err(EX_IOERR, "passwd file update"); 646254721Semaste if ( cnf->nispasswd && *cnf->nispasswd=='/') { 647254721Semaste rc = chgnispwent(cnf->nispasswd, name, pwd); 648254721Semaste if (rc == -1) 649254721Semaste warn("User '%s' not found in NIS passwd", pwd->pw_name); 650254721Semaste else 651254721Semaste warn("NIS passwd update"); 652254721Semaste /* NOTE: NIS-only update errors are not fatal */ 653254721Semaste } 654254721Semaste } 655254721Semaste } 656254721Semaste 657254721Semaste /* 658254721Semaste * Ok, user is created or changed - now edit group file 659254721Semaste */ 660254721Semaste 661254721Semaste if (mode == M_ADD || getarg(args, 'G') != NULL) { 662254721Semaste int i, j; 663254721Semaste /* First remove the user from all group */ 664254721Semaste SETGRENT(); 665254721Semaste while ((grp = GETGRENT()) != NULL) { 666254721Semaste char group[MAXLOGNAME]; 667254721Semaste if (grp->gr_mem == NULL) 668254721Semaste continue; 669254721Semaste for (i = 0; grp->gr_mem[i] != NULL; i++) { 670254721Semaste if (strcmp(grp->gr_mem[i] , pwd->pw_name) != 0) 671254721Semaste continue; 672254721Semaste for (j = i; grp->gr_mem[j] != NULL ; j++) 673254721Semaste grp->gr_mem[j] = grp->gr_mem[j+1]; 674254721Semaste strlcpy(group, grp->gr_name, MAXLOGNAME); 675254721Semaste chggrent(group, grp); 676254721Semaste } 677254721Semaste } 678254721Semaste ENDGRENT(); 679254721Semaste 680254721Semaste /* now add to group where needed */ 681254721Semaste for (i = 0; cnf->groups[i] != NULL; i++) { 682254721Semaste grp = GETGRNAM(cnf->groups[i]); 683254721Semaste grp = gr_add(grp, pwd->pw_name); 684254721Semaste /* 685254721Semaste * grp can only be NULL in 2 cases: 686254721Semaste * - the new member is already a member 687254721Semaste * - a problem with memory occurs 688254721Semaste * in both cases we want to skip now. 689254721Semaste */ 690254721Semaste if (grp == NULL) 691254721Semaste continue; 692254721Semaste chggrent(cnf->groups[i], grp); 693254721Semaste free(grp); 694254721Semaste } 695254721Semaste } 696254721Semaste 697254721Semaste 698254721Semaste /* go get a current version of pwd */ 699254721Semaste pwd = GETPWNAM(name); 700254721Semaste if (pwd == NULL) { 701254721Semaste /* This will fail when we rename, so special case that */ 702254721Semaste if (mode == M_UPDATE && conf.newname != NULL) { 703254721Semaste name = conf.newname; /* update new name */ 704254721Semaste pwd = GETPWNAM(name); /* refetch renamed rec */ 705254721Semaste } 706254721Semaste } 707254721Semaste if (pwd == NULL) /* can't go on without this */ 708254721Semaste errx(EX_NOUSER, "user '%s' disappeared during update", name); 709254721Semaste 710254721Semaste grp = GETGRGID(pwd->pw_gid); 711254721Semaste pw_log(cnf, mode, W_USER, "%s(%u):%s(%u):%s:%s:%s", 712254721Semaste pwd->pw_name, pwd->pw_uid, 713254721Semaste grp ? grp->gr_name : "unknown", (grp ? grp->gr_gid : (uid_t)-1), 714254721Semaste pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 715254721Semaste 716254721Semaste /* 717254721Semaste * If adding, let's touch and chown the user's mail file. This is not 718254721Semaste * strictly necessary under BSD with a 0755 maildir but it also 719254721Semaste * doesn't hurt anything to create the empty mailfile 720254721Semaste */ 721254721Semaste if (mode == M_ADD) { 722254721Semaste if (PWALTDIR() != PWF_ALT) { 723254721Semaste arg = getarg(args, 'R'); 724254721Semaste snprintf(path, sizeof(path), "%s%s/%s", 725254721Semaste arg ? arg->val : "", _PATH_MAILDIR, pwd->pw_name); 726254721Semaste close(open(path, O_RDWR | O_CREAT, 0600)); /* Preserve contents & 727254721Semaste * mtime */ 728254721Semaste chown(path, pwd->pw_uid, pwd->pw_gid); 729254721Semaste } 730254721Semaste } 731254721Semaste 732254721Semaste /* 733254721Semaste * Let's create and populate the user's home directory. Note 734254721Semaste * that this also `works' for editing users if -m is used, but 735254721Semaste * existing files will *not* be overwritten. 736254721Semaste */ 737254721Semaste if (PWALTDIR() != PWF_ALT && getarg(args, 'm') != NULL && pwd->pw_dir && 738254721Semaste *pwd->pw_dir == '/' && pwd->pw_dir[1]) 739254721Semaste create_and_populate_homedir(mode, pwd); 740254721Semaste 741254721Semaste /* 742254721Semaste * Finally, send mail to the new user as well, if we are asked to 743254721Semaste */ 744254721Semaste if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) { 745254721Semaste FILE *pfp = popen(_PATH_SENDMAIL " -t", "w"); 746254721Semaste 747254721Semaste if (pfp == NULL) 748254721Semaste warn("sendmail"); 749254721Semaste else { 750254721Semaste fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name); 751254721Semaste while (fgets(line, sizeof(line), fp) != NULL) { 752254721Semaste /* Do substitutions? */ 753254721Semaste fputs(line, pfp); 754254721Semaste } 755254721Semaste pclose(pfp); 756254721Semaste pw_log(cnf, mode, W_USER, "%s(%u) new user mail sent", 757254721Semaste pwd->pw_name, pwd->pw_uid); 758254721Semaste } 759254721Semaste fclose(fp); 760254721Semaste } 761254721Semaste 762254721Semaste return EXIT_SUCCESS; 763254721Semaste} 764254721Semaste 765254721Semaste 766254721Semastestatic uid_t 767254721Semastepw_uidpolicy(struct userconf * cnf, long id) 768254721Semaste{ 769254721Semaste struct passwd *pwd; 770254721Semaste uid_t uid = (uid_t) - 1; 771254721Semaste 772254721Semaste /* 773254721Semaste * Check the given uid, if any 774254721Semaste */ 775254721Semaste if (id > 0) { 776254721Semaste uid = (uid_t) id; 777254721Semaste 778254721Semaste if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate) 779254721Semaste errx(EX_DATAERR, "uid `%u' has already been allocated", pwd->pw_uid); 780254721Semaste } else { 781254721Semaste struct bitmap bm; 782254721Semaste 783254721Semaste /* 784254721Semaste * We need to allocate the next available uid under one of 785254721Semaste * two policies a) Grab the first unused uid b) Grab the 786254721Semaste * highest possible unused uid 787254721Semaste */ 788254721Semaste if (cnf->min_uid >= cnf->max_uid) { /* Sanity 789254721Semaste * claus^H^H^H^Hheck */ 790254721Semaste cnf->min_uid = 1000; 791254721Semaste cnf->max_uid = 32000; 792254721Semaste } 793254721Semaste bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); 794254721Semaste 795254721Semaste /* 796254721Semaste * Now, let's fill the bitmap from the password file 797254721Semaste */ 798254721Semaste SETPWENT(); 799254721Semaste while ((pwd = GETPWENT()) != NULL) 800254721Semaste if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) 801254721Semaste bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); 802254721Semaste ENDPWENT(); 803254721Semaste 804254721Semaste /* 805254721Semaste * Then apply the policy, with fallback to reuse if necessary 806254721Semaste */ 807254721Semaste if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) 808254721Semaste uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); 809254721Semaste 810254721Semaste /* 811254721Semaste * Another sanity check 812254721Semaste */ 813254721Semaste if (uid < cnf->min_uid || uid > cnf->max_uid) 814254721Semaste errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); 815254721Semaste bm_dealloc(&bm); 816254721Semaste } 817254721Semaste return uid; 818254721Semaste} 819254721Semaste 820254721Semaste 821254721Semastestatic uid_t 822254721Semastepw_gidpolicy(struct cargs * args, char *nam, gid_t prefer) 823254721Semaste{ 824254721Semaste struct group *grp; 825254721Semaste gid_t gid = (uid_t) - 1; 826254721Semaste struct carg *a_gid = getarg(args, 'g'); 827254721Semaste struct userconf *cnf = conf.userconf; 828254721Semaste 829254721Semaste /* 830254721Semaste * If no arg given, see if default can help out 831254721Semaste */ 832254721Semaste if (a_gid == NULL && cnf->default_group && *cnf->default_group) 833254721Semaste a_gid = addarg(args, 'g', cnf->default_group); 834254721Semaste 835254721Semaste /* 836254721Semaste * Check the given gid, if any 837254721Semaste */ 838254721Semaste SETGRENT(); 839254721Semaste if (a_gid != NULL) { 840254721Semaste if ((grp = GETGRNAM(a_gid->val)) == NULL) { 841254721Semaste gid = (gid_t) atol(a_gid->val); 842254721Semaste if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL) 843254721Semaste errx(EX_NOUSER, "group `%s' is not defined", a_gid->val); 844254721Semaste } 845254721Semaste gid = grp->gr_gid; 846254721Semaste } else if ((grp = GETGRNAM(nam)) != NULL && 847254721Semaste (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { 848254721Semaste gid = grp->gr_gid; /* Already created? Use it anyway... */ 849254721Semaste } else { 850254721Semaste struct cargs grpargs; 851254721Semaste char tmp[32]; 852254721Semaste 853254721Semaste LIST_INIT(&grpargs); 854254721Semaste 855254721Semaste /* 856254721Semaste * We need to auto-create a group with the user's name. We 857254721Semaste * can send all the appropriate output to our sister routine 858254721Semaste * bit first see if we can create a group with gid==uid so we 859254721Semaste * can keep the user and group ids in sync. We purposely do 860254721Semaste * NOT check the gid range if we can force the sync. If the 861254721Semaste * user's name dups an existing group, then the group add 862254721Semaste * function will happily handle that case for us and exit. 863254721Semaste */ 864254721Semaste if (GETGRGID(prefer) == NULL) { 865254721Semaste snprintf(tmp, sizeof(tmp), "%u", prefer); 866254721Semaste addarg(&grpargs, 'g', tmp); 867254721Semaste } 868254721Semaste if (conf.dryrun) { 869254721Semaste gid = pw_groupnext(cnf, true); 870254721Semaste } else { 871254721Semaste pw_group(M_ADD, nam, -1, &grpargs); 872254721Semaste if ((grp = GETGRNAM(nam)) != NULL) 873254721Semaste gid = grp->gr_gid; 874254721Semaste } 875254721Semaste a_gid = LIST_FIRST(&grpargs); 876254721Semaste while (a_gid != NULL) { 877254721Semaste struct carg *t = LIST_NEXT(a_gid, list); 878254721Semaste LIST_REMOVE(a_gid, list); 879254721Semaste a_gid = t; 880254721Semaste } 881254721Semaste } 882254721Semaste ENDGRENT(); 883254721Semaste return gid; 884254721Semaste} 885254721Semaste 886254721Semaste 887254721Semastestatic time_t 888254721Semastepw_pwdpolicy(struct userconf * cnf, struct cargs * args) 889254721Semaste{ 890254721Semaste time_t result = 0; 891254721Semaste time_t now = time(NULL); 892254721Semaste struct carg *arg = getarg(args, 'p'); 893254721Semaste 894254721Semaste if (arg != NULL) { 895254721Semaste if ((result = parse_date(now, arg->val)) == now) 896254721Semaste errx(EX_DATAERR, "invalid date/time `%s'", arg->val); 897254721Semaste } else if (cnf->password_days > 0) 898254721Semaste result = now + ((long) cnf->password_days * 86400L); 899254721Semaste return result; 900254721Semaste} 901254721Semaste 902254721Semaste 903254721Semastestatic time_t 904254721Semastepw_exppolicy(struct userconf * cnf, struct cargs * args) 905254721Semaste{ 906254721Semaste time_t result = 0; 907254721Semaste time_t now = time(NULL); 908254721Semaste struct carg *arg = getarg(args, 'e'); 909254721Semaste 910254721Semaste if (arg != NULL) { 911254721Semaste if ((result = parse_date(now, arg->val)) == now) 912254721Semaste errx(EX_DATAERR, "invalid date/time `%s'", arg->val); 913254721Semaste } else if (cnf->expire_days > 0) 914254721Semaste result = now + ((long) cnf->expire_days * 86400L); 915254721Semaste return result; 916254721Semaste} 917254721Semaste 918254721Semaste 919254721Semastestatic char * 920254721Semastepw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user) 921254721Semaste{ 922254721Semaste struct carg *arg = getarg(args, 'd'); 923254721Semaste static char home[128]; 924254721Semaste 925254721Semaste if (arg) 926254721Semaste return (arg->val); 927254721Semaste 928254721Semaste if (cnf->home == NULL || *cnf->home == '\0') 929254721Semaste errx(EX_CONFIG, "no base home directory set"); 930254721Semaste snprintf(home, sizeof(home), "%s/%s", cnf->home, user); 931254721Semaste 932254721Semaste return (home); 933254721Semaste} 934254721Semaste 935254721Semastestatic char * 936254721Semasteshell_path(char const * path, char *shells[], char *sh) 937254721Semaste{ 938254721Semaste if (sh != NULL && (*sh == '/' || *sh == '\0')) 939254721Semaste return sh; /* specified full path or forced none */ 940254721Semaste else { 941254721Semaste char *p; 942254721Semaste char paths[_UC_MAXLINE]; 943254721Semaste 944254721Semaste /* 945254721Semaste * We need to search paths 946254721Semaste */ 947254721Semaste strlcpy(paths, path, sizeof(paths)); 948254721Semaste for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { 949254721Semaste int i; 950254721Semaste static char shellpath[256]; 951254721Semaste 952254721Semaste if (sh != NULL) { 953254721Semaste snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh); 954254721Semaste if (access(shellpath, X_OK) == 0) 955254721Semaste return shellpath; 956254721Semaste } else 957254721Semaste for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { 958254721Semaste snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]); 959254721Semaste if (access(shellpath, X_OK) == 0) 960254721Semaste return shellpath; 961254721Semaste } 962254721Semaste } 963254721Semaste if (sh == NULL) 964254721Semaste errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); 965254721Semaste errx(EX_CONFIG, "no default shell available or defined"); 966254721Semaste return NULL; 967254721Semaste } 968254721Semaste} 969254721Semaste 970254721Semaste 971254721Semastestatic char * 972254721Semastepw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell) 973254721Semaste{ 974254721Semaste char *sh = newshell; 975254721Semaste struct carg *arg = getarg(args, 's'); 976254721Semaste 977254721Semaste if (newshell == NULL && arg != NULL) 978254721Semaste sh = arg->val; 979254721Semaste return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default); 980254721Semaste} 981254721Semaste 982254721Semaste#define SALTSIZE 32 983254721Semaste 984254721Semastestatic char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; 985254721Semaste 986254721Semastechar * 987254721Semastepw_pwcrypt(char *password) 988254721Semaste{ 989254721Semaste int i; 990254721Semaste char salt[SALTSIZE + 1]; 991254721Semaste char *cryptpw; 992254721Semaste 993254721Semaste static char buf[256]; 994254721Semaste 995254721Semaste /* 996254721Semaste * Calculate a salt value 997254721Semaste */ 998254721Semaste for (i = 0; i < SALTSIZE; i++) 999254721Semaste salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; 1000254721Semaste salt[SALTSIZE] = '\0'; 1001254721Semaste 1002254721Semaste cryptpw = crypt(password, salt); 1003254721Semaste if (cryptpw == NULL) 1004254721Semaste errx(EX_CONFIG, "crypt(3) failure"); 1005254721Semaste return strcpy(buf, cryptpw); 1006254721Semaste} 1007254721Semaste 1008254721Semaste 1009254721Semastestatic char * 1010254721Semastepw_password(struct userconf * cnf, struct cargs * args, char const * user) 1011254721Semaste{ 1012254721Semaste int i, l; 1013254721Semaste char pwbuf[32]; 1014254721Semaste 1015254721Semaste switch (cnf->default_password) { 1016254721Semaste case -1: /* Random password */ 1017254721Semaste l = (arc4random() % 8 + 8); /* 8 - 16 chars */ 1018254721Semaste for (i = 0; i < l; i++) 1019254721Semaste pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; 1020254721Semaste pwbuf[i] = '\0'; 1021254721Semaste 1022254721Semaste /* 1023254721Semaste * We give this information back to the user 1024254721Semaste */ 1025254721Semaste if (conf.fd == -1 && !conf.dryrun) { 1026254721Semaste if (isatty(STDOUT_FILENO)) 1027254721Semaste printf("Password for '%s' is: ", user); 1028254721Semaste printf("%s\n", pwbuf); 1029254721Semaste fflush(stdout); 1030254721Semaste } 1031254721Semaste break; 1032254721Semaste 1033254721Semaste case -2: /* No password at all! */ 1034254721Semaste return ""; 1035254721Semaste 1036254721Semaste case 0: /* No login - default */ 1037254721Semaste default: 1038254721Semaste return "*"; 1039254721Semaste 1040254721Semaste case 1: /* user's name */ 1041254721Semaste strlcpy(pwbuf, user, sizeof(pwbuf)); 1042254721Semaste break; 1043254721Semaste } 1044254721Semaste return pw_pwcrypt(pwbuf); 1045254721Semaste} 1046254721Semaste 1047254721Semastestatic int 1048254721Semastedelete_user(struct userconf *cnf, struct passwd *pwd, char *name, 1049254721Semaste int delete, int mode) 1050254721Semaste{ 1051254721Semaste char file[MAXPATHLEN]; 1052254721Semaste char home[MAXPATHLEN]; 1053254721Semaste uid_t uid = pwd->pw_uid; 1054254721Semaste struct group *gr, *grp; 1055254721Semaste char grname[LOGNAMESIZE]; 1056254721Semaste int rc; 1057254721Semaste struct stat st; 1058254721Semaste 1059254721Semaste if (strcmp(pwd->pw_name, "root") == 0) 1060254721Semaste errx(EX_DATAERR, "cannot remove user 'root'"); 1061254721Semaste 1062254721Semaste if (!PWALTDIR()) { 1063254721Semaste /* 1064254721Semaste * Remove opie record from /etc/opiekeys 1065254721Semaste */ 1066254721Semaste 1067254721Semaste rmopie(pwd->pw_name); 1068254721Semaste 1069254721Semaste /* 1070254721Semaste * Remove crontabs 1071254721Semaste */ 1072254721Semaste snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); 1073254721Semaste if (access(file, F_OK) == 0) { 1074254721Semaste snprintf(file, sizeof(file), "crontab -u %s -r", pwd->pw_name); 1075254721Semaste system(file); 1076254721Semaste } 1077254721Semaste } 1078254721Semaste /* 1079254721Semaste * Save these for later, since contents of pwd may be 1080254721Semaste * invalidated by deletion 1081254721Semaste */ 1082254721Semaste snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name); 1083254721Semaste strlcpy(home, pwd->pw_dir, sizeof(home)); 1084254721Semaste gr = GETGRGID(pwd->pw_gid); 1085254721Semaste if (gr != NULL) 1086254721Semaste strlcpy(grname, gr->gr_name, LOGNAMESIZE); 1087254721Semaste else 1088254721Semaste grname[0] = '\0'; 1089254721Semaste 1090254721Semaste rc = delpwent(pwd); 1091254721Semaste if (rc == -1) 1092254721Semaste err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); 1093254721Semaste else if (rc != 0) 1094254721Semaste err(EX_IOERR, "passwd update"); 1095254721Semaste 1096254721Semaste if (cnf->nispasswd && *cnf->nispasswd=='/') { 1097254721Semaste rc = delnispwent(cnf->nispasswd, name); 1098254721Semaste if (rc == -1) 1099254721Semaste warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name); 1100254721Semaste else if (rc != 0) 1101254721Semaste warn("WARNING: NIS passwd update"); 1102254721Semaste /* non-fatal */ 1103254721Semaste } 1104254721Semaste 1105254721Semaste grp = GETGRNAM(name); 1106254721Semaste if (grp != NULL && 1107254721Semaste (grp->gr_mem == NULL || *grp->gr_mem == NULL) && 1108254721Semaste strcmp(name, grname) == 0) 1109254721Semaste delgrent(GETGRNAM(name)); 1110254721Semaste SETGRENT(); 1111254721Semaste while ((grp = GETGRENT()) != NULL) { 1112254721Semaste int i, j; 1113254721Semaste char group[MAXLOGNAME]; 1114254721Semaste if (grp->gr_mem == NULL) 1115254721Semaste continue; 1116254721Semaste 1117254721Semaste for (i = 0; grp->gr_mem[i] != NULL; i++) { 1118254721Semaste if (strcmp(grp->gr_mem[i], name) != 0) 1119254721Semaste continue; 1120254721Semaste 1121254721Semaste for (j = i; grp->gr_mem[j] != NULL; j++) 1122254721Semaste grp->gr_mem[j] = grp->gr_mem[j+1]; 1123254721Semaste strlcpy(group, grp->gr_name, MAXLOGNAME); 1124254721Semaste chggrent(group, grp); 1125254721Semaste } 1126254721Semaste } 1127254721Semaste ENDGRENT(); 1128254721Semaste 1129254721Semaste pw_log(cnf, mode, W_USER, "%s(%u) account removed", name, uid); 1130254721Semaste 1131254721Semaste if (!PWALTDIR()) { 1132254721Semaste /* 1133254721Semaste * Remove mail file 1134254721Semaste */ 1135254721Semaste remove(file); 1136254721Semaste 1137254721Semaste /* 1138254721Semaste * Remove at jobs 1139254721Semaste */ 1140254721Semaste if (getpwuid(uid) == NULL) 1141254721Semaste rmat(uid); 1142254721Semaste 1143254721Semaste /* 1144254721Semaste * Remove home directory and contents 1145254721Semaste */ 1146254721Semaste if (delete && *home == '/' && getpwuid(uid) == NULL && 1147254721Semaste stat(home, &st) != -1) { 1148254721Semaste rm_r(home, uid); 1149254721Semaste pw_log(cnf, mode, W_USER, "%s(%u) home '%s' %sremoved", 1150254721Semaste name, uid, home, 1151254721Semaste stat(home, &st) == -1 ? "" : "not completely "); 1152254721Semaste } 1153254721Semaste } 1154254721Semaste 1155269024Semaste return (EXIT_SUCCESS); 1156269024Semaste} 1157269024Semaste 1158254721Semastestatic int 1159254721Semasteprint_user(struct passwd * pwd) 1160254721Semaste{ 1161254721Semaste if (!conf.pretty) { 1162254721Semaste char *buf; 1163269024Semaste 1164254721Semaste buf = conf.v7 ? pw_make_v7(pwd) : pw_make(pwd); 1165269024Semaste printf("%s\n", buf); 1166269024Semaste free(buf); 1167269024Semaste } else { 1168254721Semaste int j; 1169254721Semaste char *p; 1170254721Semaste struct group *grp = GETGRGID(pwd->pw_gid); 1171254721Semaste char uname[60] = "User &", office[60] = "[None]", 1172254721Semaste wphone[60] = "[None]", hphone[60] = "[None]"; 1173254721Semaste char acexpire[32] = "[None]", pwexpire[32] = "[None]"; 1174254721Semaste struct tm * tptr; 1175254721Semaste 1176254721Semaste if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { 1177254721Semaste strlcpy(uname, p, sizeof(uname)); 1178254721Semaste if ((p = strtok(NULL, ",")) != NULL) { 1179254721Semaste strlcpy(office, p, sizeof(office)); 1180254721Semaste if ((p = strtok(NULL, ",")) != NULL) { 1181254721Semaste strlcpy(wphone, p, sizeof(wphone)); 1182254721Semaste if ((p = strtok(NULL, "")) != NULL) { 1183254721Semaste strlcpy(hphone, p, 1184254721Semaste sizeof(hphone)); 1185254721Semaste } 1186254721Semaste } 1187254721Semaste } 1188254721Semaste } 1189254721Semaste /* 1190254721Semaste * Handle '&' in gecos field 1191254721Semaste */ 1192254721Semaste if ((p = strchr(uname, '&')) != NULL) { 1193254721Semaste int l = strlen(pwd->pw_name); 1194254721Semaste int m = strlen(p); 1195254721Semaste 1196254721Semaste memmove(p + l, p + 1, m); 1197254721Semaste memmove(p, pwd->pw_name, l); 1198254721Semaste *p = (char) toupper((unsigned char)*p); 1199254721Semaste } 1200254721Semaste if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) 1201254721Semaste strftime(acexpire, sizeof acexpire, "%c", tptr); 1202254721Semaste if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) 1203254721Semaste strftime(pwexpire, sizeof pwexpire, "%c", tptr); 1204254721Semaste printf("Login Name: %-15s #%-12u Group: %-15s #%u\n" 1205254721Semaste " Full Name: %s\n" 1206254721Semaste " Home: %-26.26s Class: %s\n" 1207254721Semaste " Shell: %-26.26s Office: %s\n" 1208254721Semaste "Work Phone: %-26.26s Home Phone: %s\n" 1209254721Semaste "Acc Expire: %-26.26s Pwd Expire: %s\n", 1210254721Semaste pwd->pw_name, pwd->pw_uid, 1211254721Semaste grp ? grp->gr_name : "(invalid)", pwd->pw_gid, 1212254721Semaste uname, pwd->pw_dir, pwd->pw_class, 1213254721Semaste pwd->pw_shell, office, wphone, hphone, 1214254721Semaste acexpire, pwexpire); 1215254721Semaste SETGRENT(); 1216254721Semaste j = 0; 1217254721Semaste while ((grp=GETGRENT()) != NULL) 1218254721Semaste { 1219254721Semaste int i = 0; 1220254721Semaste if (grp->gr_mem != NULL) { 1221254721Semaste while (grp->gr_mem[i] != NULL) 1222254721Semaste { 1223254721Semaste if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) 1224254721Semaste { 1225254721Semaste printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); 1226254721Semaste break; 1227254721Semaste } 1228254721Semaste ++i; 1229254721Semaste } 1230254721Semaste } 1231254721Semaste } 1232254721Semaste ENDGRENT(); 1233254721Semaste printf("%s", j ? "\n" : ""); 1234254721Semaste } 1235254721Semaste return EXIT_SUCCESS; 1236254721Semaste} 1237254721Semaste 1238254721Semastechar * 1239254721Semastepw_checkname(char *name, int gecos) 1240254721Semaste{ 1241254721Semaste char showch[8]; 1242254721Semaste const char *badchars, *ch, *showtype; 1243254721Semaste int reject; 1244254721Semaste 1245254721Semaste ch = name; 1246254721Semaste reject = 0; 1247254721Semaste if (gecos) { 1248254721Semaste /* See if the name is valid as a gecos (comment) field. */ 1249254721Semaste badchars = ":!@"; 1250254721Semaste showtype = "gecos field"; 1251254721Semaste } else { 1252254721Semaste /* See if the name is valid as a userid or group. */ 1253254721Semaste badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; 1254254721Semaste showtype = "userid/group name"; 1255254721Semaste /* Userids and groups can not have a leading '-'. */ 1256254721Semaste if (*ch == '-') 1257254721Semaste reject = 1; 1258254721Semaste } 1259254721Semaste if (!reject) { 1260254721Semaste while (*ch) { 1261254721Semaste if (strchr(badchars, *ch) != NULL || *ch < ' ' || 1262254721Semaste *ch == 127) { 1263254721Semaste reject = 1; 1264254721Semaste break; 1265254721Semaste } 1266254721Semaste /* 8-bit characters are only allowed in GECOS fields */ 1267254721Semaste if (!gecos && (*ch & 0x80)) { 1268254721Semaste reject = 1; 1269254721Semaste break; 1270254721Semaste } 1271254721Semaste ch++; 1272254721Semaste } 1273254721Semaste } 1274254721Semaste /* 1275254721Semaste * A `$' is allowed as the final character for userids and groups, 1276254721Semaste * mainly for the benefit of samba. 1277254721Semaste */ 1278254721Semaste if (reject && !gecos) { 1279254721Semaste if (*ch == '$' && *(ch + 1) == '\0') { 1280254721Semaste reject = 0; 1281254721Semaste ch++; 1282254721Semaste } 1283254721Semaste } 1284254721Semaste if (reject) { 1285254721Semaste snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) 1286254721Semaste ? "`%c'" : "0x%02x", *ch); 1287254721Semaste errx(EX_DATAERR, "invalid character %s at position %td in %s", 1288254721Semaste showch, (ch - name), showtype); 1289254721Semaste } 1290254721Semaste if (!gecos && (ch - name) > LOGNAMESIZE) 1291254721Semaste errx(EX_DATAERR, "name too long `%s' (max is %d)", name, 1292254721Semaste LOGNAMESIZE); 1293254721Semaste 1294254721Semaste return (name); 1295254721Semaste} 1296254721Semaste 1297254721Semaste 1298254721Semastestatic void 1299254721Semastermat(uid_t uid) 1300254721Semaste{ 1301254721Semaste DIR *d = opendir("/var/at/jobs"); 1302254721Semaste 1303254721Semaste if (d != NULL) { 1304254721Semaste struct dirent *e; 1305254721Semaste 1306254721Semaste while ((e = readdir(d)) != NULL) { 1307254721Semaste struct stat st; 1308254721Semaste 1309254721Semaste if (strncmp(e->d_name, ".lock", 5) != 0 && 1310254721Semaste stat(e->d_name, &st) == 0 && 1311254721Semaste !S_ISDIR(st.st_mode) && 1312254721Semaste st.st_uid == uid) { 1313254721Semaste char tmp[MAXPATHLEN]; 1314254721Semaste 1315254721Semaste snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", e->d_name); 1316254721Semaste system(tmp); 1317254721Semaste } 1318254721Semaste } 1319254721Semaste closedir(d); 1320254721Semaste } 1321254721Semaste} 1322254721Semaste 1323254721Semastestatic void 1324254721Semastermopie(char const * name) 1325254721Semaste{ 1326254721Semaste static const char etcopie[] = "/etc/opiekeys"; 1327254721Semaste FILE *fp = fopen(etcopie, "r+"); 1328254721Semaste 1329254721Semaste if (fp != NULL) { 1330254721Semaste char tmp[1024]; 1331254721Semaste off_t atofs = 0; 1332254721Semaste int length = strlen(name); 1333254721Semaste 1334254721Semaste while (fgets(tmp, sizeof tmp, fp) != NULL) { 1335254721Semaste if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') { 1336254721Semaste if (fseek(fp, atofs, SEEK_SET) == 0) { 1337254721Semaste fwrite("#", 1, 1, fp); /* Comment username out */ 1338254721Semaste } 1339254721Semaste break; 1340254721Semaste } 1341254721Semaste atofs = ftell(fp); 1342254721Semaste } 1343254721Semaste /* 1344254721Semaste * If we got an error of any sort, don't update! 1345254721Semaste */ 1346254721Semaste fclose(fp); 1347254721Semaste } 1348254721Semaste} 1349254721Semaste 1350254721Semaste