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: releng/10.3/usr.sbin/pw/pw_user.c 293684 2016-01-11 19:26:18Z bapt $"; 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 3830259Scharnier#include <ctype.h> 39287084Sbapt#include <dirent.h> 4030259Scharnier#include <err.h> 41287084Sbapt#include <errno.h> 4220253Sjoerg#include <fcntl.h> 43287084Sbapt#include <grp.h> 44287084Sbapt#include <pwd.h> 45287084Sbapt#include <libutil.h> 46287084Sbapt#include <login_cap.h> 4730259Scharnier#include <paths.h> 48287084Sbapt#include <string.h> 49287084Sbapt#include <sysexits.h> 5020253Sjoerg#include <termios.h> 51287084Sbapt#include <unistd.h> 52287084Sbapt 5320253Sjoerg#include "pw.h" 5420253Sjoerg#include "bitmap.h" 55287084Sbapt#include "psdate.h" 5620253Sjoerg 5723318Sache#define LOGNAMESIZE (MAXLOGNAME-1) 5822394Sdavidn 5952512Sdavidnstatic char locked_str[] = "*LOCKED*"; 6024214Sache 61287084Sbaptstatic struct passwd fakeuser = { 62287084Sbapt "nouser", 63287084Sbapt "*", 64287084Sbapt -1, 65287084Sbapt -1, 66287084Sbapt 0, 67287084Sbapt "", 68287084Sbapt "User &", 69287084Sbapt "/nonexistent", 70287084Sbapt "/bin/sh", 71287084Sbapt 0, 72287084Sbapt 0 73287084Sbapt}; 7420253Sjoerg 75287084Sbaptstatic int print_user(struct passwd *pwd, bool pretty, bool v7); 76287084Sbaptstatic uid_t pw_uidpolicy(struct userconf *cnf, intmax_t id); 77287084Sbaptstatic uid_t pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, 78287084Sbapt gid_t prefer, bool dryrun); 79287084Sbaptstatic char *pw_homepolicy(struct userconf * cnf, char *homedir, 80287084Sbapt const char *user); 81287084Sbaptstatic char *pw_shellpolicy(struct userconf * cnf); 82287084Sbaptstatic char *pw_password(struct userconf * cnf, char const * user, 83287084Sbapt bool dryrun); 84287084Sbaptstatic char *shell_path(char const * path, char *shells[], char *sh); 85287084Sbaptstatic void rmat(uid_t uid); 86287084Sbaptstatic void rmopie(char const * name); 87287084Sbapt 88285092Sbaptstatic void 89287084Sbaptmkdir_home_parents(int dfd, const char *dir) 90285092Sbapt{ 91287084Sbapt struct stat st; 92287084Sbapt char *dirs, *tmp; 93285092Sbapt 94287084Sbapt if (*dir != '/') 95287084Sbapt errx(EX_DATAERR, "invalid base directory for home '%s'", dir); 96285092Sbapt 97287084Sbapt dir++; 98285092Sbapt 99287084Sbapt if (fstatat(dfd, dir, &st, 0) != -1) { 100287084Sbapt if (S_ISDIR(st.st_mode)) 101287084Sbapt return; 102287084Sbapt errx(EX_OSFILE, "root home `/%s' is not a directory", dir); 10320267Sjoerg } 10420267Sjoerg 105287084Sbapt dirs = strdup(dir); 106287084Sbapt if (dirs == NULL) 107287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 10820253Sjoerg 109287084Sbapt tmp = strrchr(dirs, '/'); 110293684Sbapt if (tmp == NULL) { 111293684Sbapt free(dirs); 112287084Sbapt return; 113293684Sbapt } 114287084Sbapt tmp[0] = '\0'; 11521052Sdavidn 11621052Sdavidn /* 117287084Sbapt * This is a kludge especially for Joerg :) 118287084Sbapt * If the home directory would be created in the root partition, then 119287084Sbapt * we really create it under /usr which is likely to have more space. 120287084Sbapt * But we create a symlink from cnf->home -> "/usr" -> cnf->home 12121052Sdavidn */ 122287084Sbapt if (strchr(dirs, '/') == NULL) { 123287084Sbapt asprintf(&tmp, "usr/%s", dirs); 124287084Sbapt if (tmp == NULL) 125287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 126287084Sbapt if (mkdirat(dfd, tmp, _DEF_DIRMODE) != -1 || errno == EEXIST) { 127287084Sbapt fchownat(dfd, tmp, 0, 0, 0); 128287084Sbapt symlinkat(tmp, dfd, dirs); 12920253Sjoerg } 130287084Sbapt free(tmp); 13120253Sjoerg } 132287084Sbapt tmp = dirs; 133287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 134287084Sbapt while ((tmp = strchr(tmp + 1, '/')) != NULL) { 135287084Sbapt *tmp = '\0'; 136287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 137287084Sbapt if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) 138287084Sbapt err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); 13920253Sjoerg } 140287084Sbapt *tmp = '/'; 14120253Sjoerg } 14220253Sjoerg } 143287084Sbapt if (fstatat(dfd, dirs, &st, 0) == -1) { 144287084Sbapt if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) 145287084Sbapt err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); 146287084Sbapt fchownat(dfd, dirs, 0, 0, 0); 14720253Sjoerg } 14852527Sdavidn 149287084Sbapt free(dirs); 150287084Sbapt} 15120253Sjoerg 152287084Sbaptstatic void 153287084Sbaptcreate_and_populate_homedir(struct userconf *cnf, struct passwd *pwd, 154287084Sbapt const char *skeldir, mode_t homemode, bool update) 155287084Sbapt{ 156287084Sbapt int skelfd = -1; 15720253Sjoerg 158287084Sbapt /* Create home parents directories */ 159287084Sbapt mkdir_home_parents(conf.rootfd, pwd->pw_dir); 16052527Sdavidn 161287084Sbapt if (skeldir != NULL && *skeldir != '\0') { 162287084Sbapt if (*skeldir == '/') 163287084Sbapt skeldir++; 164287084Sbapt skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC); 16520253Sjoerg } 16652527Sdavidn 167287084Sbapt copymkdir(conf.rootfd, pwd->pw_dir, skelfd, homemode, pwd->pw_uid, 168287084Sbapt pwd->pw_gid, 0); 169287084Sbapt pw_log(cnf, update ? M_UPDATE : M_ADD, W_USER, "%s(%ju) home %s made", 170287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, pwd->pw_dir); 171287084Sbapt} 17220253Sjoerg 173287084Sbaptstatic int 174287084Sbaptpw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update) 175287084Sbapt{ 176287084Sbapt int b, istty; 177287084Sbapt struct termios t, n; 178287084Sbapt login_cap_t *lc; 179287084Sbapt char line[_PASSWORD_LEN+1]; 180287084Sbapt char *p; 18120253Sjoerg 182287084Sbapt if (fd == '-') { 183287084Sbapt if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { 184287084Sbapt pwd->pw_passwd = "*"; /* No access */ 185287084Sbapt return (1); 18620253Sjoerg } 187287084Sbapt return (0); 188287084Sbapt } 18952527Sdavidn 190287084Sbapt if ((istty = isatty(fd))) { 191287084Sbapt if (tcgetattr(fd, &t) == -1) 192287084Sbapt istty = 0; 193287084Sbapt else { 194287084Sbapt n = t; 195287084Sbapt n.c_lflag &= ~(ECHO); 196287084Sbapt tcsetattr(fd, TCSANOW, &n); 197287084Sbapt printf("%s%spassword for user %s:", 198287084Sbapt update ? "new " : "", 199287084Sbapt precrypted ? "encrypted " : "", 200287084Sbapt pwd->pw_name); 201287084Sbapt fflush(stdout); 20220253Sjoerg } 203287084Sbapt } 204287084Sbapt b = read(fd, line, sizeof(line) - 1); 205287084Sbapt if (istty) { /* Restore state */ 206287084Sbapt tcsetattr(fd, TCSANOW, &t); 207287084Sbapt fputc('\n', stdout); 208287084Sbapt fflush(stdout); 209287084Sbapt } 21052527Sdavidn 211287084Sbapt if (b < 0) 212287084Sbapt err(EX_IOERR, "-%c file descriptor", 213287084Sbapt precrypted ? 'H' : 'h'); 214287084Sbapt line[b] = '\0'; 215287084Sbapt if ((p = strpbrk(line, "\r\n")) != NULL) 216287084Sbapt *p = '\0'; 217287084Sbapt if (!*line) 218287084Sbapt errx(EX_DATAERR, "empty password read on file descriptor %d", 219287084Sbapt fd); 220287084Sbapt if (precrypted) { 221287084Sbapt if (strchr(line, ':') != NULL) 222287084Sbapt errx(EX_DATAERR, "bad encrypted password"); 223287084Sbapt pwd->pw_passwd = strdup(line); 22420253Sjoerg } else { 22564918Sgreen lc = login_getpwclass(pwd); 226287084Sbapt if (lc == NULL || 227287084Sbapt login_setcryptfmt(lc, "sha512", NULL) == NULL) 22864918Sgreen warn("setting crypt(3) format"); 22964918Sgreen login_close(lc); 230287084Sbapt pwd->pw_passwd = pw_pwcrypt(line); 23120253Sjoerg } 232287084Sbapt return (1); 233287084Sbapt} 23420253Sjoerg 235287084Sbaptstatic void 236287084Sbaptperform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) 237287084Sbapt{ 238287084Sbapt int rc; 239287084Sbapt struct passwd *nispwd; 24020253Sjoerg 241287084Sbapt /* duplicate for nis so that chgpwent is not modifying before NIS */ 242287084Sbapt if (nispasswd && *nispasswd == '/') 243287084Sbapt nispwd = pw_dup(pwd); 24420253Sjoerg 245287084Sbapt rc = chgpwent(name, pwd); 246287084Sbapt if (rc == -1) 247287084Sbapt errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name); 248287084Sbapt else if (rc != 0) 249287084Sbapt err(EX_IOERR, "passwd file update"); 25020253Sjoerg 251287084Sbapt if (nispasswd && *nispasswd == '/') { 252287084Sbapt rc = chgnispwent(nispasswd, name, nispwd); 253285092Sbapt if (rc == -1) 254287084Sbapt warn("User '%s' not found in NIS passwd", pwd->pw_name); 255285092Sbapt else if (rc != 0) 256287084Sbapt warn("NIS passwd update"); 257287084Sbapt /* NOTE: NIS-only update errors are not fatal */ 25821330Sdavidn } 259287084Sbapt} 26021330Sdavidn 261287084Sbapt/* 262287084Sbapt * The M_LOCK and M_UNLOCK functions simply add or remove 263287084Sbapt * a "*LOCKED*" prefix from in front of the password to 264287084Sbapt * prevent it decoding correctly, and therefore prevents 265287084Sbapt * access. Of course, this only prevents access via 266287084Sbapt * password authentication (not ssh, kerberos or any 267287084Sbapt * other method that does not use the UNIX password) but 268287084Sbapt * that is a known limitation. 269287084Sbapt */ 270287084Sbaptstatic int 271287084Sbaptpw_userlock(char *arg1, int mode) 272287084Sbapt{ 273287084Sbapt struct passwd *pwd = NULL; 274287084Sbapt char *passtmp = NULL; 275287084Sbapt char *name; 276287084Sbapt bool locked = false; 277292025Sbapt uid_t id = (uid_t)-1; 27820253Sjoerg 279287084Sbapt if (geteuid() != 0) 280287084Sbapt errx(EX_NOPERM, "you must be root"); 281274082Sbapt 282287084Sbapt if (arg1 == NULL) 283287084Sbapt errx(EX_DATAERR, "username or id required"); 284242349Sbapt 285292025Sbapt name = arg1; 286292025Sbapt if (arg1[strspn(name, "0123456789")] == '\0') 287292025Sbapt id = pw_checkid(name, UID_MAX); 288242349Sbapt 289292025Sbapt pwd = GETPWNAM(pw_checkname(name, 0)); 290292025Sbapt if (pwd == NULL && id != (uid_t)-1) { 291292025Sbapt pwd = GETPWUID(id); 292292025Sbapt if (pwd != NULL) 293292025Sbapt name = pwd->pw_name; 294292025Sbapt } 29561759Sdavidn if (pwd == NULL) { 296292025Sbapt if (id == (uid_t)-1) 297292025Sbapt errx(EX_NOUSER, "no such name or uid `%ju'", (uintmax_t) id); 298287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 29961759Sdavidn } 30020253Sjoerg 301287084Sbapt if (name == NULL) 302287084Sbapt name = pwd->pw_name; 30320253Sjoerg 304287084Sbapt if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str) -1) == 0) 305287084Sbapt locked = true; 306287084Sbapt if (mode == M_LOCK && locked) 307287084Sbapt errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); 308287084Sbapt if (mode == M_UNLOCK && !locked) 309287084Sbapt errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); 310287084Sbapt 311287084Sbapt if (mode == M_LOCK) { 312287084Sbapt asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd); 313287084Sbapt if (passtmp == NULL) /* disaster */ 314287084Sbapt errx(EX_UNAVAILABLE, "out of memory"); 315287084Sbapt pwd->pw_passwd = passtmp; 316287084Sbapt } else { 317287084Sbapt pwd->pw_passwd += sizeof(locked_str)-1; 31820253Sjoerg } 31952527Sdavidn 320287084Sbapt perform_chgpwent(name, pwd, NULL); 321287084Sbapt free(passtmp); 32252527Sdavidn 323287084Sbapt return (EXIT_SUCCESS); 32420253Sjoerg} 32520253Sjoerg 326287084Sbaptstatic uid_t 327287084Sbaptpw_uidpolicy(struct userconf * cnf, intmax_t id) 32820253Sjoerg{ 32920253Sjoerg struct passwd *pwd; 330287084Sbapt struct bitmap bm; 33120253Sjoerg uid_t uid = (uid_t) - 1; 33220253Sjoerg 33320253Sjoerg /* 33420253Sjoerg * Check the given uid, if any 33520253Sjoerg */ 336285536Sbapt if (id >= 0) { 337285092Sbapt uid = (uid_t) id; 33820253Sjoerg 339285092Sbapt if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate) 340287084Sbapt errx(EX_DATAERR, "uid `%ju' has already been allocated", 341287084Sbapt (uintmax_t)pwd->pw_uid); 342287084Sbapt return (uid); 343287084Sbapt } 344287084Sbapt /* 345287084Sbapt * We need to allocate the next available uid under one of 346287084Sbapt * two policies a) Grab the first unused uid b) Grab the 347287084Sbapt * highest possible unused uid 348287084Sbapt */ 349287084Sbapt if (cnf->min_uid >= cnf->max_uid) { /* Sanity 350287084Sbapt * claus^H^H^H^Hheck */ 351287084Sbapt cnf->min_uid = 1000; 352287084Sbapt cnf->max_uid = 32000; 353287084Sbapt } 354287084Sbapt bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); 35520253Sjoerg 356287084Sbapt /* 357287084Sbapt * Now, let's fill the bitmap from the password file 358287084Sbapt */ 359287084Sbapt SETPWENT(); 360287084Sbapt while ((pwd = GETPWENT()) != NULL) 361287084Sbapt if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) 362287084Sbapt bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); 363287084Sbapt ENDPWENT(); 36420253Sjoerg 365287084Sbapt /* 366287084Sbapt * Then apply the policy, with fallback to reuse if necessary 367287084Sbapt */ 368287084Sbapt if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) 369287084Sbapt uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); 37020253Sjoerg 371287084Sbapt /* 372287084Sbapt * Another sanity check 373287084Sbapt */ 374287084Sbapt if (uid < cnf->min_uid || uid > cnf->max_uid) 375287084Sbapt errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); 376287084Sbapt bm_dealloc(&bm); 377287084Sbapt return (uid); 37820253Sjoerg} 37920253Sjoerg 380287084Sbaptstatic uid_t 381287084Sbaptpw_gidpolicy(struct userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun) 38220253Sjoerg{ 38320253Sjoerg struct group *grp; 38420253Sjoerg gid_t gid = (uid_t) - 1; 38520253Sjoerg 38620253Sjoerg /* 38720253Sjoerg * Check the given gid, if any 38820253Sjoerg */ 38944229Sdavidn SETGRENT(); 390287084Sbapt if (grname) { 391287084Sbapt if ((grp = GETGRNAM(grname)) == NULL) { 392287084Sbapt gid = pw_checkid(grname, GID_MAX); 393287084Sbapt grp = GETGRGID(gid); 39420253Sjoerg } 39520253Sjoerg gid = grp->gr_gid; 396272192Sdteske } else if ((grp = GETGRNAM(nam)) != NULL && 397272192Sdteske (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { 39820267Sjoerg gid = grp->gr_gid; /* Already created? Use it anyway... */ 39920253Sjoerg } else { 400287084Sbapt intmax_t grid = -1; 40120253Sjoerg 40220253Sjoerg /* 40320253Sjoerg * We need to auto-create a group with the user's name. We 40420253Sjoerg * can send all the appropriate output to our sister routine 40520253Sjoerg * bit first see if we can create a group with gid==uid so we 40620253Sjoerg * can keep the user and group ids in sync. We purposely do 40720253Sjoerg * NOT check the gid range if we can force the sync. If the 40820253Sjoerg * user's name dups an existing group, then the group add 40920253Sjoerg * function will happily handle that case for us and exit. 41020253Sjoerg */ 411285536Sbapt if (GETGRGID(prefer) == NULL) 412285536Sbapt grid = prefer; 413287084Sbapt if (dryrun) { 414287084Sbapt gid = pw_groupnext(cnf, true); 415287084Sbapt } else { 416287084Sbapt if (grid == -1) 417287084Sbapt grid = pw_groupnext(cnf, true); 418287084Sbapt groupadd(cnf, nam, grid, NULL, -1, false, false, false); 41944229Sdavidn if ((grp = GETGRNAM(nam)) != NULL) 42020267Sjoerg gid = grp->gr_gid; 42120267Sjoerg } 42220253Sjoerg } 42344229Sdavidn ENDGRENT(); 424287084Sbapt return (gid); 42520253Sjoerg} 42620253Sjoerg 427287084Sbaptstatic char * 428287084Sbaptpw_homepolicy(struct userconf * cnf, char *homedir, const char *user) 42920253Sjoerg{ 430285092Sbapt static char home[128]; 43120253Sjoerg 432287084Sbapt if (homedir) 433287084Sbapt return (homedir); 43420253Sjoerg 435285092Sbapt if (cnf->home == NULL || *cnf->home == '\0') 436285092Sbapt errx(EX_CONFIG, "no base home directory set"); 437285092Sbapt snprintf(home, sizeof(home), "%s/%s", cnf->home, user); 438285092Sbapt 439285092Sbapt return (home); 44020253Sjoerg} 44120253Sjoerg 442287084Sbaptstatic char * 44320253Sjoergshell_path(char const * path, char *shells[], char *sh) 44420253Sjoerg{ 44520253Sjoerg if (sh != NULL && (*sh == '/' || *sh == '\0')) 44620253Sjoerg return sh; /* specified full path or forced none */ 44720253Sjoerg else { 44820253Sjoerg char *p; 44920253Sjoerg char paths[_UC_MAXLINE]; 45020253Sjoerg 45120253Sjoerg /* 45220253Sjoerg * We need to search paths 45320253Sjoerg */ 454130633Srobert strlcpy(paths, path, sizeof(paths)); 45520253Sjoerg for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { 45620253Sjoerg int i; 45720253Sjoerg static char shellpath[256]; 45820253Sjoerg 45920253Sjoerg if (sh != NULL) { 460285092Sbapt snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh); 46120253Sjoerg if (access(shellpath, X_OK) == 0) 46220253Sjoerg return shellpath; 46320253Sjoerg } else 46420253Sjoerg for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { 465285092Sbapt snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]); 46620253Sjoerg if (access(shellpath, X_OK) == 0) 46720253Sjoerg return shellpath; 46820253Sjoerg } 46920253Sjoerg } 47020253Sjoerg if (sh == NULL) 47130259Scharnier errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); 47230259Scharnier errx(EX_CONFIG, "no default shell available or defined"); 47320253Sjoerg return NULL; 47420253Sjoerg } 47520253Sjoerg} 47620253Sjoerg 477287084Sbaptstatic char * 478287084Sbaptpw_shellpolicy(struct userconf * cnf) 47920253Sjoerg{ 48020253Sjoerg 481287084Sbapt return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); 48220253Sjoerg} 48320253Sjoerg 484179365Santoine#define SALTSIZE 32 48520253Sjoerg 486179365Santoinestatic char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; 487179365Santoine 488287084Sbaptchar * 48920253Sjoergpw_pwcrypt(char *password) 49020253Sjoerg{ 49120253Sjoerg int i; 492179365Santoine char salt[SALTSIZE + 1]; 493231994Skevlo char *cryptpw; 49420253Sjoerg static char buf[256]; 49520253Sjoerg 49620253Sjoerg /* 49720253Sjoerg * Calculate a salt value 49820253Sjoerg */ 499179365Santoine for (i = 0; i < SALTSIZE; i++) 500181785Sache salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; 501179365Santoine salt[SALTSIZE] = '\0'; 50220253Sjoerg 503231994Skevlo cryptpw = crypt(password, salt); 504231994Skevlo if (cryptpw == NULL) 505231994Skevlo errx(EX_CONFIG, "crypt(3) failure"); 506231994Skevlo return strcpy(buf, cryptpw); 50720253Sjoerg} 50820253Sjoerg 509287084Sbaptstatic char * 510287084Sbaptpw_password(struct userconf * cnf, char const * user, bool dryrun) 51120253Sjoerg{ 51220253Sjoerg int i, l; 51320253Sjoerg char pwbuf[32]; 51420253Sjoerg 51520253Sjoerg switch (cnf->default_password) { 51620253Sjoerg case -1: /* Random password */ 51773563Skris l = (arc4random() % 8 + 8); /* 8 - 16 chars */ 51820253Sjoerg for (i = 0; i < l; i++) 519181785Sache pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; 52020253Sjoerg pwbuf[i] = '\0'; 52120253Sjoerg 52220253Sjoerg /* 52320253Sjoerg * We give this information back to the user 52420253Sjoerg */ 525287084Sbapt if (conf.fd == -1 && !dryrun) { 52661957Sache if (isatty(STDOUT_FILENO)) 52720712Sdavidn printf("Password for '%s' is: ", user); 52820253Sjoerg printf("%s\n", pwbuf); 52920253Sjoerg fflush(stdout); 53020253Sjoerg } 53120253Sjoerg break; 53220253Sjoerg 53320253Sjoerg case -2: /* No password at all! */ 53420253Sjoerg return ""; 53520253Sjoerg 53620253Sjoerg case 0: /* No login - default */ 53720253Sjoerg default: 53820253Sjoerg return "*"; 53920253Sjoerg 54020253Sjoerg case 1: /* user's name */ 541130633Srobert strlcpy(pwbuf, user, sizeof(pwbuf)); 54220253Sjoerg break; 54320253Sjoerg } 54420253Sjoerg return pw_pwcrypt(pwbuf); 54520253Sjoerg} 54620253Sjoerg 547285092Sbaptstatic int 548287084Sbaptprint_user(struct passwd * pwd, bool pretty, bool v7) 549285092Sbapt{ 550287084Sbapt int j; 551287084Sbapt char *p; 552287084Sbapt struct group *grp = GETGRGID(pwd->pw_gid); 553287084Sbapt char uname[60] = "User &", office[60] = "[None]", 554287084Sbapt wphone[60] = "[None]", hphone[60] = "[None]"; 555287084Sbapt char acexpire[32] = "[None]", pwexpire[32] = "[None]"; 556287084Sbapt struct tm * tptr; 55720253Sjoerg 558287084Sbapt if (!pretty) { 559287084Sbapt p = v7 ? pw_make_v7(pwd) : pw_make(pwd); 560287084Sbapt printf("%s\n", p); 561287084Sbapt free(p); 562287084Sbapt return (EXIT_SUCCESS); 563287084Sbapt } 564285092Sbapt 565287084Sbapt if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { 566287084Sbapt strlcpy(uname, p, sizeof(uname)); 567287084Sbapt if ((p = strtok(NULL, ",")) != NULL) { 568287084Sbapt strlcpy(office, p, sizeof(office)); 569287084Sbapt if ((p = strtok(NULL, ",")) != NULL) { 570287084Sbapt strlcpy(wphone, p, sizeof(wphone)); 571287084Sbapt if ((p = strtok(NULL, "")) != NULL) { 572287084Sbapt strlcpy(hphone, p, sizeof(hphone)); 573287084Sbapt } 574287084Sbapt } 575285092Sbapt } 576285092Sbapt } 577285092Sbapt /* 578287084Sbapt * Handle '&' in gecos field 579285092Sbapt */ 580287084Sbapt if ((p = strchr(uname, '&')) != NULL) { 581287084Sbapt int l = strlen(pwd->pw_name); 582287084Sbapt int m = strlen(p); 583285092Sbapt 584287084Sbapt memmove(p + l, p + 1, m); 585287084Sbapt memmove(p, pwd->pw_name, l); 586287084Sbapt *p = (char) toupper((unsigned char)*p); 587285092Sbapt } 588287084Sbapt if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) 589287084Sbapt strftime(acexpire, sizeof acexpire, "%c", tptr); 59061957Sache if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) 591287084Sbapt strftime(pwexpire, sizeof pwexpire, "%c", tptr); 592287084Sbapt printf("Login Name: %-15s #%-12ju Group: %-15s #%ju\n" 593287084Sbapt " Full Name: %s\n" 594287084Sbapt " Home: %-26.26s Class: %s\n" 595287084Sbapt " Shell: %-26.26s Office: %s\n" 596287084Sbapt "Work Phone: %-26.26s Home Phone: %s\n" 597287084Sbapt "Acc Expire: %-26.26s Pwd Expire: %s\n", 598287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 599287084Sbapt grp ? grp->gr_name : "(invalid)", (uintmax_t)pwd->pw_gid, 600287084Sbapt uname, pwd->pw_dir, pwd->pw_class, 601287084Sbapt pwd->pw_shell, office, wphone, hphone, 602287084Sbapt acexpire, pwexpire); 603287084Sbapt SETGRENT(); 604287084Sbapt j = 0; 605287084Sbapt while ((grp=GETGRENT()) != NULL) { 606287084Sbapt int i = 0; 607287084Sbapt if (grp->gr_mem != NULL) { 608287084Sbapt while (grp->gr_mem[i] != NULL) { 609287084Sbapt if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) { 610287084Sbapt printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); 611287084Sbapt break; 61220267Sjoerg } 613287084Sbapt ++i; 61420267Sjoerg } 61520267Sjoerg } 61620253Sjoerg } 617287084Sbapt ENDGRENT(); 618287084Sbapt printf("%s", j ? "\n" : ""); 619287084Sbapt return (EXIT_SUCCESS); 62020253Sjoerg} 62120253Sjoerg 622285092Sbaptchar * 623285092Sbaptpw_checkname(char *name, int gecos) 62420253Sjoerg{ 625109961Sgad char showch[8]; 626285092Sbapt const char *badchars, *ch, *showtype; 627109961Sgad int reject; 62820253Sjoerg 629109961Sgad ch = name; 630109961Sgad reject = 0; 631109961Sgad if (gecos) { 632109961Sgad /* See if the name is valid as a gecos (comment) field. */ 633109961Sgad badchars = ":!@"; 634109961Sgad showtype = "gecos field"; 635109961Sgad } else { 636109961Sgad /* See if the name is valid as a userid or group. */ 637109961Sgad badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; 638109961Sgad showtype = "userid/group name"; 639109961Sgad /* Userids and groups can not have a leading '-'. */ 640109961Sgad if (*ch == '-') 641109961Sgad reject = 1; 64220253Sjoerg } 643109961Sgad if (!reject) { 644109961Sgad while (*ch) { 645292026Sbapt if (strchr(badchars, *ch) != NULL || 646292026Sbapt (!gecos && *ch < ' ') || 647109961Sgad *ch == 127) { 648109961Sgad reject = 1; 649109961Sgad break; 650109961Sgad } 651109961Sgad /* 8-bit characters are only allowed in GECOS fields */ 652109961Sgad if (!gecos && (*ch & 0x80)) { 653109961Sgad reject = 1; 654109961Sgad break; 655109961Sgad } 656109961Sgad ch++; 657109961Sgad } 658109961Sgad } 659109961Sgad /* 660109961Sgad * A `$' is allowed as the final character for userids and groups, 661109961Sgad * mainly for the benefit of samba. 662109961Sgad */ 663109961Sgad if (reject && !gecos) { 664109961Sgad if (*ch == '$' && *(ch + 1) == '\0') { 665109961Sgad reject = 0; 666109961Sgad ch++; 667109961Sgad } 668109961Sgad } 669109961Sgad if (reject) { 670109961Sgad snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) 671109961Sgad ? "`%c'" : "0x%02x", *ch); 672228673Sdim errx(EX_DATAERR, "invalid character %s at position %td in %s", 673109961Sgad showch, (ch - name), showtype); 674109961Sgad } 675109961Sgad if (!gecos && (ch - name) > LOGNAMESIZE) 676287084Sbapt errx(EX_USAGE, "name too long `%s' (max is %d)", name, 677109961Sgad LOGNAMESIZE); 678285092Sbapt 679285092Sbapt return (name); 68020253Sjoerg} 68120253Sjoerg 68220253Sjoergstatic void 68320253Sjoergrmat(uid_t uid) 68420253Sjoerg{ 68520253Sjoerg DIR *d = opendir("/var/at/jobs"); 68620253Sjoerg 68720253Sjoerg if (d != NULL) { 68820253Sjoerg struct dirent *e; 68920253Sjoerg 69020253Sjoerg while ((e = readdir(d)) != NULL) { 69120253Sjoerg struct stat st; 69220253Sjoerg 69320253Sjoerg if (strncmp(e->d_name, ".lock", 5) != 0 && 69420253Sjoerg stat(e->d_name, &st) == 0 && 69520253Sjoerg !S_ISDIR(st.st_mode) && 69620253Sjoerg st.st_uid == uid) { 69720253Sjoerg char tmp[MAXPATHLEN]; 69820253Sjoerg 699287084Sbapt snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", 700287084Sbapt e->d_name); 70120253Sjoerg system(tmp); 70220253Sjoerg } 70320253Sjoerg } 70420253Sjoerg closedir(d); 70520253Sjoerg } 70620253Sjoerg} 70720747Sdavidn 70820747Sdavidnstatic void 70985145Sachermopie(char const * name) 71020747Sdavidn{ 711287084Sbapt char tmp[1014]; 712287084Sbapt FILE *fp; 713287084Sbapt int fd; 714287084Sbapt size_t len; 715287084Sbapt off_t atofs = 0; 716287084Sbapt 717287084Sbapt if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1) 718287084Sbapt return; 71920747Sdavidn 720287084Sbapt fp = fdopen(fd, "r+"); 721287084Sbapt len = strlen(name); 72220747Sdavidn 723287084Sbapt while (fgets(tmp, sizeof(tmp), fp) != NULL) { 724287084Sbapt if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') { 725287084Sbapt /* Comment username out */ 726287084Sbapt if (fseek(fp, atofs, SEEK_SET) == 0) 727287084Sbapt fwrite("#", 1, 1, fp); 728287084Sbapt break; 729287084Sbapt } 730287084Sbapt atofs = ftell(fp); 731287084Sbapt } 732287084Sbapt /* 733287084Sbapt * If we got an error of any sort, don't update! 734287084Sbapt */ 735287084Sbapt fclose(fp); 736287084Sbapt} 737287084Sbapt 738287084Sbaptint 739287084Sbaptpw_user_next(int argc, char **argv, char *name __unused) 740287084Sbapt{ 741287084Sbapt struct userconf *cnf = NULL; 742287084Sbapt const char *cfg = NULL; 743287084Sbapt int ch; 744287084Sbapt bool quiet = false; 745287084Sbapt uid_t next; 746287084Sbapt 747287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 748287084Sbapt switch (ch) { 749287084Sbapt case 'C': 750287084Sbapt cfg = optarg; 751287084Sbapt break; 752287084Sbapt case 'q': 753287084Sbapt quiet = true; 754287084Sbapt break; 755287084Sbapt } 756287084Sbapt } 757287084Sbapt 758287084Sbapt if (quiet) 759287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 760287084Sbapt 761287084Sbapt cnf = get_userconfig(cfg); 762287084Sbapt 763287084Sbapt next = pw_uidpolicy(cnf, -1); 764287084Sbapt 765287084Sbapt printf("%ju:", (uintmax_t)next); 766287084Sbapt pw_groupnext(cnf, quiet); 767287084Sbapt 768287084Sbapt return (EXIT_SUCCESS); 769287084Sbapt} 770287084Sbapt 771287084Sbaptint 772287084Sbaptpw_user_show(int argc, char **argv, char *arg1) 773287084Sbapt{ 774287084Sbapt struct passwd *pwd = NULL; 775287084Sbapt char *name = NULL; 776287084Sbapt intmax_t id = -1; 777287084Sbapt int ch; 778287084Sbapt bool all = false; 779287084Sbapt bool pretty = false; 780287084Sbapt bool force = false; 781287084Sbapt bool v7 = false; 782287084Sbapt bool quiet = false; 783287084Sbapt 784287084Sbapt if (arg1 != NULL) { 785287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 786287084Sbapt id = pw_checkid(arg1, UID_MAX); 787287084Sbapt else 788287084Sbapt name = arg1; 789287084Sbapt } 790287084Sbapt 791287084Sbapt while ((ch = getopt(argc, argv, "C:qn:u:FPa7")) != -1) { 792287084Sbapt switch (ch) { 793287084Sbapt case 'C': 794287084Sbapt /* ignore compatibility */ 795287084Sbapt break; 796287084Sbapt case 'q': 797287084Sbapt quiet = true; 798287084Sbapt break; 799287084Sbapt case 'n': 800287084Sbapt name = optarg; 801287084Sbapt break; 802287084Sbapt case 'u': 803287084Sbapt id = pw_checkid(optarg, UID_MAX); 804287084Sbapt break; 805287084Sbapt case 'F': 806287084Sbapt force = true; 807287084Sbapt break; 808287084Sbapt case 'P': 809287084Sbapt pretty = true; 810287084Sbapt break; 811287084Sbapt case 'a': 812287084Sbapt all = true; 813287084Sbapt break; 814293682Sbapt case '7': 815287084Sbapt v7 = true; 816287084Sbapt break; 817287084Sbapt } 818287084Sbapt } 819287084Sbapt 820287084Sbapt if (quiet) 821287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 822287084Sbapt 823287084Sbapt if (all) { 824287084Sbapt SETPWENT(); 825287084Sbapt while ((pwd = GETPWENT()) != NULL) 826287084Sbapt print_user(pwd, pretty, v7); 827287084Sbapt ENDPWENT(); 828287084Sbapt return (EXIT_SUCCESS); 829287084Sbapt } 830287084Sbapt 831287084Sbapt if (id < 0 && name == NULL) 832287084Sbapt errx(EX_DATAERR, "username or id required"); 833287084Sbapt 834287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 835287084Sbapt if (pwd == NULL) { 836287084Sbapt if (force) { 837287084Sbapt pwd = &fakeuser; 838287084Sbapt } else { 839287084Sbapt if (name == NULL) 840287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", 841287084Sbapt (uintmax_t) id); 842287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 843287084Sbapt } 844287084Sbapt } 845287084Sbapt 846287084Sbapt return (print_user(pwd, pretty, v7)); 847287084Sbapt} 848287084Sbapt 849287084Sbaptint 850287084Sbaptpw_user_del(int argc, char **argv, char *arg1) 851287084Sbapt{ 852287084Sbapt struct userconf *cnf = NULL; 853287084Sbapt struct passwd *pwd = NULL; 854287084Sbapt struct group *gr, *grp; 855287084Sbapt char *name = NULL; 856287084Sbapt char grname[MAXLOGNAME]; 857287084Sbapt char *nispasswd = NULL; 858287084Sbapt char file[MAXPATHLEN]; 859287084Sbapt char home[MAXPATHLEN]; 860287084Sbapt const char *cfg = NULL; 861287084Sbapt struct stat st; 862287084Sbapt intmax_t id = -1; 863287084Sbapt int ch, rc; 864287084Sbapt bool nis = false; 865287084Sbapt bool deletehome = false; 866287084Sbapt bool quiet = false; 867287084Sbapt 868287084Sbapt if (arg1 != NULL) { 869287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 870287084Sbapt id = pw_checkid(arg1, UID_MAX); 871287084Sbapt else 872287084Sbapt name = arg1; 873287084Sbapt } 874287084Sbapt 875287084Sbapt while ((ch = getopt(argc, argv, "C:qn:u:rYy:")) != -1) { 876287084Sbapt switch (ch) { 877287084Sbapt case 'C': 878287084Sbapt cfg = optarg; 879287084Sbapt break; 880287084Sbapt case 'q': 881287084Sbapt quiet = true; 882287084Sbapt break; 883287084Sbapt case 'n': 884287084Sbapt name = optarg; 885287084Sbapt break; 886287084Sbapt case 'u': 887287084Sbapt id = pw_checkid(optarg, UID_MAX); 888287084Sbapt break; 889287084Sbapt case 'r': 890287084Sbapt deletehome = true; 891287084Sbapt break; 892287084Sbapt case 'y': 893287084Sbapt nispasswd = optarg; 894287084Sbapt break; 895287084Sbapt case 'Y': 896287084Sbapt nis = true; 897287084Sbapt break; 898287084Sbapt } 899287084Sbapt } 900287084Sbapt 901287084Sbapt if (quiet) 902287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 903287084Sbapt 904287084Sbapt if (id < 0 && name == NULL) 905287084Sbapt errx(EX_DATAERR, "username or id required"); 906287084Sbapt 907287084Sbapt cnf = get_userconfig(cfg); 908287084Sbapt 909287084Sbapt if (nispasswd == NULL) 910287084Sbapt nispasswd = cnf->nispasswd; 911287084Sbapt 912287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 913287084Sbapt if (pwd == NULL) { 914287084Sbapt if (name == NULL) 915287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id); 916287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 917287084Sbapt } 918287084Sbapt 919287084Sbapt if (PWF._altdir == PWF_REGULAR && 920287084Sbapt ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { 921287084Sbapt if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 922287084Sbapt if (!nis && nispasswd && *nispasswd != '/') 923287084Sbapt errx(EX_NOUSER, "Cannot remove NIS user `%s'", 924287084Sbapt name); 925287084Sbapt } else { 926287084Sbapt errx(EX_NOUSER, "Cannot remove non local user `%s'", 927287084Sbapt name); 928287084Sbapt } 929287084Sbapt } 930287084Sbapt 931287084Sbapt id = pwd->pw_uid; 932287084Sbapt if (name == NULL) 933287084Sbapt name = pwd->pw_name; 934287084Sbapt 935287084Sbapt if (strcmp(pwd->pw_name, "root") == 0) 936287084Sbapt errx(EX_DATAERR, "cannot remove user 'root'"); 937287084Sbapt 938287084Sbapt /* Remove opie record from /etc/opiekeys */ 939287084Sbapt if (PWALTDIR() != PWF_ALT) 940287084Sbapt rmopie(pwd->pw_name); 941287084Sbapt 942287084Sbapt if (!PWALTDIR()) { 943287084Sbapt /* Remove crontabs */ 944287084Sbapt snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); 945287084Sbapt if (access(file, F_OK) == 0) { 946287084Sbapt snprintf(file, sizeof(file), "crontab -u %s -r", 947287084Sbapt pwd->pw_name); 948287084Sbapt system(file); 949287084Sbapt } 950287084Sbapt } 951287084Sbapt 952287084Sbapt /* 953287084Sbapt * Save these for later, since contents of pwd may be 954287084Sbapt * invalidated by deletion 955287084Sbapt */ 956287084Sbapt snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name); 957287084Sbapt strlcpy(home, pwd->pw_dir, sizeof(home)); 958287084Sbapt gr = GETGRGID(pwd->pw_gid); 959287084Sbapt if (gr != NULL) 960287084Sbapt strlcpy(grname, gr->gr_name, LOGNAMESIZE); 961287084Sbapt else 962287084Sbapt grname[0] = '\0'; 963287084Sbapt 964287084Sbapt rc = delpwent(pwd); 965287084Sbapt if (rc == -1) 966287084Sbapt err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); 967287084Sbapt else if (rc != 0) 968287084Sbapt err(EX_IOERR, "passwd update"); 969287084Sbapt 970287084Sbapt if (nis && nispasswd && *nispasswd=='/') { 971287084Sbapt rc = delnispwent(nispasswd, name); 972287084Sbapt if (rc == -1) 973287084Sbapt warnx("WARNING: user '%s' does not exist in NIS passwd", 974287084Sbapt pwd->pw_name); 975287084Sbapt else if (rc != 0) 976287084Sbapt warn("WARNING: NIS passwd update"); 977287084Sbapt } 978287084Sbapt 979287084Sbapt grp = GETGRNAM(name); 980287084Sbapt if (grp != NULL && 981287084Sbapt (grp->gr_mem == NULL || *grp->gr_mem == NULL) && 982287084Sbapt strcmp(name, grname) == 0) 983287084Sbapt delgrent(GETGRNAM(name)); 984287084Sbapt SETGRENT(); 985287084Sbapt while ((grp = GETGRENT()) != NULL) { 986287084Sbapt int i, j; 987287084Sbapt char group[MAXLOGNAME]; 988287084Sbapt if (grp->gr_mem == NULL) 989287084Sbapt continue; 990287084Sbapt 991287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 992287084Sbapt if (strcmp(grp->gr_mem[i], name) != 0) 993287084Sbapt continue; 994287084Sbapt 995287084Sbapt for (j = i; grp->gr_mem[j] != NULL; j++) 996287084Sbapt grp->gr_mem[j] = grp->gr_mem[j+1]; 997287084Sbapt strlcpy(group, grp->gr_name, MAXLOGNAME); 998287084Sbapt chggrent(group, grp); 999287084Sbapt } 1000287084Sbapt } 1001287084Sbapt ENDGRENT(); 1002287084Sbapt 1003287084Sbapt pw_log(cnf, M_DELETE, W_USER, "%s(%ju) account removed", name, 1004287084Sbapt (uintmax_t)id); 1005287084Sbapt 1006287084Sbapt /* Remove mail file */ 1007287084Sbapt if (PWALTDIR() != PWF_ALT) 1008287084Sbapt unlinkat(conf.rootfd, file + 1, 0); 1009287084Sbapt 1010287084Sbapt /* Remove at jobs */ 1011287084Sbapt if (!PWALTDIR() && getpwuid(id) == NULL) 1012287084Sbapt rmat(id); 1013287084Sbapt 1014287084Sbapt /* Remove home directory and contents */ 1015287084Sbapt if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' && 1016287084Sbapt GETPWUID(id) == NULL && 1017287084Sbapt fstatat(conf.rootfd, home + 1, &st, 0) != -1) { 1018287084Sbapt rm_r(conf.rootfd, home, id); 1019287084Sbapt pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s" 1020287084Sbapt "removed", name, (uintmax_t)id, home, 1021287084Sbapt fstatat(conf.rootfd, home + 1, &st, 0) == -1 ? "" : "not " 1022287084Sbapt "completely "); 1023287084Sbapt } 1024287084Sbapt 1025287084Sbapt return (EXIT_SUCCESS); 1026287084Sbapt} 1027287084Sbapt 1028287084Sbaptint 1029287084Sbaptpw_user_lock(int argc, char **argv, char *arg1) 1030287084Sbapt{ 1031287084Sbapt int ch; 1032287084Sbapt 1033287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 1034287084Sbapt switch (ch) { 1035287084Sbapt case 'C': 1036287084Sbapt case 'q': 1037287084Sbapt /* compatibility */ 1038287084Sbapt break; 1039287084Sbapt } 1040287084Sbapt } 1041287084Sbapt 1042287084Sbapt return (pw_userlock(arg1, M_LOCK)); 1043287084Sbapt} 1044287084Sbapt 1045287084Sbaptint 1046287084Sbaptpw_user_unlock(int argc, char **argv, char *arg1) 1047287084Sbapt{ 1048287084Sbapt int ch; 1049287084Sbapt 1050287084Sbapt while ((ch = getopt(argc, argv, "Cq")) != -1) { 1051287084Sbapt switch (ch) { 1052287084Sbapt case 'C': 1053287084Sbapt case 'q': 1054287084Sbapt /* compatibility */ 1055287084Sbapt break; 1056287084Sbapt } 1057287084Sbapt } 1058287084Sbapt 1059287084Sbapt return (pw_userlock(arg1, M_UNLOCK)); 1060287084Sbapt} 1061287084Sbapt 1062287084Sbaptstatic struct group * 1063287084Sbaptgroup_from_name_or_id(char *name) 1064287084Sbapt{ 1065287084Sbapt const char *errstr = NULL; 1066287084Sbapt struct group *grp; 1067287084Sbapt uintmax_t id; 1068287084Sbapt 1069287084Sbapt if ((grp = GETGRNAM(name)) == NULL) { 1070287084Sbapt id = strtounum(name, 0, GID_MAX, &errstr); 1071287084Sbapt if (errstr) 1072287084Sbapt errx(EX_NOUSER, "group `%s' does not exist", name); 1073287084Sbapt grp = GETGRGID(id); 1074287084Sbapt if (grp == NULL) 1075287084Sbapt errx(EX_NOUSER, "group `%s' does not exist", name); 1076287084Sbapt } 1077287084Sbapt 1078287084Sbapt return (grp); 1079287084Sbapt} 1080287084Sbapt 1081287084Sbaptstatic void 1082287084Sbaptsplit_groups(StringList **groups, char *groupsstr) 1083287084Sbapt{ 1084287084Sbapt struct group *grp; 1085287084Sbapt char *p; 1086287084Sbapt char tok[] = ", \t"; 1087287084Sbapt 1088287084Sbapt for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) { 1089287084Sbapt grp = group_from_name_or_id(p); 1090287084Sbapt if (*groups == NULL) 1091287084Sbapt *groups = sl_init(); 1092287084Sbapt sl_add(*groups, newstr(grp->gr_name)); 1093287084Sbapt } 1094287084Sbapt} 1095287084Sbapt 1096287084Sbaptstatic void 1097287084Sbaptvalidate_grname(struct userconf *cnf, char *group) 1098287084Sbapt{ 1099287084Sbapt struct group *grp; 1100287084Sbapt 1101287084Sbapt if (group == NULL || *group == '\0') { 1102287084Sbapt cnf->default_group = ""; 1103287084Sbapt return; 1104287084Sbapt } 1105287084Sbapt grp = group_from_name_or_id(group); 1106287084Sbapt cnf->default_group = newstr(grp->gr_name); 1107287084Sbapt} 1108287084Sbapt 1109287084Sbaptstatic mode_t 1110287084Sbaptvalidate_mode(char *mode) 1111287084Sbapt{ 1112287084Sbapt mode_t m; 1113287084Sbapt void *set; 1114287084Sbapt 1115287084Sbapt if ((set = setmode(mode)) == NULL) 1116287084Sbapt errx(EX_DATAERR, "invalid directory creation mode '%s'", mode); 1117287084Sbapt 1118287084Sbapt m = getmode(set, _DEF_DIRMODE); 1119287084Sbapt free(set); 1120287084Sbapt return (m); 1121287084Sbapt} 1122287084Sbapt 1123287084Sbaptstatic void 1124287084Sbaptmix_config(struct userconf *cmdcnf, struct userconf *cfg) 1125287084Sbapt{ 1126287084Sbapt 1127287084Sbapt if (cmdcnf->default_password == 0) 1128287084Sbapt cmdcnf->default_password = cfg->default_password; 1129287084Sbapt if (cmdcnf->reuse_uids == 0) 1130287084Sbapt cmdcnf->reuse_uids = cfg->reuse_uids; 1131287084Sbapt if (cmdcnf->reuse_gids == 0) 1132287084Sbapt cmdcnf->reuse_gids = cfg->reuse_gids; 1133287084Sbapt if (cmdcnf->nispasswd == NULL) 1134287084Sbapt cmdcnf->nispasswd = cfg->nispasswd; 1135287084Sbapt if (cmdcnf->dotdir == NULL) 1136287084Sbapt cmdcnf->dotdir = cfg->dotdir; 1137287084Sbapt if (cmdcnf->newmail == NULL) 1138287084Sbapt cmdcnf->newmail = cfg->newmail; 1139287084Sbapt if (cmdcnf->logfile == NULL) 1140287084Sbapt cmdcnf->logfile = cfg->logfile; 1141287084Sbapt if (cmdcnf->home == NULL) 1142287084Sbapt cmdcnf->home = cfg->home; 1143287084Sbapt if (cmdcnf->homemode == 0) 1144287084Sbapt cmdcnf->homemode = cfg->homemode; 1145287084Sbapt if (cmdcnf->shelldir == NULL) 1146287084Sbapt cmdcnf->shelldir = cfg->shelldir; 1147287084Sbapt if (cmdcnf->shells == NULL) 1148287084Sbapt cmdcnf->shells = cfg->shells; 1149287084Sbapt if (cmdcnf->shell_default == NULL) 1150287084Sbapt cmdcnf->shell_default = cfg->shell_default; 1151287084Sbapt if (cmdcnf->default_group == NULL) 1152287084Sbapt cmdcnf->default_group = cfg->default_group; 1153287084Sbapt if (cmdcnf->groups == NULL) 1154287084Sbapt cmdcnf->groups = cfg->groups; 1155287084Sbapt if (cmdcnf->default_class == NULL) 1156287084Sbapt cmdcnf->default_class = cfg->default_class; 1157287084Sbapt if (cmdcnf->min_uid == 0) 1158287084Sbapt cmdcnf->min_uid = cfg->min_uid; 1159287084Sbapt if (cmdcnf->max_uid == 0) 1160287084Sbapt cmdcnf->max_uid = cfg->max_uid; 1161287084Sbapt if (cmdcnf->min_gid == 0) 1162287084Sbapt cmdcnf->min_gid = cfg->min_gid; 1163287084Sbapt if (cmdcnf->max_gid == 0) 1164287084Sbapt cmdcnf->max_gid = cfg->max_gid; 1165287084Sbapt if (cmdcnf->expire_days == 0) 1166287084Sbapt cmdcnf->expire_days = cfg->expire_days; 1167287084Sbapt if (cmdcnf->password_days == 0) 1168287084Sbapt cmdcnf->password_days = cfg->password_days; 1169287084Sbapt} 1170287084Sbapt 1171287084Sbaptint 1172287084Sbaptpw_user_add(int argc, char **argv, char *arg1) 1173287084Sbapt{ 1174287084Sbapt struct userconf *cnf, *cmdcnf; 1175287084Sbapt struct passwd *pwd; 1176287084Sbapt struct group *grp; 1177287084Sbapt struct stat st; 1178287084Sbapt char args[] = "C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y"; 1179287084Sbapt char line[_PASSWORD_LEN+1], path[MAXPATHLEN]; 1180287084Sbapt char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname; 1181287084Sbapt char *default_passwd, *name, *p; 1182287084Sbapt const char *cfg; 1183287084Sbapt login_cap_t *lc; 1184287084Sbapt FILE *pfp, *fp; 1185287084Sbapt intmax_t id = -1; 1186287084Sbapt time_t now; 1187287084Sbapt int rc, ch, fd = -1; 1188287084Sbapt size_t i; 1189287084Sbapt bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf; 1190287084Sbapt 1191287084Sbapt dryrun = nis = pretty = quiet = createhome = precrypted = false; 1192287084Sbapt genconf = false; 1193287084Sbapt gecos = homedir = skel = userid = groupid = default_passwd = NULL; 1194287084Sbapt grname = name = NULL; 1195287084Sbapt 1196287084Sbapt if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL) 1197287084Sbapt err(EXIT_FAILURE, "calloc()"); 1198287084Sbapt 1199287084Sbapt if (arg1 != NULL) { 1200287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 1201287084Sbapt id = pw_checkid(arg1, UID_MAX); 1202287084Sbapt else 1203287084Sbapt name = arg1; 1204287084Sbapt } 1205287084Sbapt 1206287084Sbapt while ((ch = getopt(argc, argv, args)) != -1) { 1207287084Sbapt switch (ch) { 1208287084Sbapt case 'C': 1209287084Sbapt cfg = optarg; 1210287084Sbapt break; 1211287084Sbapt case 'q': 1212287084Sbapt quiet = true; 1213287084Sbapt break; 1214287084Sbapt case 'n': 1215287084Sbapt name = optarg; 1216287084Sbapt break; 1217287084Sbapt case 'u': 1218287084Sbapt userid = optarg; 1219287084Sbapt break; 1220287084Sbapt case 'c': 1221287084Sbapt gecos = pw_checkname(optarg, 1); 1222287084Sbapt break; 1223287084Sbapt case 'd': 1224287084Sbapt homedir = optarg; 1225287084Sbapt break; 1226287084Sbapt case 'e': 1227287084Sbapt now = time(NULL); 1228287084Sbapt cmdcnf->expire_days = parse_date(now, optarg); 1229287084Sbapt break; 1230287084Sbapt case 'p': 1231287084Sbapt now = time(NULL); 1232287084Sbapt cmdcnf->password_days = parse_date(now, optarg); 1233287084Sbapt break; 1234287084Sbapt case 'g': 1235287084Sbapt validate_grname(cmdcnf, optarg); 1236287084Sbapt grname = optarg; 1237287084Sbapt break; 1238287084Sbapt case 'G': 1239287084Sbapt split_groups(&cmdcnf->groups, optarg); 1240287084Sbapt break; 1241287084Sbapt case 'm': 1242287084Sbapt createhome = true; 1243287084Sbapt break; 1244287084Sbapt case 'M': 1245287084Sbapt cmdcnf->homemode = validate_mode(optarg); 1246287084Sbapt break; 1247287084Sbapt case 'k': 1248287084Sbapt walk = skel = optarg; 1249287084Sbapt if (*walk == '/') 1250287084Sbapt walk++; 1251287084Sbapt if (fstatat(conf.rootfd, walk, &st, 0) == -1) 1252287084Sbapt errx(EX_OSFILE, "skeleton `%s' does not " 1253287084Sbapt "exists", skel); 1254287084Sbapt if (!S_ISDIR(st.st_mode)) 1255287084Sbapt errx(EX_OSFILE, "skeleton `%s' is not a " 1256287084Sbapt "directory", skel); 1257287084Sbapt cmdcnf->dotdir = skel; 1258287084Sbapt break; 1259287084Sbapt case 's': 1260287084Sbapt cmdcnf->shell_default = optarg; 1261287084Sbapt break; 1262287084Sbapt case 'o': 1263287084Sbapt conf.checkduplicate = false; 1264287084Sbapt break; 1265287084Sbapt case 'L': 1266287084Sbapt cmdcnf->default_class = pw_checkname(optarg, 0); 1267287084Sbapt break; 1268287084Sbapt case 'i': 1269287084Sbapt groupid = optarg; 1270287084Sbapt break; 1271287084Sbapt case 'w': 1272287084Sbapt default_passwd = optarg; 1273287084Sbapt break; 1274287084Sbapt case 'H': 1275287084Sbapt if (fd != -1) 1276287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1277287084Sbapt "exclusive options"); 1278287084Sbapt fd = pw_checkfd(optarg); 1279287084Sbapt precrypted = true; 1280287084Sbapt if (fd == '-') 1281287084Sbapt errx(EX_USAGE, "-H expects a file descriptor"); 1282287084Sbapt break; 1283287084Sbapt case 'h': 1284287084Sbapt if (fd != -1) 1285287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1286287084Sbapt "exclusive options"); 1287287084Sbapt fd = pw_checkfd(optarg); 1288287084Sbapt break; 1289287084Sbapt case 'D': 1290287084Sbapt genconf = true; 1291287084Sbapt break; 1292287084Sbapt case 'b': 1293287084Sbapt cmdcnf->home = optarg; 1294287084Sbapt break; 1295287084Sbapt case 'N': 1296287084Sbapt dryrun = true; 1297287084Sbapt break; 1298287084Sbapt case 'P': 1299287084Sbapt pretty = true; 1300287084Sbapt break; 1301287084Sbapt case 'y': 1302287084Sbapt cmdcnf->nispasswd = optarg; 1303287084Sbapt break; 1304287084Sbapt case 'Y': 1305287084Sbapt nis = true; 1306287084Sbapt break; 1307287084Sbapt } 1308287084Sbapt } 1309287084Sbapt 1310287084Sbapt if (geteuid() != 0 && ! dryrun) 1311287084Sbapt errx(EX_NOPERM, "you must be root"); 1312287084Sbapt 1313287084Sbapt if (quiet) 1314287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 1315287084Sbapt 1316287084Sbapt cnf = get_userconfig(cfg); 1317287084Sbapt 1318287084Sbapt mix_config(cmdcnf, cnf); 1319287084Sbapt if (default_passwd) 1320287084Sbapt cmdcnf->default_password = boolean_val(default_passwd, 1321287084Sbapt cnf->default_password); 1322287084Sbapt if (genconf) { 1323287084Sbapt if (name != NULL) 1324287084Sbapt errx(EX_DATAERR, "can't combine `-D' with `-n name'"); 1325287084Sbapt if (userid != NULL) { 1326287084Sbapt if ((p = strtok(userid, ", \t")) != NULL) 1327287084Sbapt cmdcnf->min_uid = pw_checkid(p, UID_MAX); 1328287084Sbapt if (cmdcnf->min_uid == 0) 1329287084Sbapt cmdcnf->min_uid = 1000; 1330287084Sbapt if ((p = strtok(NULL, " ,\t")) != NULL) 1331287084Sbapt cmdcnf->max_uid = pw_checkid(p, UID_MAX); 1332287084Sbapt if (cmdcnf->max_uid == 0) 1333287084Sbapt cmdcnf->max_uid = 32000; 1334287084Sbapt } 1335287084Sbapt if (groupid != NULL) { 1336287084Sbapt if ((p = strtok(groupid, ", \t")) != NULL) 1337287084Sbapt cmdcnf->min_gid = pw_checkid(p, GID_MAX); 1338287084Sbapt if (cmdcnf->min_gid == 0) 1339287084Sbapt cmdcnf->min_gid = 1000; 1340287084Sbapt if ((p = strtok(NULL, " ,\t")) != NULL) 1341287084Sbapt cmdcnf->max_gid = pw_checkid(p, GID_MAX); 1342287084Sbapt if (cmdcnf->max_gid == 0) 1343287084Sbapt cmdcnf->max_gid = 32000; 1344287084Sbapt } 1345287084Sbapt if (write_userconfig(cmdcnf, cfg)) 1346287084Sbapt return (EXIT_SUCCESS); 1347287084Sbapt err(EX_IOERR, "config update"); 1348287084Sbapt } 1349287084Sbapt 1350287084Sbapt if (userid) 1351287084Sbapt id = pw_checkid(userid, UID_MAX); 1352287084Sbapt if (id < 0 && name == NULL) 1353287084Sbapt errx(EX_DATAERR, "user name or id required"); 1354287084Sbapt 1355287084Sbapt if (name == NULL) 1356287084Sbapt errx(EX_DATAERR, "login name required"); 1357287084Sbapt 1358287084Sbapt if (GETPWNAM(name) != NULL) 1359287084Sbapt errx(EX_DATAERR, "login name `%s' already exists", name); 1360287084Sbapt 1361287084Sbapt pwd = &fakeuser; 1362287084Sbapt pwd->pw_name = name; 1363287084Sbapt pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : ""; 1364287084Sbapt pwd->pw_uid = pw_uidpolicy(cmdcnf, id); 1365287084Sbapt pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name, 1366287084Sbapt (gid_t) pwd->pw_uid, dryrun); 1367287084Sbapt pwd->pw_change = cmdcnf->password_days; 1368287084Sbapt pwd->pw_expire = cmdcnf->expire_days; 1369287084Sbapt pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name); 1370287084Sbapt pwd->pw_shell = pw_shellpolicy(cmdcnf); 1371287084Sbapt lc = login_getpwclass(pwd); 1372287084Sbapt if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) 1373287084Sbapt warn("setting crypt(3) format"); 1374287084Sbapt login_close(lc); 1375287084Sbapt pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun); 1376287084Sbapt if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 1377287084Sbapt warnx("WARNING: new account `%s' has a uid of 0 " 1378287084Sbapt "(superuser access!)", pwd->pw_name); 1379287084Sbapt if (gecos) 1380287084Sbapt pwd->pw_gecos = gecos; 1381287084Sbapt 1382287084Sbapt if (fd != -1) 1383287084Sbapt pw_set_passwd(pwd, fd, precrypted, false); 1384287084Sbapt 1385287084Sbapt if (dryrun) 1386287084Sbapt return (print_user(pwd, pretty, false)); 1387287084Sbapt 1388287084Sbapt if ((rc = addpwent(pwd)) != 0) { 1389287084Sbapt if (rc == -1) 1390287084Sbapt errx(EX_IOERR, "user '%s' already exists", 1391287084Sbapt pwd->pw_name); 1392287084Sbapt else if (rc != 0) 1393287084Sbapt err(EX_IOERR, "passwd file update"); 1394287084Sbapt } 1395287084Sbapt if (nis && cmdcnf->nispasswd && *cmdcnf->nispasswd == '/') { 1396287084Sbapt printf("%s\n", cmdcnf->nispasswd); 1397287084Sbapt rc = addnispwent(cmdcnf->nispasswd, pwd); 1398287084Sbapt if (rc == -1) 1399287084Sbapt warnx("User '%s' already exists in NIS passwd", 1400287084Sbapt pwd->pw_name); 1401287084Sbapt else if (rc != 0) 1402287084Sbapt warn("NIS passwd update"); 1403287084Sbapt /* NOTE: we treat NIS-only update errors as non-fatal */ 1404287084Sbapt } 1405287084Sbapt 1406287084Sbapt if (cmdcnf->groups != NULL) { 1407287084Sbapt for (i = 0; i < cmdcnf->groups->sl_cur; i++) { 1408287084Sbapt grp = GETGRNAM(cmdcnf->groups->sl_str[i]); 1409287084Sbapt grp = gr_add(grp, pwd->pw_name); 1410287084Sbapt /* 1411287084Sbapt * grp can only be NULL in 2 cases: 1412287084Sbapt * - the new member is already a member 1413287084Sbapt * - a problem with memory occurs 1414287084Sbapt * in both cases we want to skip now. 1415287084Sbapt */ 1416287084Sbapt if (grp == NULL) 1417287084Sbapt continue; 1418287084Sbapt chggrent(grp->gr_name, grp); 1419287084Sbapt free(grp); 1420287084Sbapt } 1421287084Sbapt } 1422287084Sbapt 1423287084Sbapt pwd = GETPWNAM(name); 1424287084Sbapt if (pwd == NULL) 1425287084Sbapt errx(EX_NOUSER, "user '%s' disappeared during update", name); 1426287084Sbapt 1427287084Sbapt grp = GETGRGID(pwd->pw_gid); 1428287084Sbapt pw_log(cnf, M_ADD, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", 1429287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 1430287084Sbapt grp ? grp->gr_name : "unknown", 1431287084Sbapt (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), 1432287084Sbapt pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 1433287084Sbapt 1434287084Sbapt /* 1435287084Sbapt * let's touch and chown the user's mail file. This is not 1436287084Sbapt * strictly necessary under BSD with a 0755 maildir but it also 1437287084Sbapt * doesn't hurt anything to create the empty mailfile 1438287084Sbapt */ 1439287084Sbapt if (PWALTDIR() != PWF_ALT) { 1440287084Sbapt snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, 1441287084Sbapt pwd->pw_name); 1442287084Sbapt /* Preserve contents & mtime */ 1443287084Sbapt close(openat(conf.rootfd, path +1, O_RDWR | O_CREAT, 0600)); 1444287084Sbapt fchownat(conf.rootfd, path + 1, pwd->pw_uid, pwd->pw_gid, 1445287084Sbapt AT_SYMLINK_NOFOLLOW); 1446287084Sbapt } 1447287084Sbapt 1448287084Sbapt /* 1449287084Sbapt * Let's create and populate the user's home directory. Note 1450287084Sbapt * that this also `works' for editing users if -m is used, but 1451287084Sbapt * existing files will *not* be overwritten. 1452287084Sbapt */ 1453287084Sbapt if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir && 1454287084Sbapt *pwd->pw_dir == '/' && pwd->pw_dir[1]) 1455287084Sbapt create_and_populate_homedir(cmdcnf, pwd, cmdcnf->dotdir, 1456287084Sbapt cmdcnf->homemode, false); 1457287084Sbapt 1458287084Sbapt if (!PWALTDIR() && cmdcnf->newmail && *cmdcnf->newmail && 1459287084Sbapt (fp = fopen(cnf->newmail, "r")) != NULL) { 1460287084Sbapt if ((pfp = popen(_PATH_SENDMAIL " -t", "w")) == NULL) 1461287084Sbapt warn("sendmail"); 1462287084Sbapt else { 1463287084Sbapt fprintf(pfp, "From: root\n" "To: %s\n" 1464287084Sbapt "Subject: Welcome!\n\n", pwd->pw_name); 1465287084Sbapt while (fgets(line, sizeof(line), fp) != NULL) { 1466287084Sbapt /* Do substitutions? */ 1467287084Sbapt fputs(line, pfp); 146820747Sdavidn } 1469287084Sbapt pclose(pfp); 1470287084Sbapt pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent", 1471287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid); 147220747Sdavidn } 147321052Sdavidn fclose(fp); 147420747Sdavidn } 1475287084Sbapt 1476287084Sbapt if (nis && nis_update() == 0) 1477287084Sbapt pw_log(cnf, M_ADD, W_USER, "NIS maps updated"); 1478287084Sbapt 1479287084Sbapt return (EXIT_SUCCESS); 148020747Sdavidn} 148120747Sdavidn 1482287084Sbaptint 1483287084Sbaptpw_user_mod(int argc, char **argv, char *arg1) 1484287084Sbapt{ 1485287084Sbapt struct userconf *cnf; 1486287084Sbapt struct passwd *pwd; 1487287084Sbapt struct group *grp; 1488287084Sbapt StringList *groups = NULL; 1489287084Sbapt char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:"; 1490287084Sbapt const char *cfg; 1491287084Sbapt char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell; 1492287084Sbapt char *passwd, *class, *nispasswd; 1493287084Sbapt login_cap_t *lc; 1494287084Sbapt struct stat st; 1495287084Sbapt intmax_t id = -1; 1496287084Sbapt int ch, fd = -1; 1497287084Sbapt size_t i, j; 1498287084Sbapt bool quiet, createhome, pretty, dryrun, nis, edited, docreatehome; 1499287084Sbapt bool precrypted; 1500287084Sbapt mode_t homemode = 0; 1501287084Sbapt time_t expire_days, password_days, now; 1502287084Sbapt 1503287084Sbapt expire_days = password_days = -1; 1504287084Sbapt gecos = homedir = grname = name = newname = skel = shell =NULL; 1505287084Sbapt passwd = NULL; 1506287084Sbapt class = nispasswd = NULL; 1507287084Sbapt quiet = createhome = pretty = dryrun = nis = precrypted = false; 1508287084Sbapt edited = docreatehome = false; 1509287084Sbapt 1510287084Sbapt if (arg1 != NULL) { 1511287084Sbapt if (arg1[strspn(arg1, "0123456789")] == '\0') 1512287084Sbapt id = pw_checkid(arg1, UID_MAX); 1513287084Sbapt else 1514287084Sbapt name = arg1; 1515287084Sbapt } 1516287084Sbapt 1517287084Sbapt while ((ch = getopt(argc, argv, args)) != -1) { 1518287084Sbapt switch (ch) { 1519287084Sbapt case 'C': 1520287084Sbapt cfg = optarg; 1521287084Sbapt break; 1522287084Sbapt case 'q': 1523287084Sbapt quiet = true; 1524287084Sbapt break; 1525287084Sbapt case 'n': 1526287084Sbapt name = optarg; 1527287084Sbapt break; 1528287084Sbapt case 'u': 1529287084Sbapt id = pw_checkid(optarg, UID_MAX); 1530287084Sbapt break; 1531287084Sbapt case 'c': 1532287084Sbapt gecos = pw_checkname(optarg, 1); 1533287084Sbapt break; 1534287084Sbapt case 'd': 1535287084Sbapt homedir = optarg; 1536287084Sbapt break; 1537287084Sbapt case 'e': 1538287084Sbapt now = time(NULL); 1539287084Sbapt expire_days = parse_date(now, optarg); 1540287084Sbapt break; 1541287084Sbapt case 'p': 1542287084Sbapt now = time(NULL); 1543287084Sbapt password_days = parse_date(now, optarg); 1544287084Sbapt break; 1545287084Sbapt case 'g': 1546287084Sbapt group_from_name_or_id(optarg); 1547287084Sbapt grname = optarg; 1548287084Sbapt break; 1549287084Sbapt case 'G': 1550287084Sbapt split_groups(&groups, optarg); 1551287084Sbapt break; 1552287084Sbapt case 'm': 1553287084Sbapt createhome = true; 1554287084Sbapt break; 1555287084Sbapt case 'M': 1556287084Sbapt homemode = validate_mode(optarg); 1557287084Sbapt break; 1558287084Sbapt case 'l': 1559287084Sbapt newname = optarg; 1560287084Sbapt break; 1561287084Sbapt case 'k': 1562287084Sbapt walk = skel = optarg; 1563287084Sbapt if (*walk == '/') 1564287084Sbapt walk++; 1565287084Sbapt if (fstatat(conf.rootfd, walk, &st, 0) == -1) 1566287084Sbapt errx(EX_OSFILE, "skeleton `%s' does not " 1567287084Sbapt "exists", skel); 1568287084Sbapt if (!S_ISDIR(st.st_mode)) 1569287084Sbapt errx(EX_OSFILE, "skeleton `%s' is not a " 1570287084Sbapt "directory", skel); 1571287084Sbapt break; 1572287084Sbapt case 's': 1573287084Sbapt shell = optarg; 1574287084Sbapt break; 1575287084Sbapt case 'w': 1576287084Sbapt passwd = optarg; 1577287084Sbapt break; 1578287084Sbapt case 'L': 1579287084Sbapt class = pw_checkname(optarg, 0); 1580287084Sbapt break; 1581287084Sbapt case 'H': 1582287084Sbapt if (fd != -1) 1583287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1584287084Sbapt "exclusive options"); 1585287084Sbapt fd = pw_checkfd(optarg); 1586287084Sbapt precrypted = true; 1587287084Sbapt if (fd == '-') 1588287084Sbapt errx(EX_USAGE, "-H expects a file descriptor"); 1589287084Sbapt break; 1590287084Sbapt case 'h': 1591287084Sbapt if (fd != -1) 1592287084Sbapt errx(EX_USAGE, "'-h' and '-H' are mutually " 1593287084Sbapt "exclusive options"); 1594287084Sbapt fd = pw_checkfd(optarg); 1595287084Sbapt break; 1596287084Sbapt case 'N': 1597287084Sbapt dryrun = true; 1598287084Sbapt break; 1599287084Sbapt case 'P': 1600287084Sbapt pretty = true; 1601287084Sbapt break; 1602287084Sbapt case 'y': 1603287084Sbapt nispasswd = optarg; 1604287084Sbapt break; 1605287084Sbapt case 'Y': 1606287084Sbapt nis = true; 1607287084Sbapt break; 1608287084Sbapt } 1609287084Sbapt } 1610287084Sbapt 1611287084Sbapt if (geteuid() != 0 && ! dryrun) 1612287084Sbapt errx(EX_NOPERM, "you must be root"); 1613287084Sbapt 1614287084Sbapt if (quiet) 1615287084Sbapt freopen(_PATH_DEVNULL, "w", stderr); 1616287084Sbapt 1617287084Sbapt cnf = get_userconfig(cfg); 1618287084Sbapt 1619287084Sbapt if (id < 0 && name == NULL) 1620287084Sbapt errx(EX_DATAERR, "username or id required"); 1621287084Sbapt 1622287084Sbapt pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); 1623287084Sbapt if (pwd == NULL) { 1624287084Sbapt if (name == NULL) 1625287084Sbapt errx(EX_NOUSER, "no such uid `%ju'", 1626287084Sbapt (uintmax_t) id); 1627287084Sbapt errx(EX_NOUSER, "no such user `%s'", name); 1628287084Sbapt } 1629287084Sbapt 1630287084Sbapt if (name == NULL) 1631287084Sbapt name = pwd->pw_name; 1632287084Sbapt 1633287084Sbapt if (nis && nispasswd == NULL) 1634287084Sbapt nispasswd = cnf->nispasswd; 1635287084Sbapt 1636287084Sbapt if (PWF._altdir == PWF_REGULAR && 1637287084Sbapt ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { 1638287084Sbapt if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 1639287084Sbapt if (!nis && nispasswd && *nispasswd != '/') 1640287084Sbapt errx(EX_NOUSER, "Cannot modify NIS user `%s'", 1641287084Sbapt name); 1642287084Sbapt } else { 1643287084Sbapt errx(EX_NOUSER, "Cannot modify non local user `%s'", 1644287084Sbapt name); 1645287084Sbapt } 1646287084Sbapt } 1647287084Sbapt 1648287084Sbapt if (newname) { 1649287084Sbapt if (strcmp(pwd->pw_name, "root") == 0) 1650287084Sbapt errx(EX_DATAERR, "can't rename `root' account"); 1651287084Sbapt if (strcmp(pwd->pw_name, newname) != 0) { 1652287084Sbapt pwd->pw_name = pw_checkname(newname, 0); 1653287084Sbapt edited = true; 1654287084Sbapt } 1655287084Sbapt } 1656287084Sbapt 1657287084Sbapt if (id > 0 && pwd->pw_uid != id) { 1658287084Sbapt pwd->pw_uid = id; 1659287084Sbapt edited = true; 1660287084Sbapt if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) 1661287084Sbapt errx(EX_DATAERR, "can't change uid of `root' account"); 1662287084Sbapt if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 1663287084Sbapt warnx("WARNING: account `%s' will have a uid of 0 " 1664287084Sbapt "(superuser access!)", pwd->pw_name); 1665287084Sbapt } 1666287084Sbapt 1667287084Sbapt if (grname && pwd->pw_uid != 0) { 1668287084Sbapt grp = GETGRNAM(grname); 1669287084Sbapt if (grp == NULL) 1670287084Sbapt grp = GETGRGID(pw_checkid(grname, GID_MAX)); 1671287084Sbapt if (grp->gr_gid != pwd->pw_gid) { 1672287084Sbapt pwd->pw_gid = grp->gr_gid; 1673287084Sbapt edited = true; 1674287084Sbapt } 1675287084Sbapt } 1676287084Sbapt 1677287084Sbapt if (password_days >= 0 && pwd->pw_change != password_days) { 1678287084Sbapt pwd->pw_change = password_days; 1679287084Sbapt edited = true; 1680287084Sbapt } 1681287084Sbapt 1682287084Sbapt if (expire_days >= 0 && pwd->pw_expire != expire_days) { 1683287084Sbapt pwd->pw_expire = expire_days; 1684287084Sbapt edited = true; 1685287084Sbapt } 1686287084Sbapt 1687287084Sbapt if (shell) { 1688287084Sbapt shell = shell_path(cnf->shelldir, cnf->shells, shell); 1689287084Sbapt if (shell == NULL) 1690287084Sbapt shell = ""; 1691287084Sbapt if (strcmp(shell, pwd->pw_shell) != 0) { 1692287084Sbapt pwd->pw_shell = shell; 1693287084Sbapt edited = true; 1694287084Sbapt } 1695287084Sbapt } 1696287084Sbapt 1697287084Sbapt if (class && strcmp(pwd->pw_class, class) != 0) { 1698287084Sbapt pwd->pw_class = class; 1699287084Sbapt edited = true; 1700287084Sbapt } 1701287084Sbapt 1702287084Sbapt if (homedir && strcmp(pwd->pw_dir, homedir) != 0) { 1703287084Sbapt pwd->pw_dir = homedir; 1704287769Sbapt edited = true; 1705287084Sbapt if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) { 1706287084Sbapt if (!createhome) 1707287084Sbapt warnx("WARNING: home `%s' does not exist", 1708287084Sbapt pwd->pw_dir); 1709287084Sbapt else 1710287084Sbapt docreatehome = true; 1711287084Sbapt } else if (!S_ISDIR(st.st_mode)) { 1712287084Sbapt warnx("WARNING: home `%s' is not a directory", 1713287084Sbapt pwd->pw_dir); 1714287084Sbapt } 1715287084Sbapt } 1716287084Sbapt 1717287084Sbapt if (passwd && conf.fd == -1) { 1718287084Sbapt lc = login_getpwclass(pwd); 1719287084Sbapt if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) 1720287084Sbapt warn("setting crypt(3) format"); 1721287084Sbapt login_close(lc); 1722287084Sbapt cnf->default_password = boolean_val(passwd, 1723287084Sbapt cnf->default_password); 1724287084Sbapt pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun); 1725287084Sbapt edited = true; 1726287084Sbapt } 1727287084Sbapt 1728287084Sbapt if (gecos && strcmp(pwd->pw_gecos, gecos) != 0) { 1729287084Sbapt pwd->pw_gecos = gecos; 1730287084Sbapt edited = true; 1731287084Sbapt } 1732287084Sbapt 1733287084Sbapt if (fd != -1) 1734287084Sbapt edited = pw_set_passwd(pwd, fd, precrypted, true); 1735287084Sbapt 1736287084Sbapt if (dryrun) 1737287084Sbapt return (print_user(pwd, pretty, false)); 1738287084Sbapt 1739287084Sbapt if (edited) /* Only updated this if required */ 1740287084Sbapt perform_chgpwent(name, pwd, nis ? nispasswd : NULL); 1741287084Sbapt /* Now perform the needed changes concern groups */ 1742287084Sbapt if (groups != NULL) { 1743287084Sbapt /* Delete User from groups using old name */ 1744287084Sbapt SETGRENT(); 1745287084Sbapt while ((grp = GETGRENT()) != NULL) { 1746287084Sbapt if (grp->gr_mem == NULL) 1747287084Sbapt continue; 1748287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 1749287084Sbapt if (strcmp(grp->gr_mem[i] , name) != 0) 1750287084Sbapt continue; 1751287084Sbapt for (j = i; grp->gr_mem[j] != NULL ; j++) 1752287084Sbapt grp->gr_mem[j] = grp->gr_mem[j+1]; 1753287084Sbapt chggrent(grp->gr_name, grp); 1754287084Sbapt break; 1755287084Sbapt } 1756287084Sbapt } 1757287084Sbapt ENDGRENT(); 1758287084Sbapt /* Add the user to the needed groups */ 1759287084Sbapt for (i = 0; i < groups->sl_cur; i++) { 1760287084Sbapt grp = GETGRNAM(groups->sl_str[i]); 1761287084Sbapt grp = gr_add(grp, pwd->pw_name); 1762287084Sbapt if (grp == NULL) 1763287084Sbapt continue; 1764287084Sbapt chggrent(grp->gr_name, grp); 1765287084Sbapt free(grp); 1766287084Sbapt } 1767287084Sbapt } 1768287084Sbapt /* In case of rename we need to walk over the different groups */ 1769287084Sbapt if (newname) { 1770287084Sbapt SETGRENT(); 1771287084Sbapt while ((grp = GETGRENT()) != NULL) { 1772287084Sbapt if (grp->gr_mem == NULL) 1773287084Sbapt continue; 1774287084Sbapt for (i = 0; grp->gr_mem[i] != NULL; i++) { 1775287084Sbapt if (strcmp(grp->gr_mem[i], name) != 0) 1776287084Sbapt continue; 1777287084Sbapt grp->gr_mem[i] = newname; 1778287084Sbapt chggrent(grp->gr_name, grp); 1779287084Sbapt break; 1780287084Sbapt } 1781287084Sbapt } 1782287084Sbapt } 1783287084Sbapt 1784287084Sbapt /* go get a current version of pwd */ 1785287084Sbapt if (newname) 1786287084Sbapt name = newname; 1787287084Sbapt pwd = GETPWNAM(name); 1788287084Sbapt if (pwd == NULL) 1789287084Sbapt errx(EX_NOUSER, "user '%s' disappeared during update", name); 1790287084Sbapt grp = GETGRGID(pwd->pw_gid); 1791287084Sbapt pw_log(cnf, M_UPDATE, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", 1792287084Sbapt pwd->pw_name, (uintmax_t)pwd->pw_uid, 1793287084Sbapt grp ? grp->gr_name : "unknown", 1794287084Sbapt (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), 1795287084Sbapt pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 1796287084Sbapt 1797287084Sbapt /* 1798287084Sbapt * Let's create and populate the user's home directory. Note 1799287084Sbapt * that this also `works' for editing users if -m is used, but 1800287084Sbapt * existing files will *not* be overwritten. 1801287084Sbapt */ 1802287084Sbapt if (PWALTDIR() != PWF_ALT && docreatehome && pwd->pw_dir && 1803287084Sbapt *pwd->pw_dir == '/' && pwd->pw_dir[1]) { 1804287084Sbapt if (!skel) 1805287084Sbapt skel = cnf->dotdir; 1806287084Sbapt if (homemode == 0) 1807287084Sbapt homemode = cnf->homemode; 1808287084Sbapt create_and_populate_homedir(cnf, pwd, skel, homemode, true); 1809287084Sbapt } 1810287084Sbapt 1811287084Sbapt if (nis && nis_update() == 0) 1812287084Sbapt pw_log(cnf, M_UPDATE, W_USER, "NIS maps updated"); 1813287084Sbapt 1814287084Sbapt return (EXIT_SUCCESS); 1815287084Sbapt} 1816