120253Sjoerg/*- 220302Sjoerg * Copyright (C) 1996 320302Sjoerg * David L. Nugent. All rights reserved. 420253Sjoerg * 520253Sjoerg * Redistribution and use in source and binary forms, with or without 620253Sjoerg * modification, are permitted provided that the following conditions 720253Sjoerg * are met: 820253Sjoerg * 1. Redistributions of source code must retain the above copyright 920302Sjoerg * notice, this list of conditions and the following disclaimer. 1020253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright 1120253Sjoerg * notice, this list of conditions and the following disclaimer in the 1220253Sjoerg * documentation and/or other materials provided with the distribution. 1320253Sjoerg * 1420302Sjoerg * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 1520253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1620253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1720302Sjoerg * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 1820253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1920253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2020253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2120253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2220253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2320253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2420253Sjoerg * SUCH DAMAGE. 2544229Sdavidn * 2620253Sjoerg */ 2720253Sjoerg 2830259Scharnier#ifndef lint 2930259Scharnierstatic const char rcsid[] = 3050479Speter "$FreeBSD: stable/10/usr.sbin/pw/pw_user.c 360326 2020-04-25 22:23:34Z dim $"; 3130259Scharnier#endif /* not lint */ 3230259Scharnier 33287084Sbapt#include <sys/param.h> 34287084Sbapt#include <sys/resource.h> 35287084Sbapt#include <sys/time.h> 36287084Sbapt#include <sys/types.h> 37287084Sbapt 38319267Sasomers#include <assert.h> 3930259Scharnier#include <ctype.h> 40287084Sbapt#include <dirent.h> 4130259Scharnier#include <err.h> 42287084Sbapt#include <errno.h> 4320253Sjoerg#include <fcntl.h> 44287084Sbapt#include <grp.h> 45287084Sbapt#include <pwd.h> 46287084Sbapt#include <libutil.h> 47287084Sbapt#include <login_cap.h> 4830259Scharnier#include <paths.h> 49287084Sbapt#include <string.h> 50287084Sbapt#include <sysexits.h> 5120253Sjoerg#include <termios.h> 52287084Sbapt#include <unistd.h> 53287084Sbapt 5420253Sjoerg#include "pw.h" 5520253Sjoerg#include "bitmap.h" 56287084Sbapt#include "psdate.h" 5720253Sjoerg 5823318Sache#define LOGNAMESIZE (MAXLOGNAME-1) 5922394Sdavidn 6052512Sdavidnstatic char locked_str[] = "*LOCKED*"; 6124214Sache 62287084Sbaptstatic struct passwd fakeuser = { 63287084Sbapt "nouser", 64287084Sbapt "*", 65287084Sbapt -1, 66287084Sbapt -1, 67287084Sbapt 0, 68287084Sbapt "", 69287084Sbapt "User &", 70287084Sbapt "/nonexistent", 71287084Sbapt "/bin/sh", 72287084Sbapt 0, 73287084Sbapt 0 74287084Sbapt}; 7520253Sjoerg 76287084Sbaptstatic int print_user(struct passwd *pwd, bool pretty, bool v7); 77287084Sbaptstatic uid_t pw_uidpolicy(struct userconf *cnf, intmax_t id); 78287084Sbaptstatic uid_t pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, 79287084Sbapt gid_t prefer, bool dryrun); 80287084Sbaptstatic char *pw_homepolicy(struct userconf * cnf, char *homedir, 81287084Sbapt const char *user); 82287084Sbaptstatic char *pw_shellpolicy(struct userconf * cnf); 83287084Sbaptstatic char *pw_password(struct userconf * cnf, char const * user, 84287084Sbapt bool dryrun); 85287084Sbaptstatic char *shell_path(char const * path, char *shells[], char *sh); 86287084Sbaptstatic void rmat(uid_t uid); 87287084Sbaptstatic void rmopie(char const * name); 88287084Sbapt 89285092Sbaptstatic void 90287084Sbaptmkdir_home_parents(int dfd, const char *dir) 91285092Sbapt{ 92287084Sbapt struct stat st; 93287084Sbapt char *dirs, *tmp; 94285092Sbapt 95287084Sbapt if (*dir != '/') 96287084Sbapt errx(EX_DATAERR, "invalid base directory for home '%s'", dir); 97285092Sbapt 98287084Sbapt dir++; 99285092Sbapt 100287084Sbapt if (fstatat(dfd, dir, &st, 0) != -1) { 101287084Sbapt if (S_ISDIR(st.st_mode)) 102287084Sbapt return; 103287084Sbapt errx(EX_OSFILE, "root home `/%s' is not a directory", dir); 10420267Sjoerg } 10520267Sjoerg 106287084Sbapt dirs = strdup(dir); 107287084Sbapt if (dirs == NULL) 108287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 10920253Sjoerg 110287084Sbapt tmp = strrchr(dirs, '/'); 111293684Sbapt if (tmp == NULL) { 112293684Sbapt free(dirs); 113287084Sbapt return; 114293684Sbapt } 115287084Sbapt tmp[0] = '\0'; 11621052Sdavidn 11721052Sdavidn /* 118287084Sbapt * This is a kludge especially for Joerg :) 119287084Sbapt * If the home directory would be created in the root partition, then 120287084Sbapt * we really create it under /usr which is likely to have more space. 121287084Sbapt * But we create a symlink from cnf->home -> "/usr" -> cnf->home 12221052Sdavidn */ 123287084Sbapt if (strchr(dirs, '/') == NULL) { 124287084Sbapt asprintf(&tmp, "usr/%s", dirs); 125287084Sbapt if (tmp == NULL) 126287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 127287084Sbapt if (mkdirat(dfd, tmp, _DEF_DIRMODE) != -1 || errno == EEXIST) { 128287084Sbapt fchownat(dfd, tmp, 0, 0, 0); 129287084Sbapt symlinkat(tmp, dfd, dirs); 13020253Sjoerg } 131287084Sbapt free(tmp); 13220253Sjoerg } 133287084Sbapt tmp = dirs; 134287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 135287084Sbapt while ((tmp = strchr(tmp + 1, '/')) != NULL) { 136287084Sbapt *tmp = '\0'; 137287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 138287084Sbapt if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) 139287084Sbapt err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); 14020253Sjoerg } 141287084Sbapt *tmp = '/'; 14220253Sjoerg } 14320253Sjoerg } 144287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 145287084Sbapt if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) 146287084Sbapt err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); 147287084Sbapt fchownat(dfd, dirs, 0, 0, 0); 14820253Sjoerg } 14952527Sdavidn 150287084Sbapt free(dirs); 151287084Sbapt} 15220253Sjoerg 153287084Sbaptstatic void 154287084Sbaptcreate_and_populate_homedir(struct userconf *cnf, struct passwd *pwd, 155287084Sbapt const char *skeldir, mode_t homemode, bool update) 156287084Sbapt{ 157287084Sbapt int skelfd = -1; 15820253Sjoerg 159287084Sbapt /* Create home parents directories */ 160287084Sbapt mkdir_home_parents(conf.rootfd, pwd->pw_dir); 16152527Sdavidn 162287084Sbapt if (skeldir != NULL && *skeldir != '\0') { 163287084Sbapt if (*skeldir == '/') 164287084Sbapt skeldir++; 165287084Sbapt skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC); 16620253Sjoerg } 16752527Sdavidn 168287084Sbapt copymkdir(conf.rootfd, pwd->pw_dir, skelfd, homemode, pwd->pw_uid, 169287084Sbapt pwd->pw_gid, 0); 170287084Sbapt pw_log(cnf, update ? M_UPDATE : M_ADD, W_USER, "%s(%ju) home %s made", 171287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, pwd->pw_dir); 172287084Sbapt} 17320253Sjoerg 174287084Sbaptstatic int 175287084Sbaptpw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update) 176287084Sbapt{ 177287084Sbapt int b, istty; 178287084Sbapt struct termios t, n; 179287084Sbapt login_cap_t *lc; 180287084Sbapt char line[_PASSWORD_LEN+1]; 181287084Sbapt char *p; 18220253Sjoerg 183287084Sbapt if (fd == '-') { 184287084Sbapt if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { 185287084Sbapt pwd->pw_passwd = "*"; /* No access */ 186287084Sbapt return (1); 18720253Sjoerg } 188287084Sbapt return (0); 189287084Sbapt } 19052527Sdavidn 191287084Sbapt if ((istty = isatty(fd))) { 192287084Sbapt if (tcgetattr(fd, &t) == -1) 193287084Sbapt istty = 0; 194287084Sbapt else { 195287084Sbapt n = t; 196287084Sbapt n.c_lflag &= ~(ECHO); 197287084Sbapt tcsetattr(fd, TCSANOW, &n); 198287084Sbapt printf("%s%spassword for user %s:", 199287084Sbapt update ? "new " : "", 200287084Sbapt precrypted ? "encrypted " : "", 201287084Sbapt pwd->pw_name); 202287084Sbapt fflush(stdout); 20320253Sjoerg } 204287084Sbapt } 205287084Sbapt b = read(fd, line, sizeof(line) - 1); 206287084Sbapt if (istty) { /* Restore state */ 207287084Sbapt tcsetattr(fd, TCSANOW, &t); 208287084Sbapt fputc('\n', stdout); 209287084Sbapt fflush(stdout); 210287084Sbapt } 21152527Sdavidn 212287084Sbapt if (b < 0) 213287084Sbapt err(EX_IOERR, "-%c file descriptor", 214287084Sbapt precrypted ? 'H' : 'h'); 215287084Sbapt line[b] = '\0'; 216287084Sbapt if ((p = strpbrk(line, "\r\n")) != NULL) 217287084Sbapt *p = '\0'; 218287084Sbapt if (!*line) 219287084Sbapt errx(EX_DATAERR, "empty password read on file descriptor %d", 220287084Sbapt fd); 221287084Sbapt if (precrypted) { 222287084Sbapt if (strchr(line, ':') != NULL) 223287084Sbapt errx(EX_DATAERR, "bad encrypted password"); 224287084Sbapt pwd->pw_passwd = strdup(line); 22520253Sjoerg } else { 22664918Sgreen lc = login_getpwclass(pwd); 227287084Sbapt if (lc == NULL || 228287084Sbapt login_setcryptfmt(lc, "sha512", NULL) == NULL) 22964918Sgreen warn("setting crypt(3) format"); 23064918Sgreen login_close(lc); 231287084Sbapt pwd->pw_passwd = pw_pwcrypt(line); 23220253Sjoerg } 233287084Sbapt return (1); 234287084Sbapt} 23520253Sjoerg 236287084Sbaptstatic void 237287084Sbaptperform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) 238287084Sbapt{ 239287084Sbapt int rc; 240287084Sbapt struct passwd *nispwd; 24120253Sjoerg 242287084Sbapt /* duplicate for nis so that chgpwent is not modifying before NIS */ 243287084Sbapt if (nispasswd && *nispasswd == '/') 244287084Sbapt nispwd = pw_dup(pwd); 24520253Sjoerg 246287084Sbapt rc = chgpwent(name, pwd); 247287084Sbapt if (rc == -1) 248287084Sbapt errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name); 249287084Sbapt else if (rc != 0) 250287084Sbapt err(EX_IOERR, "passwd file update"); 25120253Sjoerg 252287084Sbapt if (nispasswd && *nispasswd == '/') { 253287084Sbapt rc = chgnispwent(nispasswd, name, nispwd); 254285092Sbapt if (rc == -1) 255287084Sbapt warn("User '%s' not found in NIS passwd", pwd->pw_name); 256285092Sbapt else if (rc != 0) 257287084Sbapt warn("NIS passwd update"); 258287084Sbapt /* NOTE: NIS-only update errors are not fatal */ 25921330Sdavidn } 260287084Sbapt} 26121330Sdavidn 262287084Sbapt/* 263287084Sbapt * The M_LOCK and M_UNLOCK functions simply add or remove 264287084Sbapt * a "*LOCKED*" prefix from in front of the password to 265287084Sbapt * prevent it decoding correctly, and therefore prevents 266287084Sbapt * access. Of course, this only prevents access via 267287084Sbapt * password authentication (not ssh, kerberos or any 268287084Sbapt * other method that does not use the UNIX password) but 269287084Sbapt * that is a known limitation. 270287084Sbapt */ 271287084Sbaptstatic int 272287084Sbaptpw_userlock(char *arg1, int mode) 273287084Sbapt{ 274287084Sbapt struct passwd *pwd = NULL; 275287084Sbapt char *passtmp = NULL; 276287084Sbapt char *name; 277287084Sbapt bool locked = false; 278292025Sbapt uid_t id = (uid_t)-1; 27920253Sjoerg 280287084Sbapt if (geteuid() != 0) 281287084Sbapt errx(EX_NOPERM, "you must be root"); 282274082Sbapt 283287084Sbapt if (arg1 == NULL) 284287084Sbapt errx(EX_DATAERR, "username or id required"); 285242349Sbapt 286292025Sbapt name = arg1; 287292025Sbapt if (arg1[strspn(name, "0123456789")] == '\0') 288292025Sbapt id = pw_checkid(name, UID_MAX); 289242349Sbapt 290292025Sbapt pwd = GETPWNAM(pw_checkname(name, 0)); 291292025Sbapt if (pwd == NULL && id != (uid_t)-1) { 292292025Sbapt pwd = GETPWUID(id); 293292025Sbapt if (pwd != NULL) 294292025Sbapt name = pwd->pw_name; 295292025Sbapt } 29661759Sdavidn if (pwd == NULL) { 297292025Sbapt if (id == (uid_t)-1) 298292025Sbapt errx(EX_NOUSER, "no such name or uid `%ju'", (uintmax_t) id); 299287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 30061759Sdavidn } 30120253Sjoerg 302287084Sbapt if (name == NULL) 303287084Sbapt name = pwd->pw_name; 30420253Sjoerg 305287084Sbapt if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str) -1) == 0) 306287084Sbapt locked = true; 307287084Sbapt if (mode == M_LOCK && locked) 308287084Sbapt errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); 309287084Sbapt if (mode == M_UNLOCK && !locked) 310287084Sbapt errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); 311287084Sbapt 312287084Sbapt if (mode == M_LOCK) { 313287084Sbapt asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd); 314287084Sbapt if (passtmp == NULL) /* disaster */ 315287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 316287084Sbapt pwd->pw_passwd = passtmp; 317287084Sbapt } else { 318287084Sbapt pwd->pw_passwd += sizeof(locked_str)-1; 31920253Sjoerg } 32052527Sdavidn 321287084Sbapt perform_chgpwent(name, pwd, NULL); 322287084Sbapt free(passtmp); 32352527Sdavidn 324287084Sbapt return (EXIT_SUCCESS); 32520253Sjoerg} 32620253Sjoerg 327287084Sbaptstatic uid_t 328287084Sbaptpw_uidpolicy(struct userconf * cnf, intmax_t id) 32920253Sjoerg{ 33020253Sjoerg struct passwd *pwd; 331287084Sbapt struct bitmap bm; 33220253Sjoerg uid_t uid = (uid_t) - 1; 33320253Sjoerg 33420253Sjoerg /* 33520253Sjoerg * Check the given uid, if any 33620253Sjoerg */ 337285536Sbapt if (id >= 0) { 338285092Sbapt uid = (uid_t) id; 33920253Sjoerg 340285092Sbapt if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate) 341287084Sbapt errx(EX_DATAERR, "uid `%ju' has already been allocated", 342287084Sbapt (uintmax_t)pwd->pw_uid); 343287084Sbapt return (uid); 344287084Sbapt } 345287084Sbapt /* 346287084Sbapt * We need to allocate the next available uid under one of 347287084Sbapt * two policies a) Grab the first unused uid b) Grab the 348287084Sbapt * highest possible unused uid 349287084Sbapt */ 350287084Sbapt if (cnf->min_uid >= cnf->max_uid) { /* Sanity 351287084Sbapt * claus^H^H^H^Hheck */ 352287084Sbapt cnf->min_uid = 1000; 353287084Sbapt cnf->max_uid = 32000; 354287084Sbapt } 355287084Sbapt bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); 35620253Sjoerg 357287084Sbapt /* 358287084Sbapt * Now, let's fill the bitmap from the password file 359287084Sbapt */ 360287084Sbapt SETPWENT(); 361287084Sbapt while ((pwd = GETPWENT()) != NULL) 362287084Sbapt if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) 363287084Sbapt bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); 364287084Sbapt ENDPWENT(); 36520253Sjoerg 366287084Sbapt /* 367287084Sbapt * Then apply the policy, with fallback to reuse if necessary 368287084Sbapt */ 369287084Sbapt if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) 370287084Sbapt uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); 37120253Sjoerg 372287084Sbapt /* 373287084Sbapt * Another sanity check 374287084Sbapt */ 375287084Sbapt if (uid < cnf->min_uid || uid > cnf->max_uid) 376287084Sbapt errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); 377287084Sbapt bm_dealloc(&bm); 378287084Sbapt return (uid); 37920253Sjoerg} 38020253Sjoerg 381287084Sbaptstatic uid_t 382287084Sbaptpw_gidpolicy(struct userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun) 38320253Sjoerg{ 38420253Sjoerg struct group *grp; 38520253Sjoerg gid_t gid = (uid_t) - 1; 38620253Sjoerg 38720253Sjoerg /* 38820253Sjoerg * Check the given gid, if any 38920253Sjoerg */ 39044229Sdavidn SETGRENT(); 391287084Sbapt if (grname) { 392287084Sbapt if ((grp = GETGRNAM(grname)) == NULL) { 393287084Sbapt gid = pw_checkid(grname, GID_MAX); 394287084Sbapt grp = GETGRGID(gid); 39520253Sjoerg } 39620253Sjoerg gid = grp->gr_gid; 397272192Sdteske } else if ((grp = GETGRNAM(nam)) != NULL && 398272192Sdteske (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { 39920267Sjoerg gid = grp->gr_gid; /* Already created? Use it anyway... */ 40020253Sjoerg } else { 401287084Sbapt intmax_t grid = -1; 40220253Sjoerg 40320253Sjoerg /* 40420253Sjoerg * We need to auto-create a group with the user's name. We 40520253Sjoerg * can send all the appropriate output to our sister routine 40620253Sjoerg * bit first see if we can create a group with gid==uid so we 40720253Sjoerg * can keep the user and group ids in sync. We purposely do 40820253Sjoerg * NOT check the gid range if we can force the sync. If the 40920253Sjoerg * user's name dups an existing group, then the group add 41020253Sjoerg * function will happily handle that case for us and exit. 41120253Sjoerg */ 412285536Sbapt if (GETGRGID(prefer) == NULL) 413285536Sbapt grid = prefer; 414287084Sbapt if (dryrun) { 415287084Sbapt gid = pw_groupnext(cnf, true); 416287084Sbapt } else { 417287084Sbapt if (grid == -1) 418287084Sbapt grid = pw_groupnext(cnf, true); 419287084Sbapt groupadd(cnf, nam, grid, NULL, -1, false, false, false); 42044229Sdavidn if ((grp = GETGRNAM(nam)) != NULL) 42120267Sjoerg gid = grp->gr_gid; 42220267Sjoerg } 42320253Sjoerg } 42444229Sdavidn ENDGRENT(); 425287084Sbapt return (gid); 42620253Sjoerg} 42720253Sjoerg 428287084Sbaptstatic char * 429287084Sbaptpw_homepolicy(struct userconf * cnf, char *homedir, const char *user) 43020253Sjoerg{ 431285092Sbapt static char home[128]; 43220253Sjoerg 433287084Sbapt if (homedir) 434287084Sbapt return (homedir); 43520253Sjoerg 436285092Sbapt if (cnf->home == NULL || *cnf->home == '\0') 437285092Sbapt errx(EX_CONFIG, "no base home directory set"); 438285092Sbapt snprintf(home, sizeof(home), "%s/%s", cnf->home, user); 439285092Sbapt 440285092Sbapt return (home); 44120253Sjoerg} 44220253Sjoerg 443287084Sbaptstatic char * 44420253Sjoergshell_path(char const * path, char *shells[], char *sh) 44520253Sjoerg{ 44620253Sjoerg if (sh != NULL && (*sh == '/' || *sh == '\0')) 44720253Sjoerg return sh; /* specified full path or forced none */ 44820253Sjoerg else { 44920253Sjoerg char *p; 45020253Sjoerg char paths[_UC_MAXLINE]; 45120253Sjoerg 45220253Sjoerg /* 45320253Sjoerg * We need to search paths 45420253Sjoerg */ 455130633Srobert strlcpy(paths, path, sizeof(paths)); 45620253Sjoerg for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { 45720253Sjoerg int i; 45820253Sjoerg static char shellpath[256]; 45920253Sjoerg 46020253Sjoerg if (sh != NULL) { 461285092Sbapt snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh); 46220253Sjoerg if (access(shellpath, X_OK) == 0) 46320253Sjoerg return shellpath; 46420253Sjoerg } else 46520253Sjoerg for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { 466285092Sbapt snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]); 46720253Sjoerg if (access(shellpath, X_OK) == 0) 46820253Sjoerg return shellpath; 46920253Sjoerg } 47020253Sjoerg } 47120253Sjoerg if (sh == NULL) 47230259Scharnier errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); 47330259Scharnier errx(EX_CONFIG, "no default shell available or defined"); 47420253Sjoerg return NULL; 47520253Sjoerg } 47620253Sjoerg} 47720253Sjoerg 478287084Sbaptstatic char * 479287084Sbaptpw_shellpolicy(struct userconf * cnf) 48020253Sjoerg{ 48120253Sjoerg 482287084Sbapt return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); 48320253Sjoerg} 48420253Sjoerg 485179365Santoine#define SALTSIZE 32 48620253Sjoerg 487179365Santoinestatic char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; 488179365Santoine 489287084Sbaptchar * 49020253Sjoergpw_pwcrypt(char *password) 49120253Sjoerg{ 49220253Sjoerg int i; 493179365Santoine char salt[SALTSIZE + 1]; 494231994Skevlo char *cryptpw; 49520253Sjoerg static char buf[256]; 496319267Sasomers size_t pwlen; 49720253Sjoerg 49820253Sjoerg /* 49920253Sjoerg * Calculate a salt value 50020253Sjoerg */ 501179365Santoine for (i = 0; i < SALTSIZE; i++) 502181785Sache salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; 503179365Santoine salt[SALTSIZE] = '\0'; 50420253Sjoerg 505231994Skevlo cryptpw = crypt(password, salt); 506231994Skevlo if (cryptpw == NULL) 507231994Skevlo errx(EX_CONFIG, "crypt(3) failure"); 508319267Sasomers pwlen = strlcpy(buf, cryptpw, sizeof(buf)); 509319267Sasomers assert(pwlen < sizeof(buf)); 510319267Sasomers return (buf); 51120253Sjoerg} 51220253Sjoerg 513287084Sbaptstatic char * 514287084Sbaptpw_password(struct userconf * cnf, char const * user, bool dryrun) 51520253Sjoerg{ 51620253Sjoerg int i, l; 51720253Sjoerg char pwbuf[32]; 51820253Sjoerg 51920253Sjoerg switch (cnf->default_password) { 520326849Seugen case P_NONE: /* No password at all! */ 521326849Seugen return ""; 522326849Seugen case P_RANDOM: /* Random password */ 52373563Skris l = (arc4random() % 8 + 8); /* 8 - 16 chars */ 52420253Sjoerg for (i = 0; i < l; i++) 525181785Sache pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; 52620253Sjoerg pwbuf[i] = '\0'; 52720253Sjoerg 52820253Sjoerg /* 52920253Sjoerg * We give this information back to the user 53020253Sjoerg */ 531287084Sbapt if (conf.fd == -1 && !dryrun) { 53261957Sache if (isatty(STDOUT_FILENO)) 53320712Sdavidn printf("Password for '%s' is: ", user); 53420253Sjoerg printf("%s\n", pwbuf); 53520253Sjoerg fflush(stdout); 53620253Sjoerg } 53720253Sjoerg break; 538326849Seugen case P_YES: /* user's name */ 539326849Seugen strlcpy(pwbuf, user, sizeof(pwbuf)); 540326849Seugen break; 541326849Seugen case P_NO: /* No login - default */ 542326849Seugen /* FALLTHROUGH */ 54320253Sjoerg default: 54420253Sjoerg return "*"; 54520253Sjoerg } 54620253Sjoerg return pw_pwcrypt(pwbuf); 54720253Sjoerg} 54820253Sjoerg 549285092Sbaptstatic int 550287084Sbaptprint_user(struct passwd * pwd, bool pretty, bool v7) 551285092Sbapt{ 552287084Sbapt int j; 553287084Sbapt char *p; 554287084Sbapt struct group *grp = GETGRGID(pwd->pw_gid); 555287084Sbapt char uname[60] = "User &", office[60] = "[None]", 556287084Sbapt wphone[60] = "[None]", hphone[60] = "[None]"; 557287084Sbapt char acexpire[32] = "[None]", pwexpire[32] = "[None]"; 558287084Sbapt struct tm * tptr; 55920253Sjoerg 560287084Sbapt if (!pretty) { 561287084Sbapt p = v7 ? pw_make_v7(pwd) : pw_make(pwd); 562287084Sbapt printf("%s\n", p); 563287084Sbapt free(p); 564287084Sbapt return (EXIT_SUCCESS); 565287084Sbapt } 566285092Sbapt 567287084Sbapt if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { 568287084Sbapt strlcpy(uname, p, sizeof(uname)); 569287084Sbapt if ((p = strtok(NULL, ",")) != NULL) { 570287084Sbapt strlcpy(office, p, sizeof(office)); 571287084Sbapt if ((p = strtok(NULL, ",")) != NULL) { 572287084Sbapt strlcpy(wphone, p, sizeof(wphone)); 573287084Sbapt if ((p = strtok(NULL, "")) != NULL) { 574287084Sbapt strlcpy(hphone, p, sizeof(hphone)); 575287084Sbapt } 576287084Sbapt } 577285092Sbapt } 578285092Sbapt } 579285092Sbapt /* 580287084Sbapt * Handle '&' in gecos field 581285092Sbapt */ 582287084Sbapt if ((p = strchr(uname, '&')) != NULL) { 583287084Sbapt int l = strlen(pwd->pw_name); 584287084Sbapt int m = strlen(p); 585285092Sbapt 586287084Sbapt memmove(p + l, p + 1, m); 587287084Sbapt memmove(p, pwd->pw_name, l); 588287084Sbapt *p = (char) toupper((unsigned char)*p); 589285092Sbapt } 590287084Sbapt if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) 591287084Sbapt strftime(acexpire, sizeof acexpire, "%c", tptr); 592360326Sdim if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) 593287084Sbapt strftime(pwexpire, sizeof pwexpire, "%c", tptr); 594287084Sbapt printf("Login Name: %-15s #%-12ju Group: %-15s #%ju\n" 595287084Sbapt " Full Name: %s\n" 596287084Sbapt " Home: %-26.26s Class: %s\n" 597287084Sbapt " Shell: %-26.26s Office: %s\n" 598287084Sbapt "Work Phone: %-26.26s Home Phone: %s\n" 599287084Sbapt "Acc Expire: %-26.26s Pwd Expire: %s\n", 600287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 601287084Sbapt grp ? grp->gr_name : "(invalid)", (uintmax_t)pwd->pw_gid, 602287084Sbapt uname, pwd->pw_dir, pwd->pw_class, 603287084Sbapt pwd->pw_shell, office, wphone, hphone, 604287084Sbapt acexpire, pwexpire); 605287084Sbapt SETGRENT(); 606287084Sbapt j = 0; 607287084Sbapt while ((grp=GETGRENT()) != NULL) { 608287084Sbapt int i = 0; 609287084Sbapt if (grp->gr_mem != NULL) { 610287084Sbapt while (grp->gr_mem[i] != NULL) { 611287084Sbapt if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) { 612287084Sbapt printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); 613287084Sbapt break; 61420267Sjoerg } 615287084Sbapt ++i; 61620267Sjoerg } 61720267Sjoerg } 61820253Sjoerg } 619287084Sbapt ENDGRENT(); 620287084Sbapt printf("%s", j ? "\n" : ""); 621287084Sbapt return (EXIT_SUCCESS); 62220253Sjoerg} 62320253Sjoerg 624285092Sbaptchar * 625285092Sbaptpw_checkname(char *name, int gecos) 62620253Sjoerg{ 627109961Sgad char showch[8]; 628285092Sbapt const char *badchars, *ch, *showtype; 629109961Sgad int reject; 63020253Sjoerg 631109961Sgad ch = name; 632109961Sgad reject = 0; 633109961Sgad if (gecos) { 634109961Sgad /* See if the name is valid as a gecos (comment) field. */ 635330695Sdab badchars = ":"; 636109961Sgad showtype = "gecos field"; 637109961Sgad } else { 638109961Sgad /* See if the name is valid as a userid or group. */ 639109961Sgad badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; 640109961Sgad showtype = "userid/group name"; 641109961Sgad /* Userids and groups can not have a leading '-'. */ 642109961Sgad if (*ch == '-') 643109961Sgad reject = 1; 64420253Sjoerg } 645109961Sgad if (!reject) { 646109961Sgad while (*ch) { 647292026Sbapt if (strchr(badchars, *ch) != NULL || 648292026Sbapt (!gecos && *ch < ' ') || 649109961Sgad *ch == 127) { 650109961Sgad reject = 1; 651109961Sgad break; 652109961Sgad } 653109961Sgad /* 8-bit characters are only allowed in GECOS fields */ 654109961Sgad if (!gecos && (*ch & 0x80)) { 655109961Sgad reject = 1; 656109961Sgad break; 657109961Sgad } 658109961Sgad ch++; 659109961Sgad } 660109961Sgad } 661109961Sgad /* 662109961Sgad * A `$' is allowed as the final character for userids and groups, 663109961Sgad * mainly for the benefit of samba. 664109961Sgad */ 665109961Sgad if (reject && !gecos) { 666109961Sgad if (*ch == '$' && *(ch + 1) == '\0') { 667109961Sgad reject = 0; 668109961Sgad ch++; 669109961Sgad } 670109961Sgad } 671109961Sgad if (reject) { 672109961Sgad snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) 673109961Sgad ? "`%c'" : "0x%02x", *ch); 674228673Sdim errx(EX_DATAERR, "invalid character %s at position %td in %s", 675109961Sgad showch, (ch - name), showtype); 676109961Sgad } 677109961Sgad if (!gecos && (ch - name) > LOGNAMESIZE) 678287084Sbapt errx(EX_USAGE, "name too long `%s' (max is %d)", name, 679109961Sgad LOGNAMESIZE); 680285092Sbapt 681285092Sbapt return (name); 68220253Sjoerg} 68320253Sjoerg 68420253Sjoergstatic void 68520253Sjoergrmat(uid_t uid) 68620253Sjoerg{ 68720253Sjoerg DIR *d = opendir("/var/at/jobs"); 68820253Sjoerg 68920253Sjoerg if (d != NULL) { 69020253Sjoerg struct dirent *e; 69120253Sjoerg 69220253Sjoerg while ((e = readdir(d)) != NULL) { 69320253Sjoerg struct stat st; 69420253Sjoerg 69520253Sjoerg if (strncmp(e->d_name, ".lock", 5) != 0 && 69620253Sjoerg stat(e->d_name, &st) == 0 && 69720253Sjoerg !S_ISDIR(st.st_mode) && 69820253Sjoerg st.st_uid == uid) { 69920253Sjoerg char tmp[MAXPATHLEN]; 70020253Sjoerg 701287084Sbapt snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", 702287084Sbapt e->d_name); 70320253Sjoerg system(tmp); 70420253Sjoerg } 70520253Sjoerg } 70620253Sjoerg closedir(d); 70720253Sjoerg } 70820253Sjoerg} 70920747Sdavidn 71020747Sdavidnstatic void 71185145Sachermopie(char const * name) 71220747Sdavidn{ 713287084Sbapt char tmp[1014]; 714287084Sbapt FILE *fp; 715287084Sbapt int fd; 716287084Sbapt size_t len; 717287084Sbapt off_t atofs = 0; 718287084Sbapt 719287084Sbapt if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1) 720287084Sbapt return; 72120747Sdavidn 722287084Sbapt fp = fdopen(fd, "r+"); 723287084Sbapt len = strlen(name); 72420747Sdavidn 725287084Sbapt while (fgets(tmp, sizeof(tmp), fp) != NULL) { 726287084Sbapt if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') { 727287084Sbapt /* Comment username out */ 728287084Sbapt if (fseek(fp, atofs, SEEK_SET) == 0) 729287084Sbapt fwrite("#", 1, 1, fp); 730287084Sbapt break; 731287084Sbapt } 732287084Sbapt atofs = ftell(fp); 733287084Sbapt } 734287084Sbapt /* 735287084Sbapt * If we got an error of any sort, don't update! 736287084Sbapt */ 737287084Sbapt fclose(fp); 738287084Sbapt} 739287084Sbapt 740287084Sbaptint 741287084Sbaptpw_user_next(int argc, char **argv, char *name __unused) 742287084Sbapt{ 743287084Sbapt struct userconf *cnf = NULL; 744287084Sbapt const char *cfg = NULL; 745287084Sbapt int ch; 746287084Sbapt bool quiet = false; 747287084Sbapt uid_t next; 748287084Sbapt 749287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 750287084Sbapt switch (ch) { 751287084Sbapt case 'C': 752287084Sbapt cfg = optarg; 753287084Sbapt break; 754287084Sbapt case 'q': 755287084Sbapt quiet = true; 756287084Sbapt break; 757287084Sbapt } 758287084Sbapt } 759287084Sbapt 760287084Sbapt if (quiet) 761287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 762287084Sbapt 763287084Sbapt cnf = get_userconfig(cfg); 764287084Sbapt 765287084Sbapt next = pw_uidpolicy(cnf, -1); 766287084Sbapt 767287084Sbapt printf("%ju:", (uintmax_t)next); 768287084Sbapt pw_groupnext(cnf, quiet); 769287084Sbapt 770287084Sbapt return (EXIT_SUCCESS); 771287084Sbapt} 772287084Sbapt 773287084Sbaptint 774287084Sbaptpw_user_show(int argc, char **argv, char *arg1) 775287084Sbapt{ 776287084Sbapt struct passwd *pwd = NULL; 777287084Sbapt char *name = NULL; 778287084Sbapt intmax_t id = -1; 779287084Sbapt int ch; 780287084Sbapt bool all = false; 781287084Sbapt bool pretty = false; 782287084Sbapt bool force = false; 783287084Sbapt bool v7 = false; 784287084Sbapt bool quiet = false; 785287084Sbapt 786287084Sbapt if (arg1 != NULL) { 787287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 788287084Sbapt id = pw_checkid(arg1, UID_MAX); 789287084Sbapt else 790287084Sbapt name = arg1; 791287084Sbapt } 792287084Sbapt 793287084Sbapt while ((ch = getopt(argc, argv, "C:qn:u:FPa7")) != -1) { 794287084Sbapt switch (ch) { 795287084Sbapt case 'C': 796287084Sbapt /* ignore compatibility */ 797287084Sbapt break; 798287084Sbapt case 'q': 799287084Sbapt quiet = true; 800287084Sbapt break; 801287084Sbapt case 'n': 802287084Sbapt name = optarg; 803287084Sbapt break; 804287084Sbapt case 'u': 805287084Sbapt id = pw_checkid(optarg, UID_MAX); 806287084Sbapt break; 807287084Sbapt case 'F': 808287084Sbapt force = true; 809287084Sbapt break; 810287084Sbapt case 'P': 811287084Sbapt pretty = true; 812287084Sbapt break; 813287084Sbapt case 'a': 814287084Sbapt all = true; 815287084Sbapt break; 816293682Sbapt case '7': 817287084Sbapt v7 = true; 818287084Sbapt break; 819287084Sbapt } 820287084Sbapt } 821287084Sbapt 822287084Sbapt if (quiet) 823287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 824287084Sbapt 825287084Sbapt if (all) { 826287084Sbapt SETPWENT(); 827287084Sbapt while ((pwd = GETPWENT()) != NULL) 828287084Sbapt print_user(pwd, pretty, v7); 829287084Sbapt ENDPWENT(); 830287084Sbapt return (EXIT_SUCCESS); 831287084Sbapt } 832287084Sbapt 833287084Sbapt if (id < 0 && name == NULL) 834287084Sbapt errx(EX_DATAERR, "username or id required"); 835287084Sbapt 836287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 837287084Sbapt if (pwd == NULL) { 838287084Sbapt if (force) { 839287084Sbapt pwd = &fakeuser; 840287084Sbapt } else { 841287084Sbapt if (name == NULL) 842287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", 843287084Sbapt (uintmax_t) id); 844287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 845287084Sbapt } 846287084Sbapt } 847287084Sbapt 848287084Sbapt return (print_user(pwd, pretty, v7)); 849287084Sbapt} 850287084Sbapt 851287084Sbaptint 852287084Sbaptpw_user_del(int argc, char **argv, char *arg1) 853287084Sbapt{ 854287084Sbapt struct userconf *cnf = NULL; 855287084Sbapt struct passwd *pwd = NULL; 856287084Sbapt struct group *gr, *grp; 857287084Sbapt char *name = NULL; 858287084Sbapt char grname[MAXLOGNAME]; 859287084Sbapt char *nispasswd = NULL; 860287084Sbapt char file[MAXPATHLEN]; 861287084Sbapt char home[MAXPATHLEN]; 862287084Sbapt const char *cfg = NULL; 863287084Sbapt struct stat st; 864287084Sbapt intmax_t id = -1; 865287084Sbapt int ch, rc; 866287084Sbapt bool nis = false; 867287084Sbapt bool deletehome = false; 868287084Sbapt bool quiet = false; 869287084Sbapt 870287084Sbapt if (arg1 != NULL) { 871287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 872287084Sbapt id = pw_checkid(arg1, UID_MAX); 873287084Sbapt else 874287084Sbapt name = arg1; 875287084Sbapt } 876287084Sbapt 877287084Sbapt while ((ch = getopt(argc, argv, "C:qn:u:rYy:")) != -1) { 878287084Sbapt switch (ch) { 879287084Sbapt case 'C': 880287084Sbapt cfg = optarg; 881287084Sbapt break; 882287084Sbapt case 'q': 883287084Sbapt quiet = true; 884287084Sbapt break; 885287084Sbapt case 'n': 886287084Sbapt name = optarg; 887287084Sbapt break; 888287084Sbapt case 'u': 889287084Sbapt id = pw_checkid(optarg, UID_MAX); 890287084Sbapt break; 891287084Sbapt case 'r': 892287084Sbapt deletehome = true; 893287084Sbapt break; 894287084Sbapt case 'y': 895287084Sbapt nispasswd = optarg; 896287084Sbapt break; 897287084Sbapt case 'Y': 898287084Sbapt nis = true; 899287084Sbapt break; 900287084Sbapt } 901287084Sbapt } 902287084Sbapt 903287084Sbapt if (quiet) 904287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 905287084Sbapt 906287084Sbapt if (id < 0 && name == NULL) 907287084Sbapt errx(EX_DATAERR, "username or id required"); 908287084Sbapt 909287084Sbapt cnf = get_userconfig(cfg); 910287084Sbapt 911287084Sbapt if (nispasswd == NULL) 912287084Sbapt nispasswd = cnf->nispasswd; 913287084Sbapt 914287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 915287084Sbapt if (pwd == NULL) { 916287084Sbapt if (name == NULL) 917287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id); 918287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 919287084Sbapt } 920287084Sbapt 921287084Sbapt if (PWF._altdir == PWF_REGULAR && 922287084Sbapt ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { 923287084Sbapt if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 924287084Sbapt if (!nis && nispasswd && *nispasswd != '/') 925287084Sbapt errx(EX_NOUSER, "Cannot remove NIS user `%s'", 926287084Sbapt name); 927287084Sbapt } else { 928287084Sbapt errx(EX_NOUSER, "Cannot remove non local user `%s'", 929287084Sbapt name); 930287084Sbapt } 931287084Sbapt } 932287084Sbapt 933287084Sbapt id = pwd->pw_uid; 934287084Sbapt if (name == NULL) 935287084Sbapt name = pwd->pw_name; 936287084Sbapt 937287084Sbapt if (strcmp(pwd->pw_name, "root") == 0) 938287084Sbapt errx(EX_DATAERR, "cannot remove user 'root'"); 939287084Sbapt 940287084Sbapt /* Remove opie record from /etc/opiekeys */ 941287084Sbapt if (PWALTDIR() != PWF_ALT) 942287084Sbapt rmopie(pwd->pw_name); 943287084Sbapt 944287084Sbapt if (!PWALTDIR()) { 945287084Sbapt /* Remove crontabs */ 946287084Sbapt snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); 947287084Sbapt if (access(file, F_OK) == 0) { 948287084Sbapt snprintf(file, sizeof(file), "crontab -u %s -r", 949287084Sbapt pwd->pw_name); 950287084Sbapt system(file); 951287084Sbapt } 952287084Sbapt } 953287084Sbapt 954287084Sbapt /* 955287084Sbapt * Save these for later, since contents of pwd may be 956287084Sbapt * invalidated by deletion 957287084Sbapt */ 958287084Sbapt snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name); 959287084Sbapt strlcpy(home, pwd->pw_dir, sizeof(home)); 960287084Sbapt gr = GETGRGID(pwd->pw_gid); 961287084Sbapt if (gr != NULL) 962287084Sbapt strlcpy(grname, gr->gr_name, LOGNAMESIZE); 963287084Sbapt else 964287084Sbapt grname[0] = '\0'; 965287084Sbapt 966287084Sbapt rc = delpwent(pwd); 967287084Sbapt if (rc == -1) 968287084Sbapt err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); 969287084Sbapt else if (rc != 0) 970287084Sbapt err(EX_IOERR, "passwd update"); 971287084Sbapt 972287084Sbapt if (nis && nispasswd && *nispasswd=='/') { 973287084Sbapt rc = delnispwent(nispasswd, name); 974287084Sbapt if (rc == -1) 975287084Sbapt warnx("WARNING: user '%s' does not exist in NIS passwd", 976287084Sbapt pwd->pw_name); 977287084Sbapt else if (rc != 0) 978287084Sbapt warn("WARNING: NIS passwd update"); 979287084Sbapt } 980287084Sbapt 981287084Sbapt grp = GETGRNAM(name); 982287084Sbapt if (grp != NULL && 983287084Sbapt (grp->gr_mem == NULL || *grp->gr_mem == NULL) && 984287084Sbapt strcmp(name, grname) == 0) 985287084Sbapt delgrent(GETGRNAM(name)); 986287084Sbapt SETGRENT(); 987287084Sbapt while ((grp = GETGRENT()) != NULL) { 988287084Sbapt int i, j; 989287084Sbapt char group[MAXLOGNAME]; 990287084Sbapt if (grp->gr_mem == NULL) 991287084Sbapt continue; 992287084Sbapt 993287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 994287084Sbapt if (strcmp(grp->gr_mem[i], name) != 0) 995287084Sbapt continue; 996287084Sbapt 997287084Sbapt for (j = i; grp->gr_mem[j] != NULL; j++) 998287084Sbapt grp->gr_mem[j] = grp->gr_mem[j+1]; 999287084Sbapt strlcpy(group, grp->gr_name, MAXLOGNAME); 1000287084Sbapt chggrent(group, grp); 1001287084Sbapt } 1002287084Sbapt } 1003287084Sbapt ENDGRENT(); 1004287084Sbapt 1005287084Sbapt pw_log(cnf, M_DELETE, W_USER, "%s(%ju) account removed", name, 1006287084Sbapt (uintmax_t)id); 1007287084Sbapt 1008287084Sbapt /* Remove mail file */ 1009287084Sbapt if (PWALTDIR() != PWF_ALT) 1010287084Sbapt unlinkat(conf.rootfd, file + 1, 0); 1011287084Sbapt 1012287084Sbapt /* Remove at jobs */ 1013287084Sbapt if (!PWALTDIR() && getpwuid(id) == NULL) 1014287084Sbapt rmat(id); 1015287084Sbapt 1016287084Sbapt /* Remove home directory and contents */ 1017287084Sbapt if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' && 1018287084Sbapt GETPWUID(id) == NULL && 1019287084Sbapt fstatat(conf.rootfd, home + 1, &st, 0) != -1) { 1020287084Sbapt rm_r(conf.rootfd, home, id); 1021287084Sbapt pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s" 1022287084Sbapt "removed", name, (uintmax_t)id, home, 1023287084Sbapt fstatat(conf.rootfd, home + 1, &st, 0) == -1 ? "" : "not " 1024287084Sbapt "completely "); 1025287084Sbapt } 1026287084Sbapt 1027287084Sbapt return (EXIT_SUCCESS); 1028287084Sbapt} 1029287084Sbapt 1030287084Sbaptint 1031287084Sbaptpw_user_lock(int argc, char **argv, char *arg1) 1032287084Sbapt{ 1033287084Sbapt int ch; 1034287084Sbapt 1035287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 1036287084Sbapt switch (ch) { 1037287084Sbapt case 'C': 1038287084Sbapt case 'q': 1039287084Sbapt /* compatibility */ 1040287084Sbapt break; 1041287084Sbapt } 1042287084Sbapt } 1043287084Sbapt 1044287084Sbapt return (pw_userlock(arg1, M_LOCK)); 1045287084Sbapt} 1046287084Sbapt 1047287084Sbaptint 1048287084Sbaptpw_user_unlock(int argc, char **argv, char *arg1) 1049287084Sbapt{ 1050287084Sbapt int ch; 1051287084Sbapt 1052287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 1053287084Sbapt switch (ch) { 1054287084Sbapt case 'C': 1055287084Sbapt case 'q': 1056287084Sbapt /* compatibility */ 1057287084Sbapt break; 1058287084Sbapt } 1059287084Sbapt } 1060287084Sbapt 1061287084Sbapt return (pw_userlock(arg1, M_UNLOCK)); 1062287084Sbapt} 1063287084Sbapt 1064287084Sbaptstatic struct group * 1065287084Sbaptgroup_from_name_or_id(char *name) 1066287084Sbapt{ 1067287084Sbapt const char *errstr = NULL; 1068287084Sbapt struct group *grp; 1069287084Sbapt uintmax_t id; 1070287084Sbapt 1071287084Sbapt if ((grp = GETGRNAM(name)) == NULL) { 1072287084Sbapt id = strtounum(name, 0, GID_MAX, &errstr); 1073287084Sbapt if (errstr) 1074287084Sbapt errx(EX_NOUSER, "group `%s' does not exist", name); 1075287084Sbapt grp = GETGRGID(id); 1076287084Sbapt if (grp == NULL) 1077287084Sbapt errx(EX_NOUSER, "group `%s' does not exist", name); 1078287084Sbapt } 1079287084Sbapt 1080287084Sbapt return (grp); 1081287084Sbapt} 1082287084Sbapt 1083287084Sbaptstatic void 1084287084Sbaptsplit_groups(StringList **groups, char *groupsstr) 1085287084Sbapt{ 1086287084Sbapt struct group *grp; 1087287084Sbapt char *p; 1088287084Sbapt char tok[] = ", \t"; 1089287084Sbapt 1090323333Semaste if (*groups == NULL) 1091323333Semaste *groups = sl_init(); 1092287084Sbapt for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) { 1093287084Sbapt grp = group_from_name_or_id(p); 1094287084Sbapt sl_add(*groups, newstr(grp->gr_name)); 1095287084Sbapt } 1096287084Sbapt} 1097287084Sbapt 1098287084Sbaptstatic void 1099287084Sbaptvalidate_grname(struct userconf *cnf, char *group) 1100287084Sbapt{ 1101287084Sbapt struct group *grp; 1102287084Sbapt 1103287084Sbapt if (group == NULL || *group == '\0') { 1104287084Sbapt cnf->default_group = ""; 1105287084Sbapt return; 1106287084Sbapt } 1107287084Sbapt grp = group_from_name_or_id(group); 1108287084Sbapt cnf->default_group = newstr(grp->gr_name); 1109287084Sbapt} 1110287084Sbapt 1111287084Sbaptstatic mode_t 1112287084Sbaptvalidate_mode(char *mode) 1113287084Sbapt{ 1114287084Sbapt mode_t m; 1115287084Sbapt void *set; 1116287084Sbapt 1117287084Sbapt if ((set = setmode(mode)) == NULL) 1118287084Sbapt errx(EX_DATAERR, "invalid directory creation mode '%s'", mode); 1119287084Sbapt 1120287084Sbapt m = getmode(set, _DEF_DIRMODE); 1121287084Sbapt free(set); 1122287084Sbapt return (m); 1123287084Sbapt} 1124287084Sbapt 1125326849Seugenstatic long 1126326849Seugenvalidate_expire(char *str, int opt) 1127326849Seugen{ 1128326849Seugen if (!numerics(str)) 1129326849Seugen errx(EX_DATAERR, "-%c argument must be numeric " 1130326849Seugen "when setting defaults: %s", (char)opt, str); 1131326849Seugen return strtol(str, NULL, 0); 1132326849Seugen} 1133326849Seugen 1134287084Sbaptstatic void 1135287084Sbaptmix_config(struct userconf *cmdcnf, struct userconf *cfg) 1136287084Sbapt{ 1137287084Sbapt 1138326849Seugen if (cmdcnf->default_password < 0) 1139287084Sbapt cmdcnf->default_password = cfg->default_password; 1140287084Sbapt if (cmdcnf->reuse_uids == 0) 1141287084Sbapt cmdcnf->reuse_uids = cfg->reuse_uids; 1142287084Sbapt if (cmdcnf->reuse_gids == 0) 1143287084Sbapt cmdcnf->reuse_gids = cfg->reuse_gids; 1144287084Sbapt if (cmdcnf->nispasswd == NULL) 1145287084Sbapt cmdcnf->nispasswd = cfg->nispasswd; 1146287084Sbapt if (cmdcnf->dotdir == NULL) 1147287084Sbapt cmdcnf->dotdir = cfg->dotdir; 1148287084Sbapt if (cmdcnf->newmail == NULL) 1149287084Sbapt cmdcnf->newmail = cfg->newmail; 1150287084Sbapt if (cmdcnf->logfile == NULL) 1151287084Sbapt cmdcnf->logfile = cfg->logfile; 1152287084Sbapt if (cmdcnf->home == NULL) 1153287084Sbapt cmdcnf->home = cfg->home; 1154287084Sbapt if (cmdcnf->homemode == 0) 1155287084Sbapt cmdcnf->homemode = cfg->homemode; 1156287084Sbapt if (cmdcnf->shelldir == NULL) 1157287084Sbapt cmdcnf->shelldir = cfg->shelldir; 1158287084Sbapt if (cmdcnf->shells == NULL) 1159287084Sbapt cmdcnf->shells = cfg->shells; 1160287084Sbapt if (cmdcnf->shell_default == NULL) 1161287084Sbapt cmdcnf->shell_default = cfg->shell_default; 1162287084Sbapt if (cmdcnf->default_group == NULL) 1163287084Sbapt cmdcnf->default_group = cfg->default_group; 1164287084Sbapt if (cmdcnf->groups == NULL) 1165287084Sbapt cmdcnf->groups = cfg->groups; 1166287084Sbapt if (cmdcnf->default_class == NULL) 1167287084Sbapt cmdcnf->default_class = cfg->default_class; 1168287084Sbapt if (cmdcnf->min_uid == 0) 1169287084Sbapt cmdcnf->min_uid = cfg->min_uid; 1170287084Sbapt if (cmdcnf->max_uid == 0) 1171287084Sbapt cmdcnf->max_uid = cfg->max_uid; 1172287084Sbapt if (cmdcnf->min_gid == 0) 1173287084Sbapt cmdcnf->min_gid = cfg->min_gid; 1174287084Sbapt if (cmdcnf->max_gid == 0) 1175287084Sbapt cmdcnf->max_gid = cfg->max_gid; 1176326849Seugen if (cmdcnf->expire_days < 0) 1177287084Sbapt cmdcnf->expire_days = cfg->expire_days; 1178326849Seugen if (cmdcnf->password_days < 0) 1179287084Sbapt cmdcnf->password_days = cfg->password_days; 1180287084Sbapt} 1181287084Sbapt 1182287084Sbaptint 1183287084Sbaptpw_user_add(int argc, char **argv, char *arg1) 1184287084Sbapt{ 1185287084Sbapt struct userconf *cnf, *cmdcnf; 1186287084Sbapt struct passwd *pwd; 1187287084Sbapt struct group *grp; 1188287084Sbapt struct stat st; 1189287084Sbapt char args[] = "C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y"; 1190287084Sbapt char line[_PASSWORD_LEN+1], path[MAXPATHLEN]; 1191287084Sbapt char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname; 1192287084Sbapt char *default_passwd, *name, *p; 1193316348Sbapt const char *cfg = NULL; 1194287084Sbapt login_cap_t *lc; 1195287084Sbapt FILE *pfp, *fp; 1196287084Sbapt intmax_t id = -1; 1197287084Sbapt time_t now; 1198287084Sbapt int rc, ch, fd = -1; 1199287084Sbapt size_t i; 1200287084Sbapt bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf; 1201287084Sbapt 1202287084Sbapt dryrun = nis = pretty = quiet = createhome = precrypted = false; 1203287084Sbapt genconf = false; 1204287084Sbapt gecos = homedir = skel = userid = groupid = default_passwd = NULL; 1205287084Sbapt grname = name = NULL; 1206287084Sbapt 1207287084Sbapt if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL) 1208287084Sbapt err(EXIT_FAILURE, "calloc()"); 1209287084Sbapt 1210326849Seugen cmdcnf->default_password = cmdcnf->expire_days = cmdcnf->password_days = -1; 1211326849Seugen now = time(NULL); 1212326849Seugen 1213287084Sbapt if (arg1 != NULL) { 1214287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 1215287084Sbapt id = pw_checkid(arg1, UID_MAX); 1216287084Sbapt else 1217323332Semaste name = pw_checkname(arg1, 0); 1218287084Sbapt } 1219287084Sbapt 1220287084Sbapt while ((ch = getopt(argc, argv, args)) != -1) { 1221287084Sbapt switch (ch) { 1222287084Sbapt case 'C': 1223287084Sbapt cfg = optarg; 1224287084Sbapt break; 1225287084Sbapt case 'q': 1226287084Sbapt quiet = true; 1227287084Sbapt break; 1228287084Sbapt case 'n': 1229323332Semaste name = pw_checkname(optarg, 0); 1230287084Sbapt break; 1231287084Sbapt case 'u': 1232287084Sbapt userid = optarg; 1233287084Sbapt break; 1234287084Sbapt case 'c': 1235287084Sbapt gecos = pw_checkname(optarg, 1); 1236287084Sbapt break; 1237287084Sbapt case 'd': 1238287084Sbapt homedir = optarg; 1239287084Sbapt break; 1240287084Sbapt case 'e': 1241326849Seugen if (genconf) 1242326849Seugen cmdcnf->expire_days = validate_expire(optarg, ch); 1243326849Seugen else 1244326849Seugen cmdcnf->expire_days = parse_date(now, optarg); 1245287084Sbapt break; 1246287084Sbapt case 'p': 1247326849Seugen if (genconf) 1248326849Seugen cmdcnf->password_days = validate_expire(optarg, ch); 1249326849Seugen else 1250326849Seugen cmdcnf->password_days = parse_date(now, optarg); 1251287084Sbapt break; 1252287084Sbapt case 'g': 1253287084Sbapt validate_grname(cmdcnf, optarg); 1254287084Sbapt grname = optarg; 1255287084Sbapt break; 1256287084Sbapt case 'G': 1257287084Sbapt split_groups(&cmdcnf->groups, optarg); 1258287084Sbapt break; 1259287084Sbapt case 'm': 1260287084Sbapt createhome = true; 1261287084Sbapt break; 1262287084Sbapt case 'M': 1263287084Sbapt cmdcnf->homemode = validate_mode(optarg); 1264287084Sbapt break; 1265287084Sbapt case 'k': 1266287084Sbapt walk = skel = optarg; 1267287084Sbapt if (*walk == '/') 1268287084Sbapt walk++; 1269287084Sbapt if (fstatat(conf.rootfd, walk, &st, 0) == -1) 1270287084Sbapt errx(EX_OSFILE, "skeleton `%s' does not " 1271287084Sbapt "exists", skel); 1272287084Sbapt if (!S_ISDIR(st.st_mode)) 1273287084Sbapt errx(EX_OSFILE, "skeleton `%s' is not a " 1274287084Sbapt "directory", skel); 1275287084Sbapt cmdcnf->dotdir = skel; 1276287084Sbapt break; 1277287084Sbapt case 's': 1278287084Sbapt cmdcnf->shell_default = optarg; 1279287084Sbapt break; 1280287084Sbapt case 'o': 1281287084Sbapt conf.checkduplicate = false; 1282287084Sbapt break; 1283287084Sbapt case 'L': 1284287084Sbapt cmdcnf->default_class = pw_checkname(optarg, 0); 1285287084Sbapt break; 1286287084Sbapt case 'i': 1287287084Sbapt groupid = optarg; 1288287084Sbapt break; 1289287084Sbapt case 'w': 1290287084Sbapt default_passwd = optarg; 1291287084Sbapt break; 1292287084Sbapt case 'H': 1293287084Sbapt if (fd != -1) 1294287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1295287084Sbapt "exclusive options"); 1296287084Sbapt fd = pw_checkfd(optarg); 1297287084Sbapt precrypted = true; 1298287084Sbapt if (fd == '-') 1299287084Sbapt errx(EX_USAGE, "-H expects a file descriptor"); 1300287084Sbapt break; 1301287084Sbapt case 'h': 1302287084Sbapt if (fd != -1) 1303287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1304287084Sbapt "exclusive options"); 1305287084Sbapt fd = pw_checkfd(optarg); 1306287084Sbapt break; 1307287084Sbapt case 'D': 1308287084Sbapt genconf = true; 1309287084Sbapt break; 1310287084Sbapt case 'b': 1311287084Sbapt cmdcnf->home = optarg; 1312287084Sbapt break; 1313287084Sbapt case 'N': 1314287084Sbapt dryrun = true; 1315287084Sbapt break; 1316287084Sbapt case 'P': 1317287084Sbapt pretty = true; 1318287084Sbapt break; 1319287084Sbapt case 'y': 1320287084Sbapt cmdcnf->nispasswd = optarg; 1321287084Sbapt break; 1322287084Sbapt case 'Y': 1323287084Sbapt nis = true; 1324287084Sbapt break; 1325287084Sbapt } 1326287084Sbapt } 1327287084Sbapt 1328287084Sbapt if (geteuid() != 0 && ! dryrun) 1329287084Sbapt errx(EX_NOPERM, "you must be root"); 1330287084Sbapt 1331287084Sbapt if (quiet) 1332287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 1333287084Sbapt 1334287084Sbapt cnf = get_userconfig(cfg); 1335287084Sbapt 1336287084Sbapt mix_config(cmdcnf, cnf); 1337287084Sbapt if (default_passwd) 1338305750Sasomers cmdcnf->default_password = passwd_val(default_passwd, 1339287084Sbapt cnf->default_password); 1340287084Sbapt if (genconf) { 1341287084Sbapt if (name != NULL) 1342287084Sbapt errx(EX_DATAERR, "can't combine `-D' with `-n name'"); 1343287084Sbapt if (userid != NULL) { 1344287084Sbapt if ((p = strtok(userid, ", \t")) != NULL) 1345287084Sbapt cmdcnf->min_uid = pw_checkid(p, UID_MAX); 1346287084Sbapt if (cmdcnf->min_uid == 0) 1347287084Sbapt cmdcnf->min_uid = 1000; 1348287084Sbapt if ((p = strtok(NULL, " ,\t")) != NULL) 1349287084Sbapt cmdcnf->max_uid = pw_checkid(p, UID_MAX); 1350287084Sbapt if (cmdcnf->max_uid == 0) 1351287084Sbapt cmdcnf->max_uid = 32000; 1352287084Sbapt } 1353287084Sbapt if (groupid != NULL) { 1354287084Sbapt if ((p = strtok(groupid, ", \t")) != NULL) 1355287084Sbapt cmdcnf->min_gid = pw_checkid(p, GID_MAX); 1356287084Sbapt if (cmdcnf->min_gid == 0) 1357287084Sbapt cmdcnf->min_gid = 1000; 1358287084Sbapt if ((p = strtok(NULL, " ,\t")) != NULL) 1359287084Sbapt cmdcnf->max_gid = pw_checkid(p, GID_MAX); 1360287084Sbapt if (cmdcnf->max_gid == 0) 1361287084Sbapt cmdcnf->max_gid = 32000; 1362287084Sbapt } 1363287084Sbapt if (write_userconfig(cmdcnf, cfg)) 1364287084Sbapt return (EXIT_SUCCESS); 1365287084Sbapt err(EX_IOERR, "config update"); 1366287084Sbapt } 1367287084Sbapt 1368287084Sbapt if (userid) 1369287084Sbapt id = pw_checkid(userid, UID_MAX); 1370287084Sbapt if (id < 0 && name == NULL) 1371287084Sbapt errx(EX_DATAERR, "user name or id required"); 1372287084Sbapt 1373287084Sbapt if (name == NULL) 1374287084Sbapt errx(EX_DATAERR, "login name required"); 1375287084Sbapt 1376287084Sbapt if (GETPWNAM(name) != NULL) 1377287084Sbapt errx(EX_DATAERR, "login name `%s' already exists", name); 1378287084Sbapt 1379316348Sbapt if (!grname) 1380316348Sbapt grname = cmdcnf->default_group; 1381316348Sbapt 1382287084Sbapt pwd = &fakeuser; 1383287084Sbapt pwd->pw_name = name; 1384287084Sbapt pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : ""; 1385287084Sbapt pwd->pw_uid = pw_uidpolicy(cmdcnf, id); 1386287084Sbapt pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name, 1387287084Sbapt (gid_t) pwd->pw_uid, dryrun); 1388327091Seugen 1389327091Seugen /* cmdcnf->password_days and cmdcnf->expire_days hold unixtime here */ 1390326849Seugen if (cmdcnf->password_days > 0) 1391327091Seugen pwd->pw_change = cmdcnf->password_days; 1392326849Seugen if (cmdcnf->expire_days > 0) 1393327091Seugen pwd->pw_expire = cmdcnf->expire_days; 1394326849Seugen 1395287084Sbapt pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name); 1396287084Sbapt pwd->pw_shell = pw_shellpolicy(cmdcnf); 1397287084Sbapt lc = login_getpwclass(pwd); 1398287084Sbapt if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) 1399287084Sbapt warn("setting crypt(3) format"); 1400287084Sbapt login_close(lc); 1401287084Sbapt pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun); 1402287084Sbapt if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 1403287084Sbapt warnx("WARNING: new account `%s' has a uid of 0 " 1404287084Sbapt "(superuser access!)", pwd->pw_name); 1405287084Sbapt if (gecos) 1406287084Sbapt pwd->pw_gecos = gecos; 1407287084Sbapt 1408287084Sbapt if (fd != -1) 1409287084Sbapt pw_set_passwd(pwd, fd, precrypted, false); 1410287084Sbapt 1411287084Sbapt if (dryrun) 1412287084Sbapt return (print_user(pwd, pretty, false)); 1413287084Sbapt 1414287084Sbapt if ((rc = addpwent(pwd)) != 0) { 1415287084Sbapt if (rc == -1) 1416287084Sbapt errx(EX_IOERR, "user '%s' already exists", 1417287084Sbapt pwd->pw_name); 1418287084Sbapt else if (rc != 0) 1419287084Sbapt err(EX_IOERR, "passwd file update"); 1420287084Sbapt } 1421287084Sbapt if (nis && cmdcnf->nispasswd && *cmdcnf->nispasswd == '/') { 1422287084Sbapt printf("%s\n", cmdcnf->nispasswd); 1423287084Sbapt rc = addnispwent(cmdcnf->nispasswd, pwd); 1424287084Sbapt if (rc == -1) 1425287084Sbapt warnx("User '%s' already exists in NIS passwd", 1426287084Sbapt pwd->pw_name); 1427287084Sbapt else if (rc != 0) 1428287084Sbapt warn("NIS passwd update"); 1429287084Sbapt /* NOTE: we treat NIS-only update errors as non-fatal */ 1430287084Sbapt } 1431287084Sbapt 1432287084Sbapt if (cmdcnf->groups != NULL) { 1433287084Sbapt for (i = 0; i < cmdcnf->groups->sl_cur; i++) { 1434287084Sbapt grp = GETGRNAM(cmdcnf->groups->sl_str[i]); 1435287084Sbapt grp = gr_add(grp, pwd->pw_name); 1436287084Sbapt /* 1437287084Sbapt * grp can only be NULL in 2 cases: 1438287084Sbapt * - the new member is already a member 1439287084Sbapt * - a problem with memory occurs 1440287084Sbapt * in both cases we want to skip now. 1441287084Sbapt */ 1442287084Sbapt if (grp == NULL) 1443287084Sbapt continue; 1444287084Sbapt chggrent(grp->gr_name, grp); 1445287084Sbapt free(grp); 1446287084Sbapt } 1447287084Sbapt } 1448287084Sbapt 1449287084Sbapt pwd = GETPWNAM(name); 1450287084Sbapt if (pwd == NULL) 1451287084Sbapt errx(EX_NOUSER, "user '%s' disappeared during update", name); 1452287084Sbapt 1453287084Sbapt grp = GETGRGID(pwd->pw_gid); 1454287084Sbapt pw_log(cnf, M_ADD, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", 1455287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 1456287084Sbapt grp ? grp->gr_name : "unknown", 1457287084Sbapt (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), 1458287084Sbapt pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 1459287084Sbapt 1460287084Sbapt /* 1461287084Sbapt * let's touch and chown the user's mail file. This is not 1462287084Sbapt * strictly necessary under BSD with a 0755 maildir but it also 1463287084Sbapt * doesn't hurt anything to create the empty mailfile 1464287084Sbapt */ 1465287084Sbapt if (PWALTDIR() != PWF_ALT) { 1466287084Sbapt snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, 1467287084Sbapt pwd->pw_name); 1468287084Sbapt /* Preserve contents & mtime */ 1469287084Sbapt close(openat(conf.rootfd, path +1, O_RDWR | O_CREAT, 0600)); 1470287084Sbapt fchownat(conf.rootfd, path + 1, pwd->pw_uid, pwd->pw_gid, 1471287084Sbapt AT_SYMLINK_NOFOLLOW); 1472287084Sbapt } 1473287084Sbapt 1474287084Sbapt /* 1475287084Sbapt * Let's create and populate the user's home directory. Note 1476287084Sbapt * that this also `works' for editing users if -m is used, but 1477287084Sbapt * existing files will *not* be overwritten. 1478287084Sbapt */ 1479287084Sbapt if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir && 1480287084Sbapt *pwd->pw_dir == '/' && pwd->pw_dir[1]) 1481287084Sbapt create_and_populate_homedir(cmdcnf, pwd, cmdcnf->dotdir, 1482287084Sbapt cmdcnf->homemode, false); 1483287084Sbapt 1484287084Sbapt if (!PWALTDIR() && cmdcnf->newmail && *cmdcnf->newmail && 1485287084Sbapt (fp = fopen(cnf->newmail, "r")) != NULL) { 1486287084Sbapt if ((pfp = popen(_PATH_SENDMAIL " -t", "w")) == NULL) 1487287084Sbapt warn("sendmail"); 1488287084Sbapt else { 1489287084Sbapt fprintf(pfp, "From: root\n" "To: %s\n" 1490287084Sbapt "Subject: Welcome!\n\n", pwd->pw_name); 1491287084Sbapt while (fgets(line, sizeof(line), fp) != NULL) { 1492287084Sbapt /* Do substitutions? */ 1493287084Sbapt fputs(line, pfp); 149420747Sdavidn } 1495287084Sbapt pclose(pfp); 1496287084Sbapt pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent", 1497287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid); 149820747Sdavidn } 149921052Sdavidn fclose(fp); 150020747Sdavidn } 1501287084Sbapt 1502287084Sbapt if (nis && nis_update() == 0) 1503287084Sbapt pw_log(cnf, M_ADD, W_USER, "NIS maps updated"); 1504287084Sbapt 1505287084Sbapt return (EXIT_SUCCESS); 150620747Sdavidn} 150720747Sdavidn 1508287084Sbaptint 1509287084Sbaptpw_user_mod(int argc, char **argv, char *arg1) 1510287084Sbapt{ 1511287084Sbapt struct userconf *cnf; 1512287084Sbapt struct passwd *pwd; 1513287084Sbapt struct group *grp; 1514287084Sbapt StringList *groups = NULL; 1515287084Sbapt char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:"; 1516316348Sbapt const char *cfg = NULL; 1517287084Sbapt char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell; 1518287084Sbapt char *passwd, *class, *nispasswd; 1519287084Sbapt login_cap_t *lc; 1520287084Sbapt struct stat st; 1521287084Sbapt intmax_t id = -1; 1522287084Sbapt int ch, fd = -1; 1523287084Sbapt size_t i, j; 1524314277Sbapt bool quiet, createhome, pretty, dryrun, nis, edited; 1525287084Sbapt bool precrypted; 1526287084Sbapt mode_t homemode = 0; 1527327091Seugen time_t expire_time, password_time, now; 1528287084Sbapt 1529327091Seugen expire_time = password_time = -1; 1530287084Sbapt gecos = homedir = grname = name = newname = skel = shell =NULL; 1531287084Sbapt passwd = NULL; 1532287084Sbapt class = nispasswd = NULL; 1533287084Sbapt quiet = createhome = pretty = dryrun = nis = precrypted = false; 1534314277Sbapt edited = false; 1535326849Seugen now = time(NULL); 1536287084Sbapt 1537287084Sbapt if (arg1 != NULL) { 1538287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 1539287084Sbapt id = pw_checkid(arg1, UID_MAX); 1540287084Sbapt else 1541287084Sbapt name = arg1; 1542287084Sbapt } 1543287084Sbapt 1544287084Sbapt while ((ch = getopt(argc, argv, args)) != -1) { 1545287084Sbapt switch (ch) { 1546287084Sbapt case 'C': 1547287084Sbapt cfg = optarg; 1548287084Sbapt break; 1549287084Sbapt case 'q': 1550287084Sbapt quiet = true; 1551287084Sbapt break; 1552287084Sbapt case 'n': 1553287084Sbapt name = optarg; 1554287084Sbapt break; 1555287084Sbapt case 'u': 1556287084Sbapt id = pw_checkid(optarg, UID_MAX); 1557287084Sbapt break; 1558287084Sbapt case 'c': 1559287084Sbapt gecos = pw_checkname(optarg, 1); 1560287084Sbapt break; 1561287084Sbapt case 'd': 1562287084Sbapt homedir = optarg; 1563287084Sbapt break; 1564287084Sbapt case 'e': 1565327091Seugen expire_time = parse_date(now, optarg); 1566287084Sbapt break; 1567287084Sbapt case 'p': 1568327091Seugen password_time = parse_date(now, optarg); 1569287084Sbapt break; 1570287084Sbapt case 'g': 1571287084Sbapt group_from_name_or_id(optarg); 1572287084Sbapt grname = optarg; 1573287084Sbapt break; 1574287084Sbapt case 'G': 1575287084Sbapt split_groups(&groups, optarg); 1576287084Sbapt break; 1577287084Sbapt case 'm': 1578287084Sbapt createhome = true; 1579287084Sbapt break; 1580287084Sbapt case 'M': 1581287084Sbapt homemode = validate_mode(optarg); 1582287084Sbapt break; 1583287084Sbapt case 'l': 1584287084Sbapt newname = optarg; 1585287084Sbapt break; 1586287084Sbapt case 'k': 1587287084Sbapt walk = skel = optarg; 1588287084Sbapt if (*walk == '/') 1589287084Sbapt walk++; 1590287084Sbapt if (fstatat(conf.rootfd, walk, &st, 0) == -1) 1591287084Sbapt errx(EX_OSFILE, "skeleton `%s' does not " 1592287084Sbapt "exists", skel); 1593287084Sbapt if (!S_ISDIR(st.st_mode)) 1594287084Sbapt errx(EX_OSFILE, "skeleton `%s' is not a " 1595287084Sbapt "directory", skel); 1596287084Sbapt break; 1597287084Sbapt case 's': 1598287084Sbapt shell = optarg; 1599287084Sbapt break; 1600287084Sbapt case 'w': 1601287084Sbapt passwd = optarg; 1602287084Sbapt break; 1603287084Sbapt case 'L': 1604287084Sbapt class = pw_checkname(optarg, 0); 1605287084Sbapt break; 1606287084Sbapt case 'H': 1607287084Sbapt if (fd != -1) 1608287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1609287084Sbapt "exclusive options"); 1610287084Sbapt fd = pw_checkfd(optarg); 1611287084Sbapt precrypted = true; 1612287084Sbapt if (fd == '-') 1613287084Sbapt errx(EX_USAGE, "-H expects a file descriptor"); 1614287084Sbapt break; 1615287084Sbapt case 'h': 1616287084Sbapt if (fd != -1) 1617287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1618287084Sbapt "exclusive options"); 1619287084Sbapt fd = pw_checkfd(optarg); 1620287084Sbapt break; 1621287084Sbapt case 'N': 1622287084Sbapt dryrun = true; 1623287084Sbapt break; 1624287084Sbapt case 'P': 1625287084Sbapt pretty = true; 1626287084Sbapt break; 1627287084Sbapt case 'y': 1628287084Sbapt nispasswd = optarg; 1629287084Sbapt break; 1630287084Sbapt case 'Y': 1631287084Sbapt nis = true; 1632287084Sbapt break; 1633287084Sbapt } 1634287084Sbapt } 1635287084Sbapt 1636287084Sbapt if (geteuid() != 0 && ! dryrun) 1637287084Sbapt errx(EX_NOPERM, "you must be root"); 1638287084Sbapt 1639287084Sbapt if (quiet) 1640287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 1641287084Sbapt 1642287084Sbapt cnf = get_userconfig(cfg); 1643287084Sbapt 1644287084Sbapt if (id < 0 && name == NULL) 1645287084Sbapt errx(EX_DATAERR, "username or id required"); 1646287084Sbapt 1647287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 1648287084Sbapt if (pwd == NULL) { 1649287084Sbapt if (name == NULL) 1650287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", 1651287084Sbapt (uintmax_t) id); 1652287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 1653287084Sbapt } 1654287084Sbapt 1655287084Sbapt if (name == NULL) 1656287084Sbapt name = pwd->pw_name; 1657287084Sbapt 1658287084Sbapt if (nis && nispasswd == NULL) 1659287084Sbapt nispasswd = cnf->nispasswd; 1660287084Sbapt 1661287084Sbapt if (PWF._altdir == PWF_REGULAR && 1662287084Sbapt ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { 1663287084Sbapt if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 1664287084Sbapt if (!nis && nispasswd && *nispasswd != '/') 1665287084Sbapt errx(EX_NOUSER, "Cannot modify NIS user `%s'", 1666287084Sbapt name); 1667287084Sbapt } else { 1668287084Sbapt errx(EX_NOUSER, "Cannot modify non local user `%s'", 1669287084Sbapt name); 1670287084Sbapt } 1671287084Sbapt } 1672287084Sbapt 1673287084Sbapt if (newname) { 1674287084Sbapt if (strcmp(pwd->pw_name, "root") == 0) 1675287084Sbapt errx(EX_DATAERR, "can't rename `root' account"); 1676287084Sbapt if (strcmp(pwd->pw_name, newname) != 0) { 1677287084Sbapt pwd->pw_name = pw_checkname(newname, 0); 1678287084Sbapt edited = true; 1679287084Sbapt } 1680287084Sbapt } 1681287084Sbapt 1682299483Smarkj if (id >= 0 && pwd->pw_uid != id) { 1683287084Sbapt pwd->pw_uid = id; 1684287084Sbapt edited = true; 1685287084Sbapt if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) 1686287084Sbapt errx(EX_DATAERR, "can't change uid of `root' account"); 1687287084Sbapt if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 1688287084Sbapt warnx("WARNING: account `%s' will have a uid of 0 " 1689287084Sbapt "(superuser access!)", pwd->pw_name); 1690287084Sbapt } 1691287084Sbapt 1692287084Sbapt if (grname && pwd->pw_uid != 0) { 1693287084Sbapt grp = GETGRNAM(grname); 1694287084Sbapt if (grp == NULL) 1695287084Sbapt grp = GETGRGID(pw_checkid(grname, GID_MAX)); 1696287084Sbapt if (grp->gr_gid != pwd->pw_gid) { 1697287084Sbapt pwd->pw_gid = grp->gr_gid; 1698287084Sbapt edited = true; 1699287084Sbapt } 1700287084Sbapt } 1701287084Sbapt 1702326849Seugen 1703327091Seugen if (password_time >= 0 && pwd->pw_change != password_time) { 1704327091Seugen pwd->pw_change = password_time; 1705287084Sbapt edited = true; 1706287084Sbapt } 1707287084Sbapt 1708327091Seugen if (expire_time >= 0 && pwd->pw_expire != expire_time) { 1709327091Seugen pwd->pw_expire = expire_time; 1710287084Sbapt edited = true; 1711287084Sbapt } 1712287084Sbapt 1713287084Sbapt if (shell) { 1714287084Sbapt shell = shell_path(cnf->shelldir, cnf->shells, shell); 1715287084Sbapt if (shell == NULL) 1716287084Sbapt shell = ""; 1717287084Sbapt if (strcmp(shell, pwd->pw_shell) != 0) { 1718287084Sbapt pwd->pw_shell = shell; 1719287084Sbapt edited = true; 1720287084Sbapt } 1721287084Sbapt } 1722287084Sbapt 1723287084Sbapt if (class && strcmp(pwd->pw_class, class) != 0) { 1724287084Sbapt pwd->pw_class = class; 1725287084Sbapt edited = true; 1726287084Sbapt } 1727287084Sbapt 1728287084Sbapt if (homedir && strcmp(pwd->pw_dir, homedir) != 0) { 1729287084Sbapt pwd->pw_dir = homedir; 1730287769Sbapt edited = true; 1731287084Sbapt if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) { 1732287084Sbapt if (!createhome) 1733287084Sbapt warnx("WARNING: home `%s' does not exist", 1734287084Sbapt pwd->pw_dir); 1735287084Sbapt } else if (!S_ISDIR(st.st_mode)) { 1736287084Sbapt warnx("WARNING: home `%s' is not a directory", 1737287084Sbapt pwd->pw_dir); 1738287084Sbapt } 1739287084Sbapt } 1740287084Sbapt 1741287084Sbapt if (passwd && conf.fd == -1) { 1742287084Sbapt lc = login_getpwclass(pwd); 1743287084Sbapt if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) 1744287084Sbapt warn("setting crypt(3) format"); 1745287084Sbapt login_close(lc); 1746305750Sasomers cnf->default_password = passwd_val(passwd, 1747287084Sbapt cnf->default_password); 1748287084Sbapt pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun); 1749287084Sbapt edited = true; 1750287084Sbapt } 1751287084Sbapt 1752287084Sbapt if (gecos && strcmp(pwd->pw_gecos, gecos) != 0) { 1753287084Sbapt pwd->pw_gecos = gecos; 1754287084Sbapt edited = true; 1755287084Sbapt } 1756287084Sbapt 1757287084Sbapt if (fd != -1) 1758287084Sbapt edited = pw_set_passwd(pwd, fd, precrypted, true); 1759287084Sbapt 1760287084Sbapt if (dryrun) 1761287084Sbapt return (print_user(pwd, pretty, false)); 1762287084Sbapt 1763287084Sbapt if (edited) /* Only updated this if required */ 1764287084Sbapt perform_chgpwent(name, pwd, nis ? nispasswd : NULL); 1765287084Sbapt /* Now perform the needed changes concern groups */ 1766287084Sbapt if (groups != NULL) { 1767287084Sbapt /* Delete User from groups using old name */ 1768287084Sbapt SETGRENT(); 1769287084Sbapt while ((grp = GETGRENT()) != NULL) { 1770287084Sbapt if (grp->gr_mem == NULL) 1771287084Sbapt continue; 1772287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 1773287084Sbapt if (strcmp(grp->gr_mem[i] , name) != 0) 1774287084Sbapt continue; 1775287084Sbapt for (j = i; grp->gr_mem[j] != NULL ; j++) 1776287084Sbapt grp->gr_mem[j] = grp->gr_mem[j+1]; 1777287084Sbapt chggrent(grp->gr_name, grp); 1778287084Sbapt break; 1779287084Sbapt } 1780287084Sbapt } 1781287084Sbapt ENDGRENT(); 1782287084Sbapt /* Add the user to the needed groups */ 1783287084Sbapt for (i = 0; i < groups->sl_cur; i++) { 1784287084Sbapt grp = GETGRNAM(groups->sl_str[i]); 1785287084Sbapt grp = gr_add(grp, pwd->pw_name); 1786287084Sbapt if (grp == NULL) 1787287084Sbapt continue; 1788287084Sbapt chggrent(grp->gr_name, grp); 1789287084Sbapt free(grp); 1790287084Sbapt } 1791287084Sbapt } 1792287084Sbapt /* In case of rename we need to walk over the different groups */ 1793287084Sbapt if (newname) { 1794287084Sbapt SETGRENT(); 1795287084Sbapt while ((grp = GETGRENT()) != NULL) { 1796287084Sbapt if (grp->gr_mem == NULL) 1797287084Sbapt continue; 1798287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 1799287084Sbapt if (strcmp(grp->gr_mem[i], name) != 0) 1800287084Sbapt continue; 1801287084Sbapt grp->gr_mem[i] = newname; 1802287084Sbapt chggrent(grp->gr_name, grp); 1803287084Sbapt break; 1804287084Sbapt } 1805287084Sbapt } 1806287084Sbapt } 1807287084Sbapt 1808287084Sbapt /* go get a current version of pwd */ 1809287084Sbapt if (newname) 1810287084Sbapt name = newname; 1811287084Sbapt pwd = GETPWNAM(name); 1812287084Sbapt if (pwd == NULL) 1813287084Sbapt errx(EX_NOUSER, "user '%s' disappeared during update", name); 1814287084Sbapt grp = GETGRGID(pwd->pw_gid); 1815287084Sbapt pw_log(cnf, M_UPDATE, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", 1816287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 1817287084Sbapt grp ? grp->gr_name : "unknown", 1818287084Sbapt (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), 1819287084Sbapt pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 1820287084Sbapt 1821287084Sbapt /* 1822287084Sbapt * Let's create and populate the user's home directory. Note 1823287084Sbapt * that this also `works' for editing users if -m is used, but 1824287084Sbapt * existing files will *not* be overwritten. 1825287084Sbapt */ 1826314277Sbapt if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir && 1827287084Sbapt *pwd->pw_dir == '/' && pwd->pw_dir[1]) { 1828287084Sbapt if (!skel) 1829287084Sbapt skel = cnf->dotdir; 1830287084Sbapt if (homemode == 0) 1831287084Sbapt homemode = cnf->homemode; 1832287084Sbapt create_and_populate_homedir(cnf, pwd, skel, homemode, true); 1833287084Sbapt } 1834287084Sbapt 1835287084Sbapt if (nis && nis_update() == 0) 1836287084Sbapt pw_log(cnf, M_UPDATE, W_USER, "NIS maps updated"); 1837287084Sbapt 1838287084Sbapt return (EXIT_SUCCESS); 1839287084Sbapt} 1840