X-Git-Url: https://git.cameronkatri.com/pw-darwin.git/blobdiff_plain/8d2c73dba6fca1999679ef33013d4c28784d71ba..3de6f8f6ed62ac8b9b8f7635a7c4bb48b06322ca:/pw/pw_user.c diff --git a/pw/pw_user.c b/pw/pw_user.c index d9bce87..02a6a24 100644 --- a/pw/pw_user.c +++ b/pw/pw_user.c @@ -1,4 +1,6 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (C) 1996 * David L. Nugent. All rights reserved. * @@ -31,13 +33,14 @@ static const char rcsid[] = #endif /* not lint */ #include -#include -#include #include +#include +#include #include #include #include +#include #include #include #include @@ -67,7 +70,6 @@ static struct passwd fakeuser = { "User &", "/nonexistent", "/bin/sh", - 0, 0 }; @@ -84,12 +86,79 @@ static char *shell_path(char const * path, char *shells[], char *sh); static void rmat(uid_t uid); static void rmopie(char const * name); +static void +mkdir_home_parents(int dfd, const char *dir) +{ + struct stat st; + char *dirs, *tmp; + + if (*dir != '/') + errx(EX_DATAERR, "invalid base directory for home '%s'", dir); + + dir++; + + if (fstatat(dfd, dir, &st, 0) != -1) { + if (S_ISDIR(st.st_mode)) + return; + errx(EX_OSFILE, "root home `/%s' is not a directory", dir); + } + + dirs = strdup(dir); + if (dirs == NULL) + errx(EX_UNAVAILABLE, "out of memory"); + + tmp = strrchr(dirs, '/'); + if (tmp == NULL) { + free(dirs); + return; + } + tmp[0] = '\0'; + + /* + * This is a kludge especially for Joerg :) + * If the home directory would be created in the root partition, then + * we really create it under /usr which is likely to have more space. + * But we create a symlink from cnf->home -> "/usr" -> cnf->home + */ + if (strchr(dirs, '/') == NULL) { + asprintf(&tmp, "usr/%s", dirs); + if (tmp == NULL) + errx(EX_UNAVAILABLE, "out of memory"); + if (mkdirat(dfd, tmp, _DEF_DIRMODE) != -1 || errno == EEXIST) { + fchownat(dfd, tmp, 0, 0, 0); + symlinkat(tmp, dfd, dirs); + } + free(tmp); + } + tmp = dirs; + if (fstatat(dfd, dirs, &st, 0) == -1) { + while ((tmp = strchr(tmp + 1, '/')) != NULL) { + *tmp = '\0'; + if (fstatat(dfd, dirs, &st, 0) == -1) { + if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) + err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); + } + *tmp = '/'; + } + } + if (fstatat(dfd, dirs, &st, 0) == -1) { + if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) + err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); + fchownat(dfd, dirs, 0, 0, 0); + } + + free(dirs); +} + static void create_and_populate_homedir(struct userconf *cnf, struct passwd *pwd, const char *skeldir, mode_t homemode, bool update) { int skelfd = -1; + /* Create home parents directories */ + mkdir_home_parents(conf.rootfd, pwd->pw_dir); + if (skeldir != NULL && *skeldir != '\0') { if (*skeldir == '/') skeldir++; @@ -206,7 +275,7 @@ pw_userlock(char *arg1, int mode) char *passtmp = NULL; char *name; bool locked = false; - uid_t id; + uid_t id = (uid_t)-1; if (geteuid() != 0) errx(EX_NOPERM, "you must be root"); @@ -214,15 +283,19 @@ pw_userlock(char *arg1, int mode) if (arg1 == NULL) errx(EX_DATAERR, "username or id required"); - if (arg1[strspn(arg1, "0123456789")] == '\0') - id = pw_checkid(arg1, UID_MAX); - else - name = arg1; + name = arg1; + if (arg1[strspn(name, "0123456789")] == '\0') + id = pw_checkid(name, UID_MAX); - pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); + pwd = GETPWNAM(pw_checkname(name, 0)); + if (pwd == NULL && id != (uid_t)-1) { + pwd = GETPWUID(id); + if (pwd != NULL) + name = pwd->pw_name; + } if (pwd == NULL) { - if (name == NULL) - errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id); + if (id == (uid_t)-1) + errx(EX_NOUSER, "no such name or uid `%ju'", (uintmax_t) id); errx(EX_NOUSER, "no such user `%s'", name); } @@ -409,29 +482,22 @@ pw_shellpolicy(struct userconf * cnf) return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); } -#define SALTSIZE 32 static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; char * pw_pwcrypt(char *password) { - int i; - char salt[SALTSIZE + 1]; char *cryptpw; static char buf[256]; + size_t pwlen; - /* - * Calculate a salt value - */ - for (i = 0; i < SALTSIZE; i++) - salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; - salt[SALTSIZE] = '\0'; - - cryptpw = crypt(password, salt); + cryptpw = crypt(password, crypt_gensalt("$6$", 0, chars, strlen(chars))); if (cryptpw == NULL) errx(EX_CONFIG, "crypt(3) failure"); - return strcpy(buf, cryptpw); + pwlen = strlcpy(buf, cryptpw, sizeof(buf)); + assert(pwlen < sizeof(buf)); + return (buf); } static char * @@ -441,7 +507,9 @@ pw_password(struct userconf * cnf, char const * user, bool dryrun) char pwbuf[32]; switch (cnf->default_password) { - case -1: /* Random password */ + case P_NONE: /* No password at all! */ + return ""; + case P_RANDOM: /* Random password */ l = (arc4random() % 8 + 8); /* 8 - 16 chars */ for (i = 0; i < l; i++) pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; @@ -457,17 +525,13 @@ pw_password(struct userconf * cnf, char const * user, bool dryrun) fflush(stdout); } break; - - case -2: /* No password at all! */ - return ""; - - case 0: /* No login - default */ - default: - return "*"; - - case 1: /* user's name */ + case P_YES: /* user's name */ strlcpy(pwbuf, user, sizeof(pwbuf)); break; + case P_NO: /* No login - default */ + /* FALLTHROUGH */ + default: + return "*"; } return pw_pwcrypt(pwbuf); } @@ -515,7 +579,7 @@ print_user(struct passwd * pwd, bool pretty, bool v7) } if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) strftime(acexpire, sizeof acexpire, "%c", tptr); - if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) + if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) strftime(pwexpire, sizeof pwexpire, "%c", tptr); printf("Login Name: %-15s #%-12ju Group: %-15s #%ju\n" " Full Name: %s\n" @@ -558,7 +622,7 @@ pw_checkname(char *name, int gecos) reject = 0; if (gecos) { /* See if the name is valid as a gecos (comment) field. */ - badchars = ":!@"; + badchars = ":"; showtype = "gecos field"; } else { /* See if the name is valid as a userid or group. */ @@ -570,7 +634,8 @@ pw_checkname(char *name, int gecos) } if (!reject) { while (*ch) { - if (strchr(badchars, *ch) != NULL || *ch < ' ' || + if (strchr(badchars, *ch) != NULL || + (!gecos && *ch < ' ') || *ch == 127) { reject = 1; break; @@ -637,24 +702,24 @@ rmopie(char const * name) { char tmp[1014]; FILE *fp; - int fd; size_t len; - off_t atofs = 0; - + long atofs; + int fd; + if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1) return; fp = fdopen(fd, "r+"); len = strlen(name); - while (fgets(tmp, sizeof(tmp), fp) != NULL) { + for (atofs = 0; fgets(tmp, sizeof(tmp), fp) != NULL && atofs >= 0; + atofs = ftell(fp)) { if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') { /* Comment username out */ if (fseek(fp, atofs, SEEK_SET) == 0) fwrite("#", 1, 1, fp); break; } - atofs = ftell(fp); } /* * If we got an error of any sort, don't update! @@ -671,7 +736,7 @@ pw_user_next(int argc, char **argv, char *name __unused) bool quiet = false; uid_t next; - while ((ch = getopt(argc, argv, "Cq")) != -1) { + while ((ch = getopt(argc, argv, "C:q")) != -1) { switch (ch) { case 'C': cfg = optarg; @@ -738,7 +803,7 @@ pw_user_show(int argc, char **argv, char *arg1) case 'a': all = true; break; - case 7: + case '7': v7 = true; break; } @@ -843,24 +908,15 @@ pw_user_del(int argc, char **argv, char *arg1) errx(EX_NOUSER, "no such user `%s'", name); } - if (PWF._altdir == PWF_REGULAR && - ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { - if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { - if (!nis && nispasswd && *nispasswd != '/') - errx(EX_NOUSER, "Cannot remove NIS user `%s'", - name); - } else { - errx(EX_NOUSER, "Cannot remove non local user `%s'", - name); - } - } - id = pwd->pw_uid; if (name == NULL) name = pwd->pw_name; - if (strcmp(pwd->pw_name, "root") == 0) - errx(EX_DATAERR, "cannot remove user 'root'"); + char *system_users[30] = {"nobody", "root", "mobile", "daemon", "_ftp", "_networkd", "_wireless", "_installd", "_neagent", "_ifccd", "_securityd", "_mdnsresponder", "_sshd", "_unknown", "_distnote", "_astris", "_ondemand", "_findmydevice", "_datadetectors", "_captiveagent", "_analyticsd", "_timed", "_gpsd", "_reportmemoryexception", "_diskimagesiod", "_logd", "_iconservices", "_fud", "_knowledgegraphd", "_coreml"}; + for (int i = 0; i < 30; i++) { + if (strcmp(pwd->pw_name, system_users[i]) == 0) + errx(EX_DATAERR, "cannot remove user '%s'", system_users[i]); + } /* Remove opie record from /etc/opiekeys */ if (PWALTDIR() != PWF_ALT) @@ -940,7 +996,6 @@ pw_user_del(int argc, char **argv, char *arg1) /* Remove home directory and contents */ if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' && - GETPWUID(id) == NULL && fstatat(conf.rootfd, home + 1, &st, 0) != -1) { rm_r(conf.rootfd, home, id); pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s" @@ -1012,10 +1067,10 @@ split_groups(StringList **groups, char *groupsstr) char *p; char tok[] = ", \t"; + if (*groups == NULL) + *groups = sl_init(); for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) { grp = group_from_name_or_id(p); - if (*groups == NULL) - *groups = sl_init(); sl_add(*groups, newstr(grp->gr_name)); } } @@ -1047,11 +1102,20 @@ validate_mode(char *mode) return (m); } +static long +validate_expire(char *str, int opt) +{ + if (!numerics(str)) + errx(EX_DATAERR, "-%c argument must be numeric " + "when setting defaults: %s", (char)opt, str); + return strtol(str, NULL, 0); +} + static void mix_config(struct userconf *cmdcnf, struct userconf *cfg) { - if (cmdcnf->default_password == 0) + if (cmdcnf->default_password < 0) cmdcnf->default_password = cfg->default_password; if (cmdcnf->reuse_uids == 0) cmdcnf->reuse_uids = cfg->reuse_uids; @@ -1089,9 +1153,9 @@ mix_config(struct userconf *cmdcnf, struct userconf *cfg) cmdcnf->min_gid = cfg->min_gid; if (cmdcnf->max_gid == 0) cmdcnf->max_gid = cfg->max_gid; - if (cmdcnf->expire_days == 0) + if (cmdcnf->expire_days < 0) cmdcnf->expire_days = cfg->expire_days; - if (cmdcnf->password_days == 0) + if (cmdcnf->password_days < 0) cmdcnf->password_days = cfg->password_days; } @@ -1106,7 +1170,7 @@ pw_user_add(int argc, char **argv, char *arg1) char line[_PASSWORD_LEN+1], path[MAXPATHLEN]; char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname; char *default_passwd, *name, *p; - const char *cfg; + const char *cfg = NULL; login_cap_t *lc; FILE *pfp, *fp; intmax_t id = -1; @@ -1123,11 +1187,14 @@ pw_user_add(int argc, char **argv, char *arg1) if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL) err(EXIT_FAILURE, "calloc()"); + cmdcnf->default_password = cmdcnf->expire_days = cmdcnf->password_days = -1; + now = time(NULL); + if (arg1 != NULL) { if (arg1[strspn(arg1, "0123456789")] == '\0') id = pw_checkid(arg1, UID_MAX); else - name = arg1; + name = pw_checkname(arg1, 0); } while ((ch = getopt(argc, argv, args)) != -1) { @@ -1139,7 +1206,7 @@ pw_user_add(int argc, char **argv, char *arg1) quiet = true; break; case 'n': - name = optarg; + name = pw_checkname(optarg, 0); break; case 'u': userid = optarg; @@ -1151,12 +1218,16 @@ pw_user_add(int argc, char **argv, char *arg1) homedir = optarg; break; case 'e': - now = time(NULL); - cmdcnf->expire_days = parse_date(now, optarg); + if (genconf) + cmdcnf->expire_days = validate_expire(optarg, ch); + else + cmdcnf->expire_days = parse_date(now, optarg); break; case 'p': - now = time(NULL); - cmdcnf->password_days = parse_date(now, optarg); + if (genconf) + cmdcnf->password_days = validate_expire(optarg, ch); + else + cmdcnf->password_days = parse_date(now, optarg); break; case 'g': validate_grname(cmdcnf, optarg); @@ -1244,7 +1315,7 @@ pw_user_add(int argc, char **argv, char *arg1) mix_config(cmdcnf, cnf); if (default_passwd) - cmdcnf->default_password = boolean_val(default_passwd, + cmdcnf->default_password = passwd_val(default_passwd, cnf->default_password); if (genconf) { if (name != NULL) @@ -1285,14 +1356,24 @@ pw_user_add(int argc, char **argv, char *arg1) if (GETPWNAM(name) != NULL) errx(EX_DATAERR, "login name `%s' already exists", name); + if (!grname) + grname = cmdcnf->default_group; + pwd = &fakeuser; pwd->pw_name = name; pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : ""; pwd->pw_uid = pw_uidpolicy(cmdcnf, id); pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name, (gid_t) pwd->pw_uid, dryrun); - pwd->pw_change = cmdcnf->password_days; - pwd->pw_expire = cmdcnf->expire_days; + + split_groups(&cmdcnf->groups, grname); + + /* cmdcnf->password_days and cmdcnf->expire_days hold unixtime here */ + if (cmdcnf->password_days > 0) + pwd->pw_change = cmdcnf->password_days; + if (cmdcnf->expire_days > 0) + pwd->pw_expire = cmdcnf->expire_days; + pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name); pwd->pw_shell = pw_shellpolicy(cmdcnf); lc = login_getpwclass(pwd); @@ -1414,7 +1495,7 @@ pw_user_mod(int argc, char **argv, char *arg1) struct group *grp; StringList *groups = NULL; char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:"; - const char *cfg; + const char *cfg = NULL; char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell; char *passwd, *class, *nispasswd; login_cap_t *lc; @@ -1422,17 +1503,18 @@ pw_user_mod(int argc, char **argv, char *arg1) intmax_t id = -1; int ch, fd = -1; size_t i, j; - bool quiet, createhome, pretty, dryrun, nis, edited, docreatehome; + bool quiet, createhome, pretty, dryrun, nis, edited; bool precrypted; mode_t homemode = 0; - time_t expire_days, password_days, now; + time_t expire_time, password_time, now; - expire_days = password_days = -1; + expire_time = password_time = -1; gecos = homedir = grname = name = newname = skel = shell =NULL; passwd = NULL; class = nispasswd = NULL; quiet = createhome = pretty = dryrun = nis = precrypted = false; - edited = docreatehome = false; + edited = false; + now = time(NULL); if (arg1 != NULL) { if (arg1[strspn(arg1, "0123456789")] == '\0') @@ -1462,12 +1544,10 @@ pw_user_mod(int argc, char **argv, char *arg1) homedir = optarg; break; case 'e': - now = time(NULL); - expire_days = parse_date(now, optarg); + expire_time = parse_date(now, optarg); break; case 'p': - now = time(NULL); - password_days = parse_date(now, optarg); + password_time = parse_date(now, optarg); break; case 'g': group_from_name_or_id(optarg); @@ -1560,18 +1640,6 @@ pw_user_mod(int argc, char **argv, char *arg1) if (nis && nispasswd == NULL) nispasswd = cnf->nispasswd; - if (PWF._altdir == PWF_REGULAR && - ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { - if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { - if (!nis && nispasswd && *nispasswd != '/') - errx(EX_NOUSER, "Cannot modify NIS user `%s'", - name); - } else { - errx(EX_NOUSER, "Cannot modify non local user `%s'", - name); - } - } - if (newname) { if (strcmp(pwd->pw_name, "root") == 0) errx(EX_DATAERR, "can't rename `root' account"); @@ -1581,7 +1649,7 @@ pw_user_mod(int argc, char **argv, char *arg1) } } - if (id > 0 && pwd->pw_uid != id) { + if (id >= 0 && pwd->pw_uid != id) { pwd->pw_uid = id; edited = true; if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) @@ -1601,13 +1669,14 @@ pw_user_mod(int argc, char **argv, char *arg1) } } - if (password_days >= 0 && pwd->pw_change != password_days) { - pwd->pw_change = password_days; + + if (password_time >= 0 && pwd->pw_change != password_time) { + pwd->pw_change = password_time; edited = true; } - if (expire_days >= 0 && pwd->pw_expire != expire_days) { - pwd->pw_expire = expire_days; + if (expire_time >= 0 && pwd->pw_expire != expire_time) { + pwd->pw_expire = expire_time; edited = true; } @@ -1628,12 +1697,11 @@ pw_user_mod(int argc, char **argv, char *arg1) if (homedir && strcmp(pwd->pw_dir, homedir) != 0) { pwd->pw_dir = homedir; + edited = true; if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) { if (!createhome) warnx("WARNING: home `%s' does not exist", pwd->pw_dir); - else - docreatehome = true; } else if (!S_ISDIR(st.st_mode)) { warnx("WARNING: home `%s' is not a directory", pwd->pw_dir); @@ -1645,6 +1713,8 @@ pw_user_mod(int argc, char **argv, char *arg1) if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) warn("setting crypt(3) format"); login_close(lc); + cnf->default_password = passwd_val(passwd, + cnf->default_password); pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun); edited = true; } @@ -1723,7 +1793,7 @@ pw_user_mod(int argc, char **argv, char *arg1) * that this also `works' for editing users if -m is used, but * existing files will *not* be overwritten. */ - if (PWALTDIR() != PWF_ALT && docreatehome && pwd->pw_dir && + if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir && *pwd->pw_dir == '/' && pwd->pw_dir[1]) { if (!skel) skel = cnf->dotdir;