Index: stable/10/etc/mtree/BSD.tests.dist =================================================================== --- stable/10/etc/mtree/BSD.tests.dist (revision 274081) +++ stable/10/etc/mtree/BSD.tests.dist (revision 274082) @@ -1,282 +1,284 @@ # $FreeBSD$ # # Please see the file src/etc/mtree/README before making changes to this file. # /set type=dir uname=root gname=wheel mode=0755 . include atf-c .. atf-c++ .. .. share atf .. doc atf .. .. .. tests bin date .. mv .. pax .. pkill .. sh builtins .. errors .. execution .. expansion .. parameters .. parser .. set-e .. .. test .. .. cddl lib .. sbin .. usr.bin .. usr.sbin .. .. etc .. games .. gnu lib .. usr.bin .. .. lib atf libatf-c detail .. .. libatf-c++ detail .. .. test-programs .. .. libcrypt .. libmp .. libutil .. .. libexec atf atf-check .. atf-sh .. .. .. sbin dhclient .. devd .. growfs .. mdconfig .. .. secure lib .. libexec .. usr.bin .. usr.sbin .. .. share examples tests atf .. plain .. .. .. .. sys kern .. netinet .. .. usr.bin apply .. calendar .. comm .. file2c .. join .. jot .. lastcomm .. m4 .. make archives fmt_44bsd .. fmt_44bsd_mod .. fmt_oldbsd .. .. basic t0 .. t1 .. t2 .. t3 .. .. execution ellipsis .. empty .. joberr .. plus .. .. shell builtin .. meta .. path .. path_select .. replace .. select .. .. suffixes basic .. src_wild1 .. src_wild2 .. .. syntax directive-t0 .. enl .. funny-targets .. semi .. .. sysmk t0 2 1 .. .. mk .. .. t1 2 1 .. .. mk .. .. t2 2 1 .. .. mk .. .. .. variables modifier_M .. modifier_t .. opt_V .. t0 .. .. .. ncal .. printf .. sed regress.multitest.out .. .. tr .. uudecode .. uuencode .. xargs .. yacc yacc .. .. .. usr.sbin etcupdate .. newsyslog .. + pw + .. sa .. .. .. .. # vim: set expandtab ts=4 sw=4: Index: stable/10/lib/libutil/gr_util.c =================================================================== --- stable/10/lib/libutil/gr_util.c (revision 274081) +++ stable/10/lib/libutil/gr_util.c (revision 274082) @@ -1,631 +1,638 @@ /*- * Copyright (c) 2008 Sean C. Farley * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int lockfd = -1; static char group_dir[PATH_MAX]; static char group_file[PATH_MAX]; static char tempname[PATH_MAX]; static int initialized; static size_t grmemlen(const struct group *, const char *, int *); static struct group *grcopy(const struct group *gr, char *mem, const char *, int ndx); /* * Initialize statics */ int gr_init(const char *dir, const char *group) { if (dir == NULL) { strcpy(group_dir, _PATH_ETC); } else { if (strlen(dir) >= sizeof(group_dir)) { errno = ENAMETOOLONG; return (-1); } strcpy(group_dir, dir); } if (group == NULL) { if (dir == NULL) { strcpy(group_file, _PATH_GROUP); } else if (snprintf(group_file, sizeof(group_file), "%s/group", group_dir) > (int)sizeof(group_file)) { errno = ENAMETOOLONG; return (-1); } } else { if (strlen(group) >= sizeof(group_file)) { errno = ENAMETOOLONG; return (-1); } strcpy(group_file, group); } initialized = 1; return (0); } /* * Lock the group file */ int gr_lock(void) { if (*group_file == '\0') return (-1); for (;;) { struct stat st; lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); if (lockfd == -1) { if (errno == EWOULDBLOCK) { errx(1, "the group file is busy"); } else { err(1, "could not lock the group file: "); } } if (fstat(lockfd, &st) == -1) err(1, "fstat() failed: "); if (st.st_nlink != 0) break; close(lockfd); lockfd = -1; } return (lockfd); } /* * Create and open a presmuably safe temp file for editing group data */ int gr_tmp(int mfd) { char buf[8192]; ssize_t nr; const char *p; int tfd; if (*group_file == '\0') return (-1); if ((p = strrchr(group_file, '/'))) ++p; else p = group_file; if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX", (int)(p - group_file), group_file) >= (int)sizeof(tempname)) { errno = ENAMETOOLONG; return (-1); } if ((tfd = mkstemp(tempname)) == -1) return (-1); if (mfd != -1) { while ((nr = read(mfd, buf, sizeof(buf))) > 0) if (write(tfd, buf, (size_t)nr) != nr) break; if (nr != 0) { unlink(tempname); *tempname = '\0'; close(tfd); return (-1); } } return (tfd); } /* * Copy the group file from one descriptor to another, replacing, deleting * or adding a single record on the way. */ int gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr) { char buf[8192], *end, *line, *p, *q, *r, t; struct group *fgr; const struct group *sgr; size_t len; int eof, readlen; - sgr = gr; + if (old_gr == NULL && gr == NULL) + return(-1); + + sgr = old_gr; + /* deleting a group */ if (gr == NULL) { line = NULL; - if (old_gr == NULL) + } else { + if ((line = gr_make(gr)) == NULL) return (-1); - sgr = old_gr; - } else if ((line = gr_make(gr)) == NULL) - return (-1); + } + + /* adding a group */ + if (sgr == NULL) + sgr = gr; eof = 0; len = 0; p = q = end = buf; for (;;) { /* find the end of the current line */ for (p = q; q < end && *q != '\0'; ++q) if (*q == '\n') break; /* if we don't have a complete line, fill up the buffer */ if (q >= end) { if (eof) break; if ((size_t)(q - p) >= sizeof(buf)) { warnx("group line too long"); errno = EINVAL; /* hack */ goto err; } if (p < end) { q = memmove(buf, p, end -p); end -= p - buf; } else { p = q = end = buf; } readlen = read(ffd, end, sizeof(buf) - (end -buf)); if (readlen == -1) goto err; else len = (size_t)readlen; if (len == 0 && p == buf) break; end += len; len = end - buf; if (len < (ssize_t)sizeof(buf)) { eof = 1; if (len > 0 && buf[len -1] != '\n') ++len, *end++ = '\n'; } continue; } /* is it a blank line or a comment? */ for (r = p; r < q && isspace(*r); ++r) /* nothing */; if (r == q || *r == '#') { /* yep */ if (write(tfd, p, q -p + 1) != q - p + 1) goto err; ++q; continue; } /* is it the one we're looking for? */ t = *q; *q = '\0'; fgr = gr_scan(r); /* fgr is either a struct group for the current line, * or NULL if the line is malformed. */ *q = t; if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) { /* nope */ if (fgr != NULL) free(fgr); if (write(tfd, p, q - p + 1) != q - p + 1) goto err; ++q; continue; } if (old_gr && !gr_equal(fgr, old_gr)) { warnx("entry inconsistent"); free(fgr); errno = EINVAL; /* hack */ goto err; } free(fgr); /* it is, replace or remove it */ if (line != NULL) { len = strlen(line); if (write(tfd, line, len) != (int) len) goto err; } else { /* when removed, avoid the \n */ q++; } /* we're done, just copy the rest over */ for (;;) { if (write(tfd, q, end - q) != end - q) goto err; q = buf; readlen = read(ffd, buf, sizeof(buf)); if (readlen == 0) break; else len = (size_t)readlen; if (readlen == -1) goto err; end = buf + len; } goto done; } /* if we got here, we didn't find the old entry */ if (line == NULL) { errno = ENOENT; goto err; } len = strlen(line); if ((size_t)write(tfd, line, len) != len || write(tfd, "\n", 1) != 1) goto err; done: if (line != NULL) free(line); return (0); err: if (line != NULL) free(line); return (-1); } /* * Regenerate the group file */ int gr_mkdb(void) { if (chmod(tempname, 0644) != 0) return (-1); return (rename(tempname, group_file)); } /* * Clean up. Preserves errno for the caller's convenience. */ void gr_fini(void) { int serrno; if (!initialized) return; initialized = 0; serrno = errno; if (*tempname != '\0') { unlink(tempname); *tempname = '\0'; } if (lockfd != -1) close(lockfd); errno = serrno; } /* * Compares two struct group's. */ int gr_equal(const struct group *gr1, const struct group *gr2) { int gr1_ndx; int gr2_ndx; /* Check that the non-member information is the same. */ if (gr1->gr_name == NULL || gr2->gr_name == NULL) { if (gr1->gr_name != gr2->gr_name) return (false); } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0) return (false); if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) { if (gr1->gr_passwd != gr2->gr_passwd) return (false); } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0) return (false); if (gr1->gr_gid != gr2->gr_gid) return (false); /* Check all members in both groups. * getgrnam can return gr_mem with a pointer to NULL. * gr_dup and gr_add strip out this superfluous NULL, setting * gr_mem to NULL for no members. */ if (gr1->gr_mem != NULL && gr2->gr_mem != NULL) { int i; for (i = 0; gr1->gr_mem[i] != NULL; i++) { if (strcmp(gr1->gr_mem[i], gr2->gr_mem[i]) != 0) return (false); } } /* Count number of members in both structs */ gr2_ndx = 0; if (gr2->gr_mem != NULL) for(; gr2->gr_mem[gr2_ndx] != NULL; gr2_ndx++) /* empty */; gr1_ndx = 0; if (gr1->gr_mem != NULL) for(; gr1->gr_mem[gr1_ndx] != NULL; gr1_ndx++) /* empty */; if (gr1_ndx != gr2_ndx) return (false); return (true); } /* * Make a group line out of a struct group. */ char * gr_make(const struct group *gr) { const char *group_line_format = "%s:%s:%ju:"; const char *sep; char *line; char *p; size_t line_size; int ndx; /* Calculate the length of the group line. */ line_size = snprintf(NULL, 0, group_line_format, gr->gr_name, gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1; if (gr->gr_mem != NULL) { for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) line_size += strlen(gr->gr_mem[ndx]) + 1; if (ndx > 0) line_size--; } /* Create the group line and fill it. */ if ((line = p = malloc(line_size)) == NULL) return (NULL); p += sprintf(p, group_line_format, gr->gr_name, gr->gr_passwd, (uintmax_t)gr->gr_gid); if (gr->gr_mem != NULL) { sep = ""; for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { p = stpcpy(p, sep); p = stpcpy(p, gr->gr_mem[ndx]); sep = ","; } } return (line); } /* * Duplicate a struct group. */ struct group * gr_dup(const struct group *gr) { return (gr_add(gr, NULL)); } /* * Add a new member name to a struct group. */ struct group * gr_add(const struct group *gr, const char *newmember) { char *mem; size_t len; int num_mem; num_mem = 0; len = grmemlen(gr, newmember, &num_mem); /* Create new group and copy old group into it. */ if ((mem = malloc(len)) == NULL) return (NULL); return (grcopy(gr, mem, newmember, num_mem)); } /* It is safer to walk the pointers given at gr_mem since there is no * guarantee the gr_mem + strings are contiguous in the given struct group * but compactify the new group into the following form. * * The new struct is laid out like this in memory. The example given is * for a group with two members only. * * { * (char *name) * (char *passwd) * (int gid) * (gr_mem * newgrp + sizeof(struct group) + sizeof(**)) points to gr_mem area * gr_mem area * (member1 *) * (member2 *) * (NULL) * (name string) * (passwd string) * (member1 string) * (member2 string) * } */ /* * Copy the contents of a group plus given name to a preallocated group struct */ static struct group * grcopy(const struct group *gr, char *dst, const char *name, int ndx) { int i; struct group *newgr; newgr = (struct group *)(void *)dst; /* avoid alignment warning */ dst += sizeof(*newgr); if (ndx != 0) { newgr->gr_mem = (char **)(void *)(dst); /* avoid alignment warning */ dst += (ndx + 1) * sizeof(*newgr->gr_mem); } else newgr->gr_mem = NULL; if (gr->gr_name != NULL) { newgr->gr_name = dst; dst = stpcpy(dst, gr->gr_name) + 1; } else newgr->gr_name = NULL; if (gr->gr_passwd != NULL) { newgr->gr_passwd = dst; dst = stpcpy(dst, gr->gr_passwd) + 1; } else newgr->gr_passwd = NULL; newgr->gr_gid = gr->gr_gid; i = 0; /* Original group struct might have a NULL gr_mem */ if (gr->gr_mem != NULL) { for (; gr->gr_mem[i] != NULL; i++) { newgr->gr_mem[i] = dst; dst = stpcpy(dst, gr->gr_mem[i]) + 1; } } /* If name is not NULL, newgr->gr_mem is known to be not NULL */ if (name != NULL) { newgr->gr_mem[i++] = dst; dst = stpcpy(dst, name) + 1; } /* if newgr->gr_mem is not NULL add NULL marker */ if (newgr->gr_mem != NULL) newgr->gr_mem[i] = NULL; return (newgr); } /* * Calculate length of a struct group + given name */ static size_t grmemlen(const struct group *gr, const char *name, int *num_mem) { size_t len; int i; if (gr == NULL) return (0); /* Calculate size of the group. */ len = sizeof(*gr); if (gr->gr_name != NULL) len += strlen(gr->gr_name) + 1; if (gr->gr_passwd != NULL) len += strlen(gr->gr_passwd) + 1; i = 0; if (gr->gr_mem != NULL) { for (; gr->gr_mem[i] != NULL; i++) { len += strlen(gr->gr_mem[i]) + 1; len += sizeof(*gr->gr_mem); } } if (name != NULL) { i++; len += strlen(name) + 1; len += sizeof(*gr->gr_mem); } /* Allow for NULL pointer */ if (i != 0) len += sizeof(*gr->gr_mem); *num_mem = i; return(len); } /* * Scan a line and place it into a group structure. */ static bool __gr_scan(char *line, struct group *gr) { char *loc; int ndx; /* Assign non-member information to structure. */ gr->gr_name = line; if ((loc = strchr(line, ':')) == NULL) return (false); *loc = '\0'; gr->gr_passwd = loc + 1; if (*gr->gr_passwd == ':') *gr->gr_passwd = '\0'; else { if ((loc = strchr(loc + 1, ':')) == NULL) return (false); *loc = '\0'; } if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1) return (false); /* Assign member information to structure. */ if ((loc = strchr(loc + 1, ':')) == NULL) return (false); line = loc + 1; gr->gr_mem = NULL; ndx = 0; do { gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) * (ndx + 1)); if (gr->gr_mem == NULL) return (false); /* Skip locations without members (i.e., empty string). */ do { gr->gr_mem[ndx] = strsep(&line, ","); } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0'); } while (gr->gr_mem[ndx++] != NULL); return (true); } /* * Create a struct group from a line. */ struct group * gr_scan(const char *line) { struct group gr; char *line_copy; struct group *new_gr; if ((line_copy = strdup(line)) == NULL) return (NULL); if (!__gr_scan(line_copy, &gr)) { free(line_copy); return (NULL); } new_gr = gr_dup(&gr); free(line_copy); if (gr.gr_mem != NULL) free(gr.gr_mem); return (new_gr); } Index: stable/10/usr.sbin/pw/Makefile =================================================================== --- stable/10/usr.sbin/pw/Makefile (revision 274081) +++ stable/10/usr.sbin/pw/Makefile (revision 274082) @@ -1,14 +1,20 @@ # $FreeBSD$ +.include + PROG= pw MAN= pw.conf.5 pw.8 SRCS= pw.c pw_conf.c pw_user.c pw_group.c pw_log.c pw_nis.c pw_vpw.c \ grupd.c pwupd.c fileupd.c psdate.c \ bitmap.c cpdir.c rm_r.c -WARNS?= 2 +WARNS?= 3 DPADD= ${LIBCRYPT} ${LIBUTIL} LDADD= -lcrypt -lutil + +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif .include Index: stable/10/usr.sbin/pw/pw_group.c =================================================================== --- stable/10/usr.sbin/pw/pw_group.c (revision 274081) +++ stable/10/usr.sbin/pw/pw_group.c (revision 274082) @@ -1,430 +1,438 @@ /*- * Copyright (C) 1996 * David L. Nugent. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include "pw.h" #include "bitmap.h" static struct passwd *lookup_pwent(const char *user); static void delete_members(char ***members, int *grmembers, int *i, struct carg *arg, struct group *grp); static int print_group(struct group * grp, int pretty); static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args); int pw_group(struct userconf * cnf, int mode, struct cargs * args) { int rc; + struct carg *a_newname = getarg(args, 'l'); struct carg *a_name = getarg(args, 'n'); struct carg *a_gid = getarg(args, 'g'); struct carg *arg; struct group *grp = NULL; int grmembers = 0; char **members = NULL; static struct group fakegroup = { "nogroup", "*", -1, NULL }; + if (a_gid != NULL) { + if (strspn(a_gid->val, "0123456789") != strlen(a_gid->val)) + errx(EX_USAGE, "-g expects a number"); + } + if (mode == M_LOCK || mode == M_UNLOCK) errx(EX_USAGE, "'lock' command is not available for groups"); /* * With M_NEXT, we only need to return the * next gid to stdout */ if (mode == M_NEXT) { gid_t next = gr_gidpolicy(cnf, args); if (getarg(args, 'q')) return next; printf("%ld\n", (long)next); return EXIT_SUCCESS; } if (mode == M_PRINT && getarg(args, 'a')) { int pretty = getarg(args, 'P') != NULL; SETGRENT(); while ((grp = GETGRENT()) != NULL) print_group(grp, pretty); ENDGRENT(); return EXIT_SUCCESS; } if (a_gid == NULL) { if (a_name == NULL) errx(EX_DATAERR, "group name or id required"); if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) { (a_gid = a_name)->ch = 'g'; a_name = NULL; } } grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val)); if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) { if (a_name == NULL && grp == NULL) /* Try harder */ grp = GETGRGID(atoi(a_gid->val)); if (grp == NULL) { if (mode == M_PRINT && getarg(args, 'F')) { char *fmems[1]; fmems[0] = NULL; fakegroup.gr_name = a_name ? a_name->val : "nogroup"; fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1; fakegroup.gr_mem = fmems; return print_group(&fakegroup, getarg(args, 'P') != NULL); } errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val); } if (a_name == NULL) /* Needed later */ a_name = addarg(args, 'n', grp->gr_name); /* * Handle deletions now */ if (mode == M_DELETE) { gid_t gid = grp->gr_gid; rc = delgrent(grp); if (rc == -1) err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name); else if (rc != 0) { warn("group update"); return EX_IOERR; } pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid); return EXIT_SUCCESS; } else if (mode == M_PRINT) return print_group(grp, getarg(args, 'P') != NULL); if (a_gid) grp->gr_gid = (gid_t) atoi(a_gid->val); - if ((arg = getarg(args, 'l')) != NULL) - grp->gr_name = pw_checkname((u_char *)arg->val, 0); + if (a_newname != NULL) + grp->gr_name = pw_checkname((u_char *)a_newname->val, 0); } else { if (a_name == NULL) /* Required */ errx(EX_DATAERR, "group name required"); else if (grp != NULL) /* Exists */ errx(EX_DATAERR, "group name `%s' already exists", a_name->val); extendarray(&members, &grmembers, 200); members[0] = NULL; grp = &fakegroup; grp->gr_name = pw_checkname((u_char *)a_name->val, 0); grp->gr_passwd = "*"; grp->gr_gid = gr_gidpolicy(cnf, args); grp->gr_mem = members; } /* * This allows us to set a group password Group passwords is an * antique idea, rarely used and insecure (no secure database) Should * be discouraged, but it is apparently still supported by some * software. */ if ((arg = getarg(args, 'h')) != NULL || (arg = getarg(args, 'H')) != NULL) { if (strcmp(arg->val, "-") == 0) grp->gr_passwd = "*"; /* No access */ else { int fd = atoi(arg->val); int precrypt = (arg->ch == 'H'); int b; int istty = isatty(fd); struct termios t; char *p, line[256]; if (istty) { if (tcgetattr(fd, &t) == -1) istty = 0; else { struct termios n = t; /* Disable echo */ n.c_lflag &= ~(ECHO); tcsetattr(fd, TCSANOW, &n); printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name); fflush(stdout); } } b = read(fd, line, sizeof(line) - 1); if (istty) { /* Restore state */ tcsetattr(fd, TCSANOW, &t); fputc('\n', stdout); fflush(stdout); } if (b < 0) { warn("-h file descriptor"); return EX_OSERR; } line[b] = '\0'; if ((p = strpbrk(line, " \t\r\n")) != NULL) *p = '\0'; if (!*line) errx(EX_DATAERR, "empty password read on file descriptor %d", fd); if (precrypt) { if (strchr(line, ':') != NULL) return EX_DATAERR; grp->gr_passwd = line; } else grp->gr_passwd = pw_pwcrypt(line); } } if (((arg = getarg(args, 'M')) != NULL || (arg = getarg(args, 'd')) != NULL || (arg = getarg(args, 'm')) != NULL) && arg->val) { int i = 0; char *p; struct passwd *pwd; /* Make sure this is not stay NULL with -M "" */ extendarray(&members, &grmembers, 200); if (arg->ch == 'd') delete_members(&members, &grmembers, &i, arg, grp); else if (arg->ch == 'm') { int k = 0; if (grp->gr_mem != NULL) { while (grp->gr_mem[k] != NULL) { if (extendarray(&members, &grmembers, i + 2) != -1) members[i++] = grp->gr_mem[k]; k++; } } } if (arg->ch != 'd') for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { int j; /* * Check for duplicates */ pwd = lookup_pwent(p); for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++) ; if (j == i && extendarray(&members, &grmembers, i + 2) != -1) members[i++] = newstr(pwd->pw_name); } while (i < grmembers) members[i++] = NULL; grp->gr_mem = members; } if (getarg(args, 'N') != NULL) return print_group(grp, getarg(args, 'P') != NULL); if (mode == M_ADD && (rc = addgrent(grp)) != 0) { if (rc == -1) warnx("group '%s' already exists", grp->gr_name); else warn("group update"); return EX_IOERR; } else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) { if (rc == -1) warnx("group '%s' not available (NIS?)", grp->gr_name); else warn("group update"); return EX_IOERR; } + + arg = a_newname != NULL ? a_newname : a_name; /* grp may have been invalidated */ - if ((grp = GETGRNAM(a_name->val)) == NULL) + if ((grp = GETGRNAM(arg->val)) == NULL) errx(EX_SOFTWARE, "group disappeared during update"); pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid); free(members); return EXIT_SUCCESS; } /* * Lookup a passwd entry using a name or UID. */ static struct passwd * lookup_pwent(const char *user) { struct passwd *pwd; if ((pwd = GETPWNAM(user)) == NULL && (!isdigit((unsigned char)*user) || (pwd = getpwuid((uid_t) atoi(user))) == NULL)) errx(EX_NOUSER, "user `%s' does not exist", user); return (pwd); } /* * Delete requested members from a group. */ static void delete_members(char ***members, int *grmembers, int *i, struct carg *arg, struct group *grp) { bool matchFound; char *user; char *valueCopy; char *valuePtr; int k; struct passwd *pwd; if (grp->gr_mem == NULL) return; k = 0; while (grp->gr_mem[k] != NULL) { matchFound = false; if ((valueCopy = strdup(arg->val)) == NULL) errx(EX_UNAVAILABLE, "out of memory"); valuePtr = valueCopy; while ((user = strsep(&valuePtr, ", \t")) != NULL) { pwd = lookup_pwent(user); if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) { matchFound = true; break; } } free(valueCopy); if (!matchFound && extendarray(members, grmembers, *i + 2) != -1) (*members)[(*i)++] = grp->gr_mem[k]; k++; } return; } static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args) { struct group *grp; gid_t gid = (gid_t) - 1; struct carg *a_gid = getarg(args, 'g'); /* * Check the given gid, if any */ if (a_gid != NULL) { gid = (gid_t) atol(a_gid->val); if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL) errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid); } else { struct bitmap bm; /* * We need to allocate the next available gid under one of * two policies a) Grab the first unused gid b) Grab the * highest possible unused gid */ if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ cnf->min_gid = 1000; cnf->max_gid = 32000; } bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); /* * Now, let's fill the bitmap from the password file */ SETGRENT(); while ((grp = GETGRENT()) != NULL) if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) bm_setbit(&bm, grp->gr_gid - cnf->min_gid); ENDGRENT(); /* * Then apply the policy, with fallback to reuse if necessary */ if (cnf->reuse_gids) gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); else { gid = (gid_t) (bm_lastset(&bm) + 1); if (!bm_isset(&bm, gid)) gid += cnf->min_gid; else gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); } /* * Another sanity check */ if (gid < cnf->min_gid || gid > cnf->max_gid) errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); bm_dealloc(&bm); } return gid; } static int print_group(struct group * grp, int pretty) { if (!pretty) { char *buf = NULL; buf = gr_make(grp); printf("%s\n", buf); free(buf); } else { int i; printf("Group Name: %-15s #%lu\n" " Members: ", grp->gr_name, (long) grp->gr_gid); if (grp->gr_mem != NULL) { for (i = 0; grp->gr_mem[i]; i++) printf("%s%s", i ? "," : "", grp->gr_mem[i]); } fputs("\n\n", stdout); } return EXIT_SUCCESS; } Index: stable/10/usr.sbin/pw/pw_user.c =================================================================== --- stable/10/usr.sbin/pw/pw_user.c (revision 274081) +++ stable/10/usr.sbin/pw/pw_user.c (revision 274082) @@ -1,1324 +1,1345 @@ /*- * Copyright (C) 1996 * David L. Nugent. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pw.h" #include "bitmap.h" #define LOGNAMESIZE (MAXLOGNAME-1) static char locked_str[] = "*LOCKED*"; static int print_user(struct passwd * pwd, int pretty, int v7); static uid_t pw_uidpolicy(struct userconf * cnf, struct cargs * args); static uid_t pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer); static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args); static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args); static char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user); static char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell); static char *pw_password(struct userconf * cnf, struct cargs * args, char const * user); static char *shell_path(char const * path, char *shells[], char *sh); static void rmat(uid_t uid); static void rmopie(char const * name); /*- * -C config configuration file * -q quiet operation * -n name login name * -u uid user id * -c comment user name/comment * -d directory home directory * -e date account expiry date * -p date password expiry date * -g grp primary group * -G grp1,grp2 additional groups * -m [ -k dir ] create and set up home * -s shell name of login shell * -o duplicate uid ok * -L class user class * -l name new login name * -h fd password filehandle * -H fd encrypted password filehandle * -F force print or add * Setting defaults: * -D set user defaults * -b dir default home root dir * -e period default expiry period * -p period default password change period * -g group default group * -G grp1,grp2.. default additional groups * -L class default login class * -k dir default home skeleton * -s shell default shell * -w method default password method */ int pw_user(struct userconf * cnf, int mode, struct cargs * args) { int rc, edited = 0; char *p = NULL; char *passtmp; struct carg *a_name; struct carg *a_uid; struct carg *arg; struct passwd *pwd = NULL; struct group *grp; struct stat st; char line[_PASSWORD_LEN+1]; FILE *fp; char *dmode_c; void *set = NULL; static struct passwd fakeuser = { NULL, "*", -1, -1, 0, "", "User &", "/nonexistent", "/bin/sh", 0 #if defined(__FreeBSD__) ,0 #endif }; /* * With M_NEXT, we only need to return the * next uid to stdout */ if (mode == M_NEXT) { uid_t next = pw_uidpolicy(cnf, args); if (getarg(args, 'q')) return next; printf("%ld:", (long)next); pw_group(cnf, mode, args); return EXIT_SUCCESS; } /* * We can do all of the common legwork here */ if ((arg = getarg(args, 'b')) != NULL) { cnf->home = arg->val; } if ((arg = getarg(args, 'M')) != NULL) { dmode_c = arg->val; if ((set = setmode(dmode_c)) == NULL) errx(EX_DATAERR, "invalid directory creation mode '%s'", dmode_c); cnf->homemode = getmode(set, _DEF_DIRMODE); free(set); } /* * If we'll need to use it or we're updating it, * then create the base home directory if necessary */ if (arg != NULL || getarg(args, 'm') != NULL) { int l = strlen(cnf->home); if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */ cnf->home[--l] = '\0'; if (l < 2 || *cnf->home != '/') /* Check for absolute path name */ errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home); if (stat(cnf->home, &st) == -1) { char dbuf[MAXPATHLEN]; /* * 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(cnf->home+1, '/') == NULL) { strcpy(dbuf, "/usr"); strncat(dbuf, cnf->home, MAXPATHLEN-5); if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) { chown(dbuf, 0, 0); /* * Skip first "/" and create symlink: * /home -> usr/home */ symlink(dbuf+1, cnf->home); } /* If this falls, fall back to old method */ } strlcpy(dbuf, cnf->home, sizeof(dbuf)); p = dbuf; if (stat(dbuf, &st) == -1) { while ((p = strchr(p + 1, '/')) != NULL) { *p = '\0'; if (stat(dbuf, &st) == -1) { if (mkdir(dbuf, _DEF_DIRMODE) == -1) goto direrr; chown(dbuf, 0, 0); } else if (!S_ISDIR(st.st_mode)) errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf); *p = '/'; } } if (stat(dbuf, &st) == -1) { if (mkdir(dbuf, _DEF_DIRMODE) == -1) { direrr: err(EX_OSFILE, "mkdir '%s'", dbuf); } chown(dbuf, 0, 0); } } else if (!S_ISDIR(st.st_mode)) errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home); } if ((arg = getarg(args, 'e')) != NULL) cnf->expire_days = atoi(arg->val); if ((arg = getarg(args, 'y')) != NULL) cnf->nispasswd = arg->val; if ((arg = getarg(args, 'p')) != NULL && arg->val) cnf->password_days = atoi(arg->val); if ((arg = getarg(args, 'g')) != NULL) { if (!*(p = arg->val)) /* Handle empty group list specially */ cnf->default_group = ""; else { if ((grp = GETGRNAM(p)) == NULL) { if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) errx(EX_NOUSER, "group `%s' does not exist", p); } cnf->default_group = newstr(grp->gr_name); } } if ((arg = getarg(args, 'L')) != NULL) cnf->default_class = pw_checkname((u_char *)arg->val, 0); if ((arg = getarg(args, 'G')) != NULL && arg->val) { int i = 0; for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { if ((grp = GETGRNAM(p)) == NULL) { if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) errx(EX_NOUSER, "group `%s' does not exist", p); } if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1) cnf->groups[i++] = newstr(grp->gr_name); } while (i < cnf->numgroups) cnf->groups[i++] = NULL; } if ((arg = getarg(args, 'k')) != NULL) { if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode)) errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir); } if ((arg = getarg(args, 's')) != NULL) cnf->shell_default = arg->val; if ((arg = getarg(args, 'w')) != NULL) cnf->default_password = boolean_val(arg->val, cnf->default_password); if (mode == M_ADD && getarg(args, 'D')) { if (getarg(args, 'n') != NULL) errx(EX_DATAERR, "can't combine `-D' with `-n name'"); if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { if ((cnf->min_uid = (uid_t) atoi(p)) == 0) cnf->min_uid = 1000; if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid) cnf->max_uid = 32000; } if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { if ((cnf->min_gid = (gid_t) atoi(p)) == 0) cnf->min_gid = 1000; if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid) cnf->max_gid = 32000; } arg = getarg(args, 'C'); if (write_userconfig(arg ? arg->val : NULL)) return EXIT_SUCCESS; warn("config update"); return EX_IOERR; } if (mode == M_PRINT && getarg(args, 'a')) { int pretty = getarg(args, 'P') != NULL; int v7 = getarg(args, '7') != NULL; SETPWENT(); while ((pwd = GETPWENT()) != NULL) print_user(pwd, pretty, v7); ENDPWENT(); return EXIT_SUCCESS; } if ((a_name = getarg(args, 'n')) != NULL) pwd = GETPWNAM(pw_checkname((u_char *)a_name->val, 0)); a_uid = getarg(args, 'u'); if (a_uid == NULL) { if (a_name == NULL) errx(EX_DATAERR, "user name or id required"); /* * Determine whether 'n' switch is name or uid - we don't * really don't really care which we have, but we need to * know. */ if (mode != M_ADD && pwd == NULL && strspn(a_name->val, "0123456789") == strlen(a_name->val) && *a_name->val) { (a_uid = a_name)->ch = 'u'; a_name = NULL; } + } else { + if (strspn(a_uid->val, "0123456789") != strlen(a_uid->val)) + errx(EX_USAGE, "-u expects a number"); } /* * Update, delete & print require that the user exists */ if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT || mode == M_LOCK || mode == M_UNLOCK) { if (a_name == NULL && pwd == NULL) /* Try harder */ pwd = GETPWUID(atoi(a_uid->val)); if (pwd == NULL) { if (mode == M_PRINT && getarg(args, 'F')) { fakeuser.pw_name = a_name ? a_name->val : "nouser"; fakeuser.pw_uid = a_uid ? (uid_t) atol(a_uid->val) : -1; return print_user(&fakeuser, getarg(args, 'P') != NULL, getarg(args, '7') != NULL); } if (a_name == NULL) errx(EX_NOUSER, "no such uid `%s'", a_uid->val); errx(EX_NOUSER, "no such user `%s'", a_name->val); } if (a_name == NULL) /* May be needed later */ a_name = addarg(args, 'n', newstr(pwd->pw_name)); /* * The M_LOCK and M_UNLOCK functions simply add or remove * a "*LOCKED*" prefix from in front of the password to * prevent it decoding correctly, and therefore prevents * access. Of course, this only prevents access via * password authentication (not ssh, kerberos or any * other method that does not use the UNIX password) but * that is a known limitation. */ if (mode == M_LOCK) { if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0) errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); passtmp = malloc(strlen(pwd->pw_passwd) + sizeof(locked_str)); if (passtmp == NULL) /* disaster */ errx(EX_UNAVAILABLE, "out of memory"); strcpy(passtmp, locked_str); strcat(passtmp, pwd->pw_passwd); pwd->pw_passwd = passtmp; edited = 1; } else if (mode == M_UNLOCK) { if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0) errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); pwd->pw_passwd += sizeof(locked_str)-1; edited = 1; } else if (mode == M_DELETE) { /* * Handle deletions now */ char file[MAXPATHLEN]; char home[MAXPATHLEN]; uid_t uid = pwd->pw_uid; struct group *gr; char grname[LOGNAMESIZE]; if (strcmp(pwd->pw_name, "root") == 0) errx(EX_DATAERR, "cannot remove user 'root'"); if (!PWALTDIR()) { /* * Remove opie record from /etc/opiekeys */ rmopie(pwd->pw_name); /* * Remove crontabs */ snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); if (access(file, F_OK) == 0) { sprintf(file, "crontab -u %s -r", pwd->pw_name); system(file); } } /* * Save these for later, since contents of pwd may be * invalidated by deletion */ sprintf(file, "%s/%s", _PATH_MAILDIR, pwd->pw_name); strlcpy(home, pwd->pw_dir, sizeof(home)); gr = GETGRGID(pwd->pw_gid); if (gr != NULL) strlcpy(grname, gr->gr_name, LOGNAMESIZE); else grname[0] = '\0'; rc = delpwent(pwd); if (rc == -1) err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); else if (rc != 0) { warn("passwd update"); return EX_IOERR; } if (cnf->nispasswd && *cnf->nispasswd=='/') { rc = delnispwent(cnf->nispasswd, a_name->val); if (rc == -1) warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name); else if (rc != 0) warn("WARNING: NIS passwd update"); /* non-fatal */ } grp = GETGRNAM(a_name->val); if (grp != NULL && (grp->gr_mem == NULL || *grp->gr_mem == NULL) && strcmp(a_name->val, grname) == 0) delgrent(GETGRNAM(a_name->val)); SETGRENT(); while ((grp = GETGRENT()) != NULL) { int i, j; char group[MAXLOGNAME]; if (grp->gr_mem != NULL) { for (i = 0; grp->gr_mem[i] != NULL; i++) { if (!strcmp(grp->gr_mem[i], a_name->val)) { for (j = i; grp->gr_mem[j] != NULL; j++) grp->gr_mem[j] = grp->gr_mem[j+1]; strlcpy(group, grp->gr_name, MAXLOGNAME); chggrent(group, grp); } } } } ENDGRENT(); pw_log(cnf, mode, W_USER, "%s(%ld) account removed", a_name->val, (long) uid); if (!PWALTDIR()) { /* * Remove mail file */ remove(file); /* * Remove at jobs */ if (getpwuid(uid) == NULL) rmat(uid); /* * Remove home directory and contents */ if (getarg(args, 'r') != NULL && *home == '/' && getpwuid(uid) == NULL) { if (stat(home, &st) != -1) { rm_r(home, uid); pw_log(cnf, mode, W_USER, "%s(%ld) home '%s' %sremoved", a_name->val, (long) uid, home, stat(home, &st) == -1 ? "" : "not completely "); } } } return EXIT_SUCCESS; } else if (mode == M_PRINT) return print_user(pwd, getarg(args, 'P') != NULL, getarg(args, '7') != NULL); /* * The rest is edit code */ if ((arg = getarg(args, 'l')) != NULL) { if (strcmp(pwd->pw_name, "root") == 0) errx(EX_DATAERR, "can't rename `root' account"); pwd->pw_name = pw_checkname((u_char *)arg->val, 0); edited = 1; } if ((arg = getarg(args, 'u')) != NULL && isdigit((unsigned char)*arg->val)) { pwd->pw_uid = (uid_t) atol(arg->val); edited = 1; if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) errx(EX_DATAERR, "can't change uid of `root' account"); if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name); } if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */ gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid; if (newgid != pwd->pw_gid) { edited = 1; pwd->pw_gid = newgid; } } if ((arg = getarg(args, 'p')) != NULL) { if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { if (pwd->pw_change != 0) { pwd->pw_change = 0; edited = 1; } } else { time_t now = time(NULL); time_t expire = parse_date(now, arg->val); if (pwd->pw_change != expire) { pwd->pw_change = expire; edited = 1; } } } if ((arg = getarg(args, 'e')) != NULL) { if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { if (pwd->pw_expire != 0) { pwd->pw_expire = 0; edited = 1; } } else { time_t now = time(NULL); time_t expire = parse_date(now, arg->val); if (pwd->pw_expire != expire) { pwd->pw_expire = expire; edited = 1; } } } if ((arg = getarg(args, 's')) != NULL) { char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val); if (shell == NULL) shell = ""; if (strcmp(shell, pwd->pw_shell) != 0) { pwd->pw_shell = shell; edited = 1; } } if (getarg(args, 'L')) { if (cnf->default_class == NULL) cnf->default_class = ""; if (strcmp(pwd->pw_class, cnf->default_class) != 0) { pwd->pw_class = cnf->default_class; edited = 1; } } if ((arg = getarg(args, 'd')) != NULL) { if (strcmp(pwd->pw_dir, arg->val)) edited = 1; if (stat(pwd->pw_dir = arg->val, &st) == -1) { if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0) warnx("WARNING: home `%s' does not exist", pwd->pw_dir); } else if (!S_ISDIR(st.st_mode)) warnx("WARNING: home `%s' is not a directory", pwd->pw_dir); } if ((arg = getarg(args, 'w')) != NULL && getarg(args, 'h') == NULL && getarg(args, 'H') == NULL) { login_cap_t *lc; lc = login_getpwclass(pwd); if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) warn("setting crypt(3) format"); login_close(lc); pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); edited = 1; } } else { login_cap_t *lc; /* * Add code */ if (a_name == NULL) /* Required */ errx(EX_DATAERR, "login name required"); else if ((pwd = GETPWNAM(a_name->val)) != NULL) /* Exists */ errx(EX_DATAERR, "login name `%s' already exists", a_name->val); /* * Now, set up defaults for a new user */ pwd = &fakeuser; pwd->pw_name = a_name->val; pwd->pw_class = cnf->default_class ? cnf->default_class : ""; pwd->pw_uid = pw_uidpolicy(cnf, args); pwd->pw_gid = pw_gidpolicy(cnf, args, pwd->pw_name, (gid_t) pwd->pw_uid); pwd->pw_change = pw_pwdpolicy(cnf, args); pwd->pw_expire = pw_exppolicy(cnf, args); pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name); pwd->pw_shell = pw_shellpolicy(cnf, args, NULL); lc = login_getpwclass(pwd); if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) warn("setting crypt(3) format"); login_close(lc); pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); edited = 1; if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name); } /* * Shared add/edit code */ if ((arg = getarg(args, 'c')) != NULL) { char *gecos = pw_checkname((u_char *)arg->val, 1); if (strcmp(pwd->pw_gecos, gecos) != 0) { pwd->pw_gecos = gecos; edited = 1; } } if ((arg = getarg(args, 'h')) != NULL || (arg = getarg(args, 'H')) != NULL) { if (strcmp(arg->val, "-") == 0) { if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { pwd->pw_passwd = "*"; /* No access */ edited = 1; } } else { int fd = atoi(arg->val); int precrypt = (arg->ch == 'H'); int b; int istty = isatty(fd); struct termios t; login_cap_t *lc; if (istty) { if (tcgetattr(fd, &t) == -1) istty = 0; else { struct termios n = t; /* Disable echo */ n.c_lflag &= ~(ECHO); tcsetattr(fd, TCSANOW, &n); printf("%s%spassword for user %s:", (mode == M_UPDATE) ? "new " : "", precrypt ? "encrypted " : "", pwd->pw_name); fflush(stdout); } } b = read(fd, line, sizeof(line) - 1); if (istty) { /* Restore state */ tcsetattr(fd, TCSANOW, &t); fputc('\n', stdout); fflush(stdout); } if (b < 0) { warn("-%c file descriptor", precrypt ? 'H' : 'h'); return EX_IOERR; } line[b] = '\0'; if ((p = strpbrk(line, "\r\n")) != NULL) *p = '\0'; if (!*line) errx(EX_DATAERR, "empty password read on file descriptor %d", fd); if (precrypt) { if (strchr(line, ':') != NULL) return EX_DATAERR; pwd->pw_passwd = line; } else { lc = login_getpwclass(pwd); if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) warn("setting crypt(3) format"); login_close(lc); pwd->pw_passwd = pw_pwcrypt(line); } edited = 1; } } /* * Special case: -N only displays & exits */ if (getarg(args, 'N') != NULL) return print_user(pwd, getarg(args, 'P') != NULL, getarg(args, '7') != NULL); if (mode == M_ADD) { edited = 1; /* Always */ rc = addpwent(pwd); if (rc == -1) { warnx("user '%s' already exists", pwd->pw_name); return EX_IOERR; } else if (rc != 0) { warn("passwd file update"); return EX_IOERR; } if (cnf->nispasswd && *cnf->nispasswd=='/') { rc = addnispwent(cnf->nispasswd, pwd); if (rc == -1) warnx("User '%s' already exists in NIS passwd", pwd->pw_name); else warn("NIS passwd update"); /* NOTE: we treat NIS-only update errors as non-fatal */ } } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) { if (edited) { /* Only updated this if required */ rc = chgpwent(a_name->val, pwd); if (rc == -1) { warnx("user '%s' does not exist (NIS?)", pwd->pw_name); return EX_IOERR; } else if (rc != 0) { warn("passwd file update"); return EX_IOERR; } if ( cnf->nispasswd && *cnf->nispasswd=='/') { rc = chgnispwent(cnf->nispasswd, a_name->val, pwd); if (rc == -1) warn("User '%s' not found in NIS passwd", pwd->pw_name); else warn("NIS passwd update"); /* NOTE: NIS-only update errors are not fatal */ } } } /* * Ok, user is created or changed - now edit group file */ if (mode == M_ADD || getarg(args, 'G') != NULL) { - int i; + int i, j; + /* First remove the user from all group */ + SETGRENT(); + while ((grp = GETGRENT()) != NULL) { + char group[MAXLOGNAME]; + if (grp->gr_mem == NULL) + continue; + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (strcmp(grp->gr_mem[i] , pwd->pw_name) != 0) + continue; + for (j = i; grp->gr_mem[j] != NULL ; j++) + grp->gr_mem[j] = grp->gr_mem[j+1]; + strlcpy(group, grp->gr_name, MAXLOGNAME); + chggrent(group, grp); + } + } + ENDGRENT(); + + /* now add to group where needed */ for (i = 0; cnf->groups[i] != NULL; i++) { grp = GETGRNAM(cnf->groups[i]); grp = gr_add(grp, pwd->pw_name); /* * grp can only be NULL in 2 cases: * - the new member is already a member * - a problem with memory occurs * in both cases we want to skip now. */ if (grp == NULL) continue; chggrent(cnf->groups[i], grp); free(grp); } } /* go get a current version of pwd */ pwd = GETPWNAM(a_name->val); if (pwd == NULL) { /* This will fail when we rename, so special case that */ if (mode == M_UPDATE && (arg = getarg(args, 'l')) != NULL) { a_name->val = arg->val; /* update new name */ pwd = GETPWNAM(a_name->val); /* refetch renamed rec */ } } if (pwd == NULL) /* can't go on without this */ errx(EX_NOUSER, "user '%s' disappeared during update", a_name->val); grp = GETGRGID(pwd->pw_gid); pw_log(cnf, mode, W_USER, "%s(%ld):%s(%ld):%s:%s:%s", pwd->pw_name, (long) pwd->pw_uid, grp ? grp->gr_name : "unknown", (long) (grp ? grp->gr_gid : -1), pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); /* * If adding, let's touch and chown the user's mail file. This is not * strictly necessary under BSD with a 0755 maildir but it also * doesn't hurt anything to create the empty mailfile */ if (mode == M_ADD) { if (!PWALTDIR()) { sprintf(line, "%s/%s", _PATH_MAILDIR, pwd->pw_name); close(open(line, O_RDWR | O_CREAT, 0600)); /* Preserve contents & * mtime */ chown(line, pwd->pw_uid, pwd->pw_gid); } } /* * Let's create and populate the user's home directory. Note * that this also `works' for editing users if -m is used, but * existing files will *not* be overwritten. */ if (!PWALTDIR() && getarg(args, 'm') != NULL && pwd->pw_dir && *pwd->pw_dir == '/' && pwd->pw_dir[1]) { copymkdir(pwd->pw_dir, cnf->dotdir, cnf->homemode, pwd->pw_uid, pwd->pw_gid); pw_log(cnf, mode, W_USER, "%s(%ld) home %s made", pwd->pw_name, (long) pwd->pw_uid, pwd->pw_dir); } /* * Finally, send mail to the new user as well, if we are asked to */ if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) { FILE *pfp = popen(_PATH_SENDMAIL " -t", "w"); if (pfp == NULL) warn("sendmail"); else { fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name); while (fgets(line, sizeof(line), fp) != NULL) { /* Do substitutions? */ fputs(line, pfp); } pclose(pfp); pw_log(cnf, mode, W_USER, "%s(%ld) new user mail sent", pwd->pw_name, (long) pwd->pw_uid); } fclose(fp); } return EXIT_SUCCESS; } static uid_t pw_uidpolicy(struct userconf * cnf, struct cargs * args) { struct passwd *pwd; uid_t uid = (uid_t) - 1; struct carg *a_uid = getarg(args, 'u'); /* * Check the given uid, if any */ if (a_uid != NULL) { uid = (uid_t) atol(a_uid->val); if ((pwd = GETPWUID(uid)) != NULL && getarg(args, 'o') == NULL) errx(EX_DATAERR, "uid `%ld' has already been allocated", (long) pwd->pw_uid); } else { struct bitmap bm; /* * We need to allocate the next available uid under one of * two policies a) Grab the first unused uid b) Grab the * highest possible unused uid */ if (cnf->min_uid >= cnf->max_uid) { /* Sanity * claus^H^H^H^Hheck */ cnf->min_uid = 1000; cnf->max_uid = 32000; } bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); /* * Now, let's fill the bitmap from the password file */ SETPWENT(); while ((pwd = GETPWENT()) != NULL) if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); ENDPWENT(); /* * Then apply the policy, with fallback to reuse if necessary */ if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); /* * Another sanity check */ if (uid < cnf->min_uid || uid > cnf->max_uid) errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); bm_dealloc(&bm); } return uid; } static uid_t pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer) { struct group *grp; gid_t gid = (uid_t) - 1; struct carg *a_gid = getarg(args, 'g'); /* * If no arg given, see if default can help out */ if (a_gid == NULL && cnf->default_group && *cnf->default_group) a_gid = addarg(args, 'g', cnf->default_group); /* * Check the given gid, if any */ SETGRENT(); if (a_gid != NULL) { if ((grp = GETGRNAM(a_gid->val)) == NULL) { gid = (gid_t) atol(a_gid->val); if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL) errx(EX_NOUSER, "group `%s' is not defined", a_gid->val); } gid = grp->gr_gid; } else if ((grp = GETGRNAM(nam)) != NULL && (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { gid = grp->gr_gid; /* Already created? Use it anyway... */ } else { struct cargs grpargs; char tmp[32]; LIST_INIT(&grpargs); addarg(&grpargs, 'n', nam); /* * We need to auto-create a group with the user's name. We * can send all the appropriate output to our sister routine * bit first see if we can create a group with gid==uid so we * can keep the user and group ids in sync. We purposely do * NOT check the gid range if we can force the sync. If the * user's name dups an existing group, then the group add * function will happily handle that case for us and exit. */ if (GETGRGID(prefer) == NULL) { sprintf(tmp, "%lu", (unsigned long) prefer); addarg(&grpargs, 'g', tmp); } if (getarg(args, 'N')) { addarg(&grpargs, 'N', NULL); addarg(&grpargs, 'q', NULL); gid = pw_group(cnf, M_NEXT, &grpargs); } else { pw_group(cnf, M_ADD, &grpargs); if ((grp = GETGRNAM(nam)) != NULL) gid = grp->gr_gid; } a_gid = LIST_FIRST(&grpargs); while (a_gid != NULL) { struct carg *t = LIST_NEXT(a_gid, list); LIST_REMOVE(a_gid, list); a_gid = t; } } ENDGRENT(); return gid; } static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args) { time_t result = 0; time_t now = time(NULL); struct carg *arg = getarg(args, 'p'); if (arg != NULL) { if ((result = parse_date(now, arg->val)) == now) errx(EX_DATAERR, "invalid date/time `%s'", arg->val); } else if (cnf->password_days > 0) result = now + ((long) cnf->password_days * 86400L); return result; } static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args) { time_t result = 0; time_t now = time(NULL); struct carg *arg = getarg(args, 'e'); if (arg != NULL) { if ((result = parse_date(now, arg->val)) == now) errx(EX_DATAERR, "invalid date/time `%s'", arg->val); } else if (cnf->expire_days > 0) result = now + ((long) cnf->expire_days * 86400L); return result; } static char * pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user) { struct carg *arg = getarg(args, 'd'); if (arg) return arg->val; else { static char home[128]; if (cnf->home == NULL || *cnf->home == '\0') errx(EX_CONFIG, "no base home directory set"); sprintf(home, "%s/%s", cnf->home, user); return home; } } static char * shell_path(char const * path, char *shells[], char *sh) { if (sh != NULL && (*sh == '/' || *sh == '\0')) return sh; /* specified full path or forced none */ else { char *p; char paths[_UC_MAXLINE]; /* * We need to search paths */ strlcpy(paths, path, sizeof(paths)); for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { int i; static char shellpath[256]; if (sh != NULL) { sprintf(shellpath, "%s/%s", p, sh); if (access(shellpath, X_OK) == 0) return shellpath; } else for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { sprintf(shellpath, "%s/%s", p, shells[i]); if (access(shellpath, X_OK) == 0) return shellpath; } } if (sh == NULL) errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); errx(EX_CONFIG, "no default shell available or defined"); return NULL; } } static char * pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell) { char *sh = newshell; struct carg *arg = getarg(args, 's'); if (newshell == NULL && arg != NULL) sh = arg->val; return shell_path(cnf->shelldir, cnf->shells, sh ? sh : 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]; /* * 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); if (cryptpw == NULL) errx(EX_CONFIG, "crypt(3) failure"); return strcpy(buf, cryptpw); } static char * pw_password(struct userconf * cnf, struct cargs * args, char const * user) { int i, l; char pwbuf[32]; switch (cnf->default_password) { case -1: /* Random password */ l = (arc4random() % 8 + 8); /* 8 - 16 chars */ for (i = 0; i < l; i++) pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; pwbuf[i] = '\0'; /* * We give this information back to the user */ if (getarg(args, 'h') == NULL && getarg(args, 'H') == NULL && getarg(args, 'N') == NULL) { if (isatty(STDOUT_FILENO)) printf("Password for '%s' is: ", user); printf("%s\n", pwbuf); fflush(stdout); } break; case -2: /* No password at all! */ return ""; case 0: /* No login - default */ default: return "*"; case 1: /* user's name */ strlcpy(pwbuf, user, sizeof(pwbuf)); break; } return pw_pwcrypt(pwbuf); } static int print_user(struct passwd * pwd, int pretty, int v7) { if (!pretty) { char *buf; if (!v7) pwd->pw_passwd = (pwd->pw_passwd == NULL) ? "" : "*"; buf = v7 ? pw_make_v7(pwd) : pw_make(pwd); printf("%s\n", buf); free(buf); } else { int j; char *p; struct group *grp = GETGRGID(pwd->pw_gid); char uname[60] = "User &", office[60] = "[None]", wphone[60] = "[None]", hphone[60] = "[None]"; char acexpire[32] = "[None]", pwexpire[32] = "[None]"; struct tm * tptr; if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { strlcpy(uname, p, sizeof(uname)); if ((p = strtok(NULL, ",")) != NULL) { strlcpy(office, p, sizeof(office)); if ((p = strtok(NULL, ",")) != NULL) { strlcpy(wphone, p, sizeof(wphone)); if ((p = strtok(NULL, "")) != NULL) { strlcpy(hphone, p, sizeof(hphone)); } } } } /* * Handle '&' in gecos field */ if ((p = strchr(uname, '&')) != NULL) { int l = strlen(pwd->pw_name); int m = strlen(p); memmove(p + l, p + 1, m); memmove(p, pwd->pw_name, l); *p = (char) toupper((unsigned char)*p); } 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) strftime(pwexpire, sizeof pwexpire, "%c", tptr); printf("Login Name: %-15s #%-12ld Group: %-15s #%ld\n" " Full Name: %s\n" " Home: %-26.26s Class: %s\n" " Shell: %-26.26s Office: %s\n" "Work Phone: %-26.26s Home Phone: %s\n" "Acc Expire: %-26.26s Pwd Expire: %s\n", pwd->pw_name, (long) pwd->pw_uid, grp ? grp->gr_name : "(invalid)", (long) pwd->pw_gid, uname, pwd->pw_dir, pwd->pw_class, pwd->pw_shell, office, wphone, hphone, acexpire, pwexpire); SETGRENT(); j = 0; while ((grp=GETGRENT()) != NULL) { int i = 0; if (grp->gr_mem != NULL) { while (grp->gr_mem[i] != NULL) { if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) { printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); break; } ++i; } } } ENDGRENT(); printf("%s", j ? "\n" : ""); } return EXIT_SUCCESS; } char * pw_checkname(u_char *name, int gecos) { char showch[8]; u_char const *badchars, *ch, *showtype; int reject; ch = name; reject = 0; if (gecos) { /* See if the name is valid as a gecos (comment) field. */ badchars = ":!@"; showtype = "gecos field"; } else { /* See if the name is valid as a userid or group. */ badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; showtype = "userid/group name"; /* Userids and groups can not have a leading '-'. */ if (*ch == '-') reject = 1; } if (!reject) { while (*ch) { if (strchr(badchars, *ch) != NULL || *ch < ' ' || *ch == 127) { reject = 1; break; } /* 8-bit characters are only allowed in GECOS fields */ if (!gecos && (*ch & 0x80)) { reject = 1; break; } ch++; } } /* * A `$' is allowed as the final character for userids and groups, * mainly for the benefit of samba. */ if (reject && !gecos) { if (*ch == '$' && *(ch + 1) == '\0') { reject = 0; ch++; } } if (reject) { snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) ? "`%c'" : "0x%02x", *ch); errx(EX_DATAERR, "invalid character %s at position %td in %s", showch, (ch - name), showtype); } if (!gecos && (ch - name) > LOGNAMESIZE) errx(EX_DATAERR, "name too long `%s' (max is %d)", name, LOGNAMESIZE); return (char *)name; } static void rmat(uid_t uid) { DIR *d = opendir("/var/at/jobs"); if (d != NULL) { struct dirent *e; while ((e = readdir(d)) != NULL) { struct stat st; if (strncmp(e->d_name, ".lock", 5) != 0 && stat(e->d_name, &st) == 0 && !S_ISDIR(st.st_mode) && st.st_uid == uid) { char tmp[MAXPATHLEN]; sprintf(tmp, "/usr/bin/atrm %s", e->d_name); system(tmp); } } closedir(d); } } static void rmopie(char const * name) { static const char etcopie[] = "/etc/opiekeys"; FILE *fp = fopen(etcopie, "r+"); if (fp != NULL) { char tmp[1024]; off_t atofs = 0; int length = strlen(name); while (fgets(tmp, sizeof tmp, fp) != NULL) { if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') { if (fseek(fp, atofs, SEEK_SET) == 0) { fwrite("#", 1, 1, fp); /* Comment username out */ } break; } atofs = ftell(fp); } /* * If we got an error of any sort, don't update! */ fclose(fp); } } Index: stable/10/usr.sbin/pw/tests/Makefile =================================================================== --- stable/10/usr.sbin/pw/tests/Makefile (nonexistent) +++ stable/10/usr.sbin/pw/tests/Makefile (revision 274082) @@ -0,0 +1,10 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/usr.sbin/pw + +ATF_TESTS_SH= pw_delete pw_modify + +FILES= group helper_functions.shin master.passwd +FILESDIR= ${TESTSDIR} + +.include Property changes on: stable/10/usr.sbin/pw/tests/Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/pw_delete.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_delete.sh (nonexistent) +++ stable/10/usr.sbin/pw/tests/pw_delete.sh (revision 274082) @@ -0,0 +1,47 @@ +# $FreeBSD$ + +# Import helper functions +. $(atf_get_srcdir)/helper_functions.shin + +# Test that a user can be deleted when another user is part of this +# user's default group and does not go into an infinate loop. +# PR: 191427 +atf_test_case rmuser_seperate_group cleanup +rmuser_seperate_group_head() { + atf_set "timeout" "30" +} +rmuser_seperate_group_body() { + populate_etc_skel + pw -V ${HOME} useradd test || atf_fail "Creating test user" + pw -V ${HOME} groupmod test -M 'test,root' || \ + atf_fail "Modifying the group" + pw -V ${HOME} userdel test || atf_fail "delete the user" +} + +atf_test_case group_do_not_delete_wheel_if_group_unkown +group_do_not_delete_wheel_if_group_unkown_head() { + atf_set "descr" "Make sure we do not consider as gid 0 an unknown group" +} + +group_do_not_delete_wheel_if_group_unkown_body() { + populate_etc_skel + atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x pw -V ${HOME} groupshow wheel + atf_check -e inline:"pw: -g expects a number\n" -s exit:64 -x pw -V ${HOME} groupdel -g I_do_not_exist + atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x pw -V ${HOME} groupshow wheel +} + +atf_test_case user_do_not_try_to_delete_root_if_user_unkown +user_do_not_try_to_delete_root_if_user_unkown_head() { + atf_set "descr" "Make sure not to try to remove root if deleteing an unknown user" +} + +user_do_not_try_to_delete_root_if_user_unkown_body() { + populate_etc_skel + atf_check -e inline:"pw: -u expects a number\n" -s exit:64 -x pw -V ${HOME} userdel -u plop +} + +atf_init_test_cases() { + atf_add_test_case rmuser_seperate_group + atf_add_test_case group_do_not_delete_wheel_if_group_unkown + atf_add_test_case user_do_not_try_to_delete_root_if_user_unkown +} Property changes on: stable/10/usr.sbin/pw/tests/pw_delete.sh ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/pw_modify.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_modify.sh (nonexistent) +++ stable/10/usr.sbin/pw/tests/pw_modify.sh (revision 274082) @@ -0,0 +1,80 @@ +# $FreeBSD$ + +# Import helper functions +. $(atf_get_srcdir)/helper_functions.shin + + +# Test adding & removing a user from a group +atf_test_case groupmod_user +groupmod_user_body() { + populate_etc_skel + atf_check -s exit:0 pw -V ${HOME} addgroup test + atf_check -s exit:0 pw -V ${HOME} groupmod test -m root + atf_check -s exit:0 -o match:"^test:\*:1001:root$" \ + grep "^test:\*:.*:root$" $HOME/group + atf_check -s exit:0 pw -V ${HOME} groupmod test -d root + atf_check -s exit:0 -o match:"^test:\*:1001:$" \ + grep "^test:\*:.*:$" $HOME/group +} + + +# Test adding and removing a user that does not exist +atf_test_case groupmod_invalid_user +groupmod_invalid_user_body() { + populate_etc_skel + atf_check -s exit:0 pw -V ${HOME} addgroup test + atf_check -s exit:67 -e match:"does not exist" pw -V ${HOME} groupmod test -m foo + atf_check -s exit:0 pw -V ${HOME} groupmod test -d foo +} + +atf_test_case groupmod_bug_193704 +groupmod_bug_193704_head() { + atf_set "descr" "Regression test for the #193704 bug" +} +groupmod_bug_193704_body() { + populate_etc_skel + atf_check -s exit:0 -x pw -V ${HOME} groupadd test + atf_check -s exit:0 -x pw -V ${HOME} groupmod test -l newgroupname + atf_check -s exit:65 -e match:"^pw: unknown group" -x pw -V ${HOME} groupshow test +} + +atf_test_case usermod_bug_185666 +usermod_bug_185666_head() { + atf_set "descr" "Regression test for the #185666 bug" +} + +usermod_bug_185666_body() { + populate_etc_skel + atf_check -s exit:0 -x pw -V ${HOME} useradd testuser + atf_check -s exit:0 -x pw -V ${HOME} groupadd testgroup + atf_check -s exit:0 -x pw -V ${HOME} groupadd testgroup2 + atf_check -s exit:0 -x pw -V ${HOME} usermod testuser -G testgroup + atf_check -o inline:"testuser:*:1001:\n" -x pw -V${HOME} groupshow testuser + atf_check -o inline:"testgroup:*:1002:testuser\n" -x pw -V ${HOME} groupshow testgroup + atf_check -o inline:"testgroup2:*:1003:\n" -x pw -V${HOME} groupshow testgroup2 + atf_check -s exit:0 -x pw -V ${HOME} usermod testuser -G testgroup2 + atf_check -o inline:"testuser:*:1001:\n" -x pw -V ${HOME} groupshow testuser + atf_check -o inline:"testgroup:*:1002:\n" -x pw -V ${HOME} groupshow testgroup + atf_check -o inline:"testgroup2:*:1003:testuser\n" -x pw -V ${HOME} groupshow testgroup2 +} + +atf_test_case do_not_duplicate_group_on_gid_change +do_not_duplicate_group_on_gid_change_head() { + atf_set "descr" "Do not duplicate group on gid change" +} + +do_not_duplicate_group_on_gid_change_body() { + populate_etc_skel + atf_check -s exit:0 -x pw -V ${HOME} groupadd testgroup + atf_check -s exit:0 -x pw -V ${HOME} groupmod testgroup -g 12345 + # use grep to see if the entry has not be duplicated + atf_check -o inline:"testgroup:*:12345:\n" -s exit:0 -x grep "^testgroup" ${HOME}/group +} + +atf_init_test_cases() { + atf_add_test_case groupmod_user + atf_add_test_case groupmod_invalid_user + atf_add_test_case groupmod_bug_193704 + atf_add_test_case usermod_bug_185666 + atf_add_test_case do_not_duplicate_group_on_gid_change +} Property changes on: stable/10/usr.sbin/pw/tests/pw_modify.sh ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/group =================================================================== --- stable/10/usr.sbin/pw/tests/group (nonexistent) +++ stable/10/usr.sbin/pw/tests/group (revision 274082) @@ -0,0 +1,3 @@ +# $FreeBSD$ +# +wheel:*:0:root Property changes on: stable/10/usr.sbin/pw/tests/group ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/helper_functions.shin =================================================================== --- stable/10/usr.sbin/pw/tests/helper_functions.shin (nonexistent) +++ stable/10/usr.sbin/pw/tests/helper_functions.shin (revision 274082) @@ -0,0 +1,15 @@ +# $FreeBSD$ + +# Workdir to run tests in +TESTDIR=$(atf_get_srcdir) + +# Populate the files pw needs to use into $HOME/etc +populate_etc_skel() { + cp ${TESTDIR}/master.passwd ${HOME} || \ + atf_fail "Populating master.passwd in ${HOME}" + cp ${TESTDIR}/group ${HOME} || atf_fail "Populating group in ${HOME}" + + # Generate the passwd file + pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \ + atf_fail "generate passwd from master.passwd" +} Property changes on: stable/10/usr.sbin/pw/tests/helper_functions.shin ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/master.passwd =================================================================== --- stable/10/usr.sbin/pw/tests/master.passwd (nonexistent) +++ stable/10/usr.sbin/pw/tests/master.passwd (revision 274082) @@ -0,0 +1,4 @@ +# $FreeBSD$ +# +root:*:0:0::0:0:Charlie &:/root:/bin/csh +toor:*:0:0::0:0:Bourne-again Superuser:/root: Property changes on: stable/10/usr.sbin/pw/tests/master.passwd ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Index: stable/10 =================================================================== --- stable/10 (revision 274081) +++ stable/10 (revision 274082) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r272445,272578,273772,273779,273782,273786-273787,273791