NetBSD/usr.sbin/user/user.c
hubertf 5653bfc431 if creategid() fails, don't try to print system error message (use errx()
instead of err()), as all error cases in creategid() are already commented
properly from inside creategid().

This prevents funny errors like:

	miyu# groupadd test2
	miyu# groupadd test2
	groupadd: group `test2' already exists
	groupadd: can't add group: problems with /etc/group file: Inappropriate ioctl for device
2001-09-16 18:08:37 +00:00

1880 lines
46 KiB
C

/* $NetBSD: user.c,v 1.43 2001/09/16 18:08:37 hubertf Exp $ */
/*
* Copyright (c) 1999 Alistair G. Crooks. 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Alistair G. Crooks.
* 4. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* 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 <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1999 \
The NetBSD Foundation, Inc. All rights reserved.");
__RCSID("$NetBSD: user.c,v 1.43 2001/09/16 18:08:37 hubertf Exp $");
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <fcntl.h>
#include <grp.h>
#include <paths.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <util.h>
#include "defs.h"
#include "usermgmt.h"
/* this struct describes a uid range */
typedef struct range_t {
int r_from; /* low uid */
int r_to; /* high uid */
} range_t;
/* this struct encapsulates the user information */
typedef struct user_t {
int u_flags; /* see below */
int u_uid; /* uid of user */
char *u_password; /* encrypted password */
char *u_comment; /* comment field */
char *u_home; /* home directory */
char *u_primgrp; /* primary group */
int u_groupc; /* # of secondary groups */
char *u_groupv[NGROUPS_MAX]; /* secondary groups */
char *u_shell; /* user's shell */
char *u_basedir; /* base directory for home */
char *u_expire; /* when password will expire */
int u_inactive; /* inactive */
char *u_skeldir; /* directory for startup files */
char *u_class; /* login class */
unsigned u_rsize; /* size of range array */
unsigned u_rc; /* # of ranges */
range_t *u_rv; /* the ranges */
unsigned u_defrc; /* # of ranges in defaults */
int u_preserve; /* preserve uids on deletion */
} user_t;
/* flags for which fields of the user_t replace the passwd entry */
enum {
F_COMMENT = 0x0001,
F_DUPUID = 0x0002,
F_EXPIRE = 0x0004,
F_GROUP = 0x0008,
F_HOMEDIR = 0x0010,
F_MKDIR = 0x0020,
F_INACTIVE = 0x0040,
F_PASSWORD = 0x0080,
F_SECGROUP = 0x0100,
F_SHELL = 0x0200,
F_UID = 0x0400,
F_USERNAME = 0x0800,
F_CLASS = 0x1000
};
#define CONFFILE "/etc/usermgmt.conf"
#ifndef DEF_GROUP
#define DEF_GROUP "users"
#endif
#ifndef DEF_BASEDIR
#define DEF_BASEDIR "/home"
#endif
#ifndef DEF_SKELDIR
#define DEF_SKELDIR "/etc/skel"
#endif
#ifndef DEF_SHELL
#define DEF_SHELL _PATH_CSHELL
#endif
#ifndef DEF_COMMENT
#define DEF_COMMENT ""
#endif
#ifndef DEF_LOWUID
#define DEF_LOWUID 1000
#endif
#ifndef DEF_HIGHUID
#define DEF_HIGHUID 60000
#endif
#ifndef DEF_INACTIVE
#define DEF_INACTIVE 0
#endif
#ifndef DEF_EXPIRE
#define DEF_EXPIRE NULL
#endif
#ifndef DEF_CLASS
#define DEF_CLASS ""
#endif
#ifndef WAITSECS
#define WAITSECS 10
#endif
#ifndef NOBODY_UID
#define NOBODY_UID 32767
#endif
/* some useful constants */
enum {
MaxShellNameLen = 256,
MaxFileNameLen = MAXPATHLEN,
MaxUserNameLen = 32,
MaxFieldNameLen = 32,
MaxCommandLen = 2048,
MaxEntryLen = 2048,
PasswordLength = 13,
LowGid = DEF_LOWUID,
HighGid = DEF_HIGHUID
};
/* Full paths of programs used here */
#define CHMOD "/bin/chmod"
#define CHOWN "/usr/sbin/chown"
#define MKDIR "/bin/mkdir"
#define MV "/bin/mv"
#define NOLOGIN "/sbin/nologin"
#define PAX "/bin/pax"
#define RM "/bin/rm"
#define UNSET_EXPIRY "Null (unset)"
static int asystem(const char *fmt, ...)
__attribute__((__format__(__printf__, 1, 2)));
static int verbose;
/* if *cpp is non-null, free it, then assign `n' chars of `s' to it */
static void
memsave(char **cpp, char *s, size_t n)
{
if (*cpp != NULL) {
FREE(*cpp);
}
NEWARRAY(char, *cpp, n + 1, exit(1));
(void) memcpy(*cpp, s, n);
(*cpp)[n] = '\0';
}
/* a replacement for system(3) */
static int
asystem(const char *fmt, ...)
{
va_list vp;
char buf[MaxCommandLen];
int ret;
va_start(vp, fmt);
(void) vsnprintf(buf, sizeof(buf), fmt, vp);
va_end(vp);
if (verbose) {
(void) printf("Command: %s\n", buf);
}
if ((ret = system(buf)) != 0) {
warnx("[Warning] can't system `%s'", buf);
}
return ret;
}
/* remove a users home directory, returning 1 for success (ie, no problems encountered) */
static int
removehomedir(const char *user, int uid, const char *dir)
{
struct stat st;
/* userid not root? */
if (uid == 0) {
warnx("Not deleting home directory `%s'; userid is 0", dir);
return 0;
}
/* directory exists (and is a directory!) */
if (stat(dir, &st) < 0) {
warnx("Home directory `%s' doesn't exist", dir);
return 0;
}
if (!S_ISDIR(st.st_mode)) {
warnx("Home directory `%s' is not a directory", dir);
return 0;
}
/* userid matches directory owner? */
if (st.st_uid != uid) {
warnx("User `%s' doesn't own directory `%s', not removed\n", user, dir);
return 0;
}
(void) seteuid(uid);
/* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */
(void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir);
(void) seteuid(0);
if (rmdir(dir) < 0) {
warnx("Unable to remove all files in `%s'\n", dir);
return 0;
}
return 1;
}
#define NetBSD_1_4_K 104110000
#if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_4_K)
/* bounds checking strncpy */
static int
strlcpy(char *to, char *from, size_t tosize)
{
size_t n;
int fromsize;
fromsize = strlen(from);
n = MIN(tosize - 1, fromsize);
(void) memcpy(to, from, n);
to[n] = '\0';
return fromsize;
}
#endif /* NetBSD < 1.4K */
/*
* Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
* 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``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.
*/
/*
* research has shown that NetBSD 1.3H was the first version of -current
* with asprintf in libc. agc
*/
#define NetBSD_1_3_H 103080000
#if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_3_H)
int
asprintf(char **str, char const *fmt, ...)
{
int ret;
va_list ap;
FILE f;
unsigned char *_base;
va_start(ap, fmt);
f._flags = __SWR | __SSTR | __SALC;
f._bf._base = f._p = (unsigned char *)malloc(128);
if (f._bf._base == NULL)
goto err;
f._bf._size = f._w = 127; /* Leave room for the NUL */
ret = vfprintf(&f, fmt, ap);
if (ret == -1)
goto err;
*f._p = '\0';
va_end(ap);
_base = realloc(f._bf._base, (size_t)(ret + 1));
if (_base == NULL)
goto err;
*str = (char *)_base;
return (ret);
err:
if (f._bf._base)
free(f._bf._base);
*str = NULL;
return (-1);
}
#endif /* NetBSD < 1.3H */
/* return 1 if all of `s' is numeric */
static int
is_number(char *s)
{
for ( ; *s ; s++) {
if (!isdigit(*s)) {
return 0;
}
}
return 1;
}
/*
* check that the effective uid is 0 - called from funcs which will
* modify data and config files.
*/
static void
checkeuid(void)
{
if (geteuid() != 0) {
errx(EXIT_FAILURE, "Program must be run as root");
}
}
/* copy any dot files into the user's home directory */
static int
copydotfiles(char *skeldir, int uid, int gid, char *dir)
{
struct dirent *dp;
DIR *dirp;
int n;
if ((dirp = opendir(skeldir)) == NULL) {
warn("can't open source . files dir `%s'", skeldir);
return 0;
}
for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) {
if (strcmp(dp->d_name, ".") == 0 ||
strcmp(dp->d_name, "..") == 0) {
continue;
}
n = 1;
}
(void) closedir(dirp);
if (n == 0) {
warnx("No \"dot\" initialisation files found");
} else {
(void) asystem("cd %s; %s -rw -pe %s . %s",
skeldir, PAX, (verbose) ? "-v" : "", dir);
}
(void) asystem("%s -R -h %d:%d %s", CHOWN, uid, gid, dir);
(void) asystem("%s -R u+w %s", CHMOD, dir);
return n;
}
/* create a group entry with gid `gid' */
static int
creategid(char *group, int gid, char *name)
{
struct stat st;
FILE *from;
FILE *to;
char buf[MaxEntryLen];
char f[MaxFileNameLen];
int fd;
int cc;
if (getgrnam(group) != NULL) {
warnx("group `%s' already exists", group);
return 0;
}
if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
warn("can't create gid for %s: can't open %s", name, _PATH_GROUP);
return 0;
}
if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
warn("can't lock `%s'", _PATH_GROUP);
}
(void) fstat(fileno(from), &st);
(void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP);
if ((fd = mkstemp(f)) < 0) {
(void) fclose(from);
warn("can't create gid: mkstemp failed");
return 0;
}
if ((to = fdopen(fd, "w")) == NULL) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: fdopen `%s' failed", f);
return 0;
}
while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) {
if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: short write to `%s'", f);
return 0;
}
}
(void) fprintf(to, "%s:*:%d:%s\n", group, gid, name);
(void) fclose(from);
(void) fclose(to);
if (rename(f, _PATH_GROUP) < 0) {
warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
return 0;
}
(void) chmod(_PATH_GROUP, st.st_mode & 07777);
return 1;
}
/* modify the group entry with name `group' to be newent */
static int
modify_gid(char *group, char *newent)
{
struct stat st;
FILE *from;
FILE *to;
char buf[MaxEntryLen];
char f[MaxFileNameLen];
char *colon;
int groupc;
int entc;
int fd;
int cc;
if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
warn("can't create gid for %s: can't open %s", group, _PATH_GROUP);
return 0;
}
if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
warn("can't lock `%s'", _PATH_GROUP);
}
(void) fstat(fileno(from), &st);
(void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP);
if ((fd = mkstemp(f)) < 0) {
(void) fclose(from);
warn("can't create gid: mkstemp failed");
return 0;
}
if ((to = fdopen(fd, "w")) == NULL) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: fdopen `%s' failed", f);
return 0;
}
groupc = strlen(group);
while (fgets(buf, sizeof(buf), from) != NULL) {
cc = strlen(buf);
if ((colon = strchr(buf, ':')) == NULL) {
warn("badly formed entry `%s'", buf);
continue;
}
entc = (int)(colon - buf);
if (entc == groupc && strncmp(group, buf, (unsigned) entc) == 0) {
if (newent == NULL) {
continue;
} else {
cc = strlen(newent);
(void) strlcpy(buf, newent, sizeof(buf));
}
}
if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: short write to `%s'", f);
return 0;
}
}
(void) fclose(from);
(void) fclose(to);
if (rename(f, _PATH_GROUP) < 0) {
warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
return 0;
}
(void) chmod(_PATH_GROUP, st.st_mode & 07777);
return 1;
}
/* modify the group entries for all `groups', by adding `user' */
static int
append_group(char *user, int ngroups, char **groups)
{
struct group *grp;
struct stat st;
FILE *from;
FILE *to;
char buf[MaxEntryLen];
char f[MaxFileNameLen];
char *colon;
int groupc;
int entc;
int fd;
int nc;
int cc;
int i;
int j;
for (i = 0 ; i < ngroups ; i++) {
if ((grp = getgrnam(groups[i])) == NULL) {
warnx("can't append group `%s' for user `%s'", groups[i], user);
} else {
for (j = 0 ; grp->gr_mem[j] ; j++) {
if (strcmp(user, grp->gr_mem[j]) == 0) {
/* already in it */
groups[i] = "";
}
}
}
}
if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
warn("can't append group for %s: can't open %s", user, _PATH_GROUP);
return 0;
}
if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
warn("can't lock `%s'", _PATH_GROUP);
}
(void) fstat(fileno(from), &st);
(void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP);
if ((fd = mkstemp(f)) < 0) {
(void) fclose(from);
warn("can't create gid: mkstemp failed");
return 0;
}
if ((to = fdopen(fd, "w")) == NULL) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: fdopen `%s' failed", f);
return 0;
}
while (fgets(buf, sizeof(buf), from) != NULL) {
cc = strlen(buf);
if ((colon = strchr(buf, ':')) == NULL) {
warn("badly formed entry `%s'", buf);
continue;
}
entc = (int)(colon - buf);
for (i = 0 ; i < ngroups ; i++) {
if ((groupc = strlen(groups[i])) == 0) {
continue;
}
if (entc == groupc && strncmp(groups[i], buf, (unsigned) entc) == 0) {
if ((nc = snprintf(&buf[cc - 1],
sizeof(buf) - cc + 1,
"%s%s\n",
(buf[cc - 2] == ':') ? "" : ",",
user)) < 0) {
warnx("Warning: group `%s' entry too long", groups[i]);
}
cc += nc - 1;
}
}
if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
(void) fclose(from);
(void) close(fd);
(void) unlink(f);
warn("can't create gid: short write to `%s'", f);
return 0;
}
}
(void) fclose(from);
(void) fclose(to);
if (rename(f, _PATH_GROUP) < 0) {
warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
return 0;
}
(void) chmod(_PATH_GROUP, st.st_mode & 07777);
return 1;
}
/* return 1 if `login' is a valid login name */
static int
valid_login(char *login)
{
char *cp;
for (cp = login ; *cp ; cp++) {
if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-') {
return 0;
}
}
return 1;
}
/* return 1 if `group' is a valid group name */
static int
valid_group(char *group)
{
char *cp;
for (cp = group ; *cp ; cp++) {
if (!isalnum(*cp)) {
return 0;
}
}
return 1;
}
/* find the next gid in the range lo .. hi */
static int
getnextgid(int *gidp, int lo, int hi)
{
for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
if (getgrgid((gid_t)*gidp) == NULL) {
return 1;
}
}
return 0;
}
#ifdef EXTENSIONS
/* save a range of uids */
static int
save_range(user_t *up, char *cp)
{
int from;
int to;
int i;
if (up->u_rsize == 0) {
up->u_rsize = 32;
NEWARRAY(range_t, up->u_rv, up->u_rsize, return(0));
} else if (up->u_rc == up->u_rsize) {
up->u_rsize *= 2;
RENEW(range_t, up->u_rv, up->u_rsize, return(0));
}
if (up->u_rv && sscanf(cp, "%d..%d", &from, &to) == 2) {
for (i = 0 ; i < up->u_rc ; i++) {
if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
break;
}
}
if (i == up->u_rc) {
up->u_rv[up->u_rc].r_from = from;
up->u_rv[up->u_rc].r_to = to;
up->u_rc += 1;
}
} else {
warnx("Bad range `%s'", cp);
return 0;
}
return 1;
}
#endif
/* set the defaults in the defaults file */
static int
setdefaults(user_t *up)
{
char template[MaxFileNameLen];
FILE *fp;
int ret;
int fd;
#ifdef EXTENSIONS
int i;
#endif
(void) snprintf(template, sizeof(template), "%s.XXXXXX", CONFFILE);
if ((fd = mkstemp(template)) < 0) {
warnx("can't mkstemp `%s' for writing", CONFFILE);
return 0;
}
if ((fp = fdopen(fd, "w")) == NULL) {
warn("can't fdopen `%s' for writing", CONFFILE);
return 0;
}
ret = 1;
if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
#ifdef EXTENSIONS
fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 ||
#endif
fprintf(fp, "inactive\t%d\n", up->u_inactive) <= 0 ||
fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 ||
fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) {
warn("can't write to `%s'", CONFFILE);
ret = 0;
}
#ifdef EXTENSIONS
for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) {
if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
warn("can't write to `%s'", CONFFILE);
ret = 0;
}
}
#endif
(void) fclose(fp);
if (ret) {
ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
}
return ret;
}
/* read the defaults file */
static void
read_defaults(user_t *up)
{
struct stat st;
size_t lineno;
size_t len;
FILE *fp;
char *cp;
char *s;
memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP));
memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR));
memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR));
memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL));
memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT));
#ifdef EXTENSIONS
memsave(&up->u_class, DEF_CLASS, strlen(DEF_CLASS));
#endif
up->u_rsize = 16;
NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1));
up->u_inactive = DEF_INACTIVE;
up->u_expire = DEF_EXPIRE;
if ((fp = fopen(CONFFILE, "r")) == NULL) {
if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) {
warn("can't create `%s' defaults file", CONFFILE);
}
fp = fopen(CONFFILE, "r");
}
if (fp != NULL) {
while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) {
if (strncmp(s, "group", 5) == 0) {
for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
}
memsave(&up->u_primgrp, cp, strlen(cp));
} else if (strncmp(s, "base_dir", 8) == 0) {
for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
}
memsave(&up->u_basedir, cp, strlen(cp));
} else if (strncmp(s, "skel_dir", 8) == 0) {
for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
}
memsave(&up->u_skeldir, cp, strlen(cp));
} else if (strncmp(s, "shell", 5) == 0) {
for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
}
memsave(&up->u_shell, cp, strlen(cp));
#ifdef EXTENSIONS
} else if (strncmp(s, "class", 5) == 0) {
for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
}
memsave(&up->u_class, cp, strlen(cp));
#endif
} else if (strncmp(s, "inactive", 8) == 0) {
for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
}
up->u_inactive = atoi(cp);
#ifdef EXTENSIONS
} else if (strncmp(s, "range", 5) == 0) {
for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
}
(void) save_range(up, cp);
#endif
#ifdef EXTENSIONS
} else if (strncmp(s, "preserve", 8) == 0) {
for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
}
up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
(strncmp(cp, "yes", 3) == 0) ? 1 :
atoi(cp);
#endif
} else if (strncmp(s, "expire", 6) == 0) {
for (cp = s + 6 ; *cp && isspace(*cp) ; cp++) {
}
if (strcmp(cp, UNSET_EXPIRY) == 0) {
if (up->u_expire) {
FREE(up->u_expire);
}
up->u_expire = NULL;
} else {
memsave(&up->u_expire, cp, strlen(cp));
}
}
(void) free(s);
}
(void) fclose(fp);
}
if (up->u_rc == 0) {
up->u_rv[up->u_rc].r_from = DEF_LOWUID;
up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
up->u_rc += 1;
}
up->u_defrc = up->u_rc;
}
/* return the next valid unused uid */
static int
getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid)
{
for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) {
if (sync_uid_gid) {
if (getgrgid((gid_t)(*uid)) == NULL) {
return 1;
}
} else {
return 1;
}
}
}
return 0;
}
/* add a user */
static int
adduser(char *login, user_t *up)
{
struct group *grp;
struct stat st;
struct tm tm;
time_t expire;
char password[PasswordLength + 1];
char home[MaxFileNameLen];
char buf[MaxFileNameLen];
int sync_uid_gid;
int masterfd;
int ptmpfd;
int gid;
int cc;
int i;
if (!valid_login(login)) {
errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
}
if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
}
if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
}
pw_init();
if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
(void) close(masterfd);
err(EXIT_FAILURE, "can't obtain pw_lock");
}
while ((cc = read(masterfd, buf, sizeof(buf))) > 0) {
if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
(void) close(masterfd);
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc);
}
}
/* if no uid was specified, get next one in [low_uid..high_uid] range */
sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
if (up->u_uid == -1) {
for (i = 0 ; i < up->u_rc ; i++) {
if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) {
break;
}
}
if (i == up->u_rc) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid);
}
}
/* check uid isn't already allocated */
if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid);
}
/* if -g=uid was specified, check gid is unused */
if (sync_uid_gid) {
if (getgrgid((gid_t)(up->u_uid)) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
}
gid = up->u_uid;
} else if ((grp = getgrnam(up->u_primgrp)) != NULL) {
gid = grp->gr_gid;
} else if (is_number(up->u_primgrp) &&
(grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) {
gid = grp->gr_gid;
} else {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
}
/* check name isn't already in use */
if (!(up->u_flags & F_DUPUID) && getpwnam(login) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "already a `%s' user", login);
}
if (up->u_flags & F_HOMEDIR) {
(void) strlcpy(home, up->u_home, sizeof(home));
} else {
/* if home directory hasn't been given, make it up */
(void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login);
}
expire = 0;
if (up->u_expire != NULL) {
(void) memset(&tm, 0, sizeof(tm));
if (strptime(up->u_expire, "%c", &tm) == NULL) {
warnx("invalid time format `%s'", optarg);
} else {
expire = mktime(&tm);
}
}
if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR)) {
warnx("Warning: home directory `%s' doesn't exist, and -m was not specified",
home);
}
password[PasswordLength] = '\0';
if (up->u_password != NULL &&
strlen(up->u_password) == PasswordLength) {
(void) memcpy(password, up->u_password, PasswordLength);
} else {
(void) memset(password, '*', PasswordLength);
if (up->u_password != NULL) {
warnx("Password `%s' is invalid: setting it to `%s'",
up->u_password, password);
}
}
cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d:%s:%d:%ld:%s:%s:%s\n",
login,
password,
up->u_uid,
gid,
#ifdef EXTENSIONS
up->u_class,
#else
"",
#endif
up->u_inactive,
(long) expire,
up->u_comment,
home,
up->u_shell);
if (write(ptmpfd, buf, (size_t) cc) != cc) {
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "can't add `%s'", buf);
}
if (up->u_flags & F_MKDIR) {
if (lstat(home, &st) == 0) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "home directory `%s' already exists", home);
} else {
if (asystem("%s -p %s", MKDIR, home) != 0) {
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "can't mkdir `%s'", home);
}
(void) copydotfiles(up->u_skeldir, up->u_uid, gid, home);
}
}
if (strcmp(up->u_primgrp, "=uid") == 0 &&
getgrnam(login) == NULL &&
!creategid(login, gid, login)) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login);
}
if (up->u_groupc > 0 && !append_group(login, up->u_groupc, up->u_groupv)) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "can't append `%s' to new groups", login);
}
(void) close(ptmpfd);
if (pw_mkdb(login, 0) < 0) {
err(EXIT_FAILURE, "pw_mkdb failed");
}
return 1;
}
/* modify a user */
static int
moduser(char *login, char *newlogin, user_t *up)
{
struct passwd *pwp;
struct group *grp;
struct tm tm;
const char *homedir;
char newdir[MaxFileNameLen];
size_t colonc, len, loginc;
FILE *master;
char *buf, *colon, *line;
int masterfd;
int ptmpfd;
int error;
if (!valid_login(newlogin)) {
errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
}
if ((pwp = getpwnam(login)) == NULL) {
errx(EXIT_FAILURE, "No such user `%s'", login);
}
/* keep dir name in case we need it for '-m' */
homedir = pwp->pw_dir;
if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
}
if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
}
pw_init();
if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
(void) close(masterfd);
err(EXIT_FAILURE, "can't obtain pw_lock");
}
if ((master = fdopen(masterfd, "r")) == NULL) {
(void) close(masterfd);
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "can't fdopen fd for %s", _PATH_MASTERPASSWD);
}
if (up != NULL) {
if (up->u_flags & F_USERNAME) {
/* if changing name, check new name isn't already in use */
if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "already a `%s' user", newlogin);
}
pwp->pw_name = newlogin;
/*
* Provide a new directory name in case the
* home directory is to be moved.
*/
if (up->u_flags & F_MKDIR) {
snprintf(newdir, sizeof(newdir), "%s/%s", up->u_basedir, newlogin);
pwp->pw_dir = newdir;
}
}
if (up->u_flags & F_PASSWORD) {
if (up->u_password != NULL && strlen(up->u_password) == PasswordLength)
pwp->pw_passwd = up->u_password;
}
if (up->u_flags & F_UID) {
/* check uid isn't already allocated */
if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid);
}
pwp->pw_uid = up->u_uid;
}
if (up->u_flags & F_GROUP) {
/* if -g=uid was specified, check gid is unused */
if (strcmp(up->u_primgrp, "=uid") == 0) {
if (getgrgid((gid_t)(up->u_uid)) != NULL) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
}
pwp->pw_gid = up->u_uid;
} else if ((grp = getgrnam(up->u_primgrp)) != NULL) {
pwp->pw_gid = grp->gr_gid;
} else if (is_number(up->u_primgrp) &&
(grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) {
pwp->pw_gid = grp->gr_gid;
} else {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
}
}
if (up->u_flags |= F_INACTIVE)
pwp->pw_change = up->u_inactive;
if (up->u_flags & F_EXPIRE) {
(void) memset(&tm, 0, sizeof(tm));
if (strptime(up->u_expire, "%c", &tm) == NULL)
warnx("invalid time format `%s'", optarg);
else
pwp->pw_expire = mktime(&tm);
}
if (up->u_flags & F_COMMENT)
pwp->pw_gecos = up->u_comment;
if (up->u_flags & F_HOMEDIR)
pwp->pw_dir = up->u_home;
if (up->u_flags & F_SHELL)
pwp->pw_shell = up->u_shell;
#ifdef EXTENSIONS
if (up->u_flags & F_CLASS)
pwp->pw_class = up->u_class;
#endif
}
loginc = strlen(login);
while ((line = fgetln(master, &len)) != NULL) {
if ((colon = strchr(line, ':')) == NULL) {
warnx("Malformed entry `%s'. Skipping", line);
continue;
}
colonc = (size_t)(colon - line);
if (strncmp(login, line, loginc) == 0 && loginc == colonc) {
if (up != NULL) {
len = (int)asprintf(&buf, "%s:%s:%d:%d:"
#ifdef EXTENSIONS
"%s"
#endif
":%ld:%ld:%s:%s:%s\n",
newlogin,
pwp->pw_passwd,
pwp->pw_uid,
pwp->pw_gid,
#ifdef EXTENSIONS
pwp->pw_class,
#endif
(long)pwp->pw_change,
(long)pwp->pw_expire,
pwp->pw_gecos,
pwp->pw_dir,
pwp->pw_shell);
if (write(ptmpfd, buf, len) != len) {
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "can't add `%s'", buf);
}
}
/* } else if (write(ptmpfd, line, len) != len) { */
} else if ((colonc = write(ptmpfd, line, len)) != len) {
(void) close(masterfd);
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "short write to /etc/ptmp (%lld not %lld chars)",
(long long)colonc,
(long long)len);
}
}
if (up != NULL) {
if ((up->u_flags & F_MKDIR) &&
asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) {
(void) close(ptmpfd);
(void) pw_abort();
err(EXIT_FAILURE, "can't move `%s' to `%s'",
homedir, pwp->pw_dir);
}
if (up->u_groupc > 0 &&
!append_group(newlogin, up->u_groupc, up->u_groupv)) {
(void) close(ptmpfd);
(void) pw_abort();
errx(EXIT_FAILURE, "can't append `%s' to new groups",
newlogin);
}
}
(void) close(ptmpfd);
if (up != NULL && strcmp(login, newlogin) == 0) {
error = pw_mkdb(login, 0);
} else {
error = pw_mkdb(NULL, 0);
}
if (error < 0) {
err(EXIT_FAILURE, "pw_mkdb failed");
}
return 1;
}
#ifdef EXTENSIONS
/* see if we can find out the user struct */
static struct passwd *
find_user_info(char *name)
{
struct passwd *pwp;
if ((pwp = getpwnam(name)) != NULL) {
return pwp;
}
if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) {
return pwp;
}
return NULL;
}
#endif
#ifdef EXTENSIONS
/* see if we can find out the group struct */
static struct group *
find_group_info(char *name)
{
struct group *grp;
if ((grp = getgrnam(name)) != NULL) {
return grp;
}
if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) {
return grp;
}
return NULL;
}
#endif
/* print out usage message, and then exit */
void
usermgmt_usage(const char *prog)
{
if (strcmp(prog, "useradd") == 0) {
(void) fprintf(stderr, "Usage: %s -D [-b basedir] [-e expiry] "
"[-f inactive] [-g group]\n\t[-r lowuid..highuid] "
"[-s shell] [-L class]\n", prog);
(void) fprintf(stderr, "Usage: %s [-G group] [-b basedir] "
"[-c comment] [-d homedir] [-e expiry]\n\t[-f inactive] "
"[-g group] [-k skeletondir] [-m] [-o] [-p password]\n"
"\t[-r lowuid..highuid] [-s shell]\n\t[-u uid] [-v] user\n",
prog);
} else if (strcmp(prog, "usermod") == 0) {
(void) fprintf(stderr, "Usage: %s [-G group] [-c comment] "
"[-d homedir] [-e expire] [-f inactive]\n\t[-g group] "
"[-l newname] [-m] [-o] [-p password] [-s shell] [-u uid]\n"
"\t[-L class] [-v] user\n", prog);
} else if (strcmp(prog, "userdel") == 0) {
(void) fprintf(stderr, "Usage: %s -D [-p preserve]\n", prog);
(void) fprintf(stderr,
"Usage: %s [-p preserve] [-r] [-v] user\n", prog);
#ifdef EXTENSIONS
} else if (strcmp(prog, "userinfo") == 0) {
(void) fprintf(stderr, "Usage: %s [-e] [-v] user\n", prog);
#endif
} else if (strcmp(prog, "groupadd") == 0) {
(void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-v] group\n",
prog);
} else if (strcmp(prog, "groupdel") == 0) {
(void) fprintf(stderr, "Usage: %s [-v] group\n", prog);
} else if (strcmp(prog, "groupmod") == 0) {
(void) fprintf(stderr,
"Usage: %s [-g gid] [-o] [-n newname] [-v] group\n", prog);
} else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
(void) fprintf(stderr,
"Usage: %s ( add | del | mod | info ) ...\n", prog);
#ifdef EXTENSIONS
} else if (strcmp(prog, "groupinfo") == 0) {
(void) fprintf(stderr, "Usage: %s [-e] [-v] group\n", prog);
#endif
}
exit(EXIT_FAILURE);
/* NOTREACHED */
}
#ifdef EXTENSIONS
#define ADD_OPT_EXTENSIONS "p:r:vL:"
#else
#define ADD_OPT_EXTENSIONS
#endif
int
useradd(int argc, char **argv)
{
user_t u;
int defaultfield;
int bigD;
int c;
#ifdef EXTENSIONS
int i;
#endif
(void) memset(&u, 0, sizeof(u));
read_defaults(&u);
u.u_uid = -1;
defaultfield = bigD = 0;
while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) {
switch(c) {
case 'D':
bigD = 1;
break;
case 'G':
while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
u.u_groupc < NGROUPS_MAX) {
if (u.u_groupv[u.u_groupc][0] != 0) {
u.u_groupc++;
}
}
if (optarg != NULL) {
warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX);
}
break;
case 'b':
defaultfield = 1;
memsave(&u.u_basedir, optarg, strlen(optarg));
break;
case 'c':
memsave(&u.u_comment, optarg, strlen(optarg));
break;
case 'd':
memsave(&u.u_home, optarg, strlen(optarg));
u.u_flags |= F_HOMEDIR;
break;
case 'e':
defaultfield = 1;
memsave(&u.u_expire, optarg, strlen(optarg));
break;
case 'f':
defaultfield = 1;
u.u_inactive = atoi(optarg);
break;
case 'g':
defaultfield = 1;
memsave(&u.u_primgrp, optarg, strlen(optarg));
break;
case 'k':
memsave(&u.u_skeldir, optarg, strlen(optarg));
break;
#ifdef EXTENSIONS
case 'L':
defaultfield = 1;
memsave(&u.u_class, optarg, strlen(optarg));
break;
#endif
case 'm':
u.u_flags |= F_MKDIR;
break;
case 'o':
u.u_flags |= F_DUPUID;
break;
#ifdef EXTENSIONS
case 'p':
memsave(&u.u_password, optarg, strlen(optarg));
break;
#endif
#ifdef EXTENSIONS
case 'r':
defaultfield = 1;
(void) save_range(&u, optarg);
break;
#endif
case 's':
defaultfield = 1;
memsave(&u.u_shell, optarg, strlen(optarg));
break;
case 'u':
if (!is_number(optarg)) {
errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
}
u.u_uid = atoi(optarg);
break;
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
if (bigD) {
if (defaultfield) {
checkeuid();
return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
}
(void) printf("group\t\t%s\n", u.u_primgrp);
(void) printf("base_dir\t%s\n", u.u_basedir);
(void) printf("skel_dir\t%s\n", u.u_skeldir);
(void) printf("shell\t\t%s\n", u.u_shell);
#ifdef EXTENSIONS
(void) printf("class\t\t%s\n", u.u_class);
#endif
(void) printf("inactive\t%d\n", u.u_inactive);
(void) printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire);
#ifdef EXTENSIONS
for (i = 0 ; i < u.u_rc ; i++) {
(void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
}
#endif
return EXIT_SUCCESS;
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("useradd");
}
checkeuid();
return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#ifdef EXTENSIONS
#define MOD_OPT_EXTENSIONS "p:vL:"
#else
#define MOD_OPT_EXTENSIONS
#endif
int
usermod(int argc, char **argv)
{
user_t u;
char newuser[MaxUserNameLen + 1];
int c, have_new_user;
(void) memset(&u, 0, sizeof(u));
(void) memset(newuser, 0, sizeof(newuser));
read_defaults(&u);
have_new_user = 0;
while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) {
switch(c) {
case 'G':
while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
u.u_groupc < NGROUPS_MAX) {
if (u.u_groupv[u.u_groupc][0] != 0) {
u.u_groupc++;
}
}
if (optarg != NULL) {
warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX);
}
u.u_flags |= F_SECGROUP;
break;
case 'c':
memsave(&u.u_comment, optarg, strlen(optarg));
u.u_flags |= F_COMMENT;
break;
case 'd':
memsave(&u.u_home, optarg, strlen(optarg));
u.u_flags |= F_HOMEDIR;
break;
case 'e':
memsave(&u.u_expire, optarg, strlen(optarg));
u.u_flags |= F_EXPIRE;
break;
case 'f':
u.u_inactive = atoi(optarg);
u.u_flags |= F_INACTIVE;
break;
case 'g':
memsave(&u.u_primgrp, optarg, strlen(optarg));
u.u_flags |= F_GROUP;
break;
case 'l':
(void) strlcpy(newuser, optarg, sizeof(newuser));
have_new_user = 1;
u.u_flags |= F_USERNAME;
break;
#ifdef EXTENSIONS
case 'L':
memsave(&u.u_class, optarg, strlen(optarg));
u.u_flags |= F_CLASS;
break;
#endif
case 'm':
u.u_flags |= F_MKDIR;
break;
case 'o':
u.u_flags |= F_DUPUID;
break;
#ifdef EXTENSIONS
case 'p':
memsave(&u.u_password, optarg, strlen(optarg));
u.u_flags |= F_PASSWORD;
break;
#endif
case 's':
memsave(&u.u_shell, optarg, strlen(optarg));
u.u_flags |= F_SHELL;
break;
case 'u':
if (!is_number(optarg)) {
errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
}
u.u_uid = atoi(optarg);
u.u_flags |= F_UID;
break;
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) &&
!(u.u_flags & F_USERNAME)) {
warnx("option 'm' useless without 'd' or 'l' -- ignored");
u.u_flags &= ~F_MKDIR;
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("usermod");
}
checkeuid();
return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#ifdef EXTENSIONS
#define DEL_OPT_EXTENSIONS "Dp:v"
#else
#define DEL_OPT_EXTENSIONS
#endif
int
userdel(int argc, char **argv)
{
struct passwd *pwp;
user_t u;
char password[PasswordLength + 1];
int defaultfield;
int rmhome;
int bigD;
int c;
(void) memset(&u, 0, sizeof(u));
read_defaults(&u);
defaultfield = bigD = rmhome = 0;
while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) {
switch(c) {
#ifdef EXTENSIONS
case 'D':
bigD = 1;
break;
#endif
#ifdef EXTENSIONS
case 'p':
defaultfield = 1;
u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
(strcmp(optarg, "yes") == 0) ? 1 :
atoi(optarg);
break;
#endif
case 'r':
rmhome = 1;
break;
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
#ifdef EXTENSIONS
if (bigD) {
if (defaultfield) {
checkeuid();
return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
}
(void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
return EXIT_SUCCESS;
}
#endif
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("userdel");
}
checkeuid();
if ((pwp = getpwnam(*argv)) == NULL) {
warnx("No such user `%s'", *argv);
return EXIT_FAILURE;
}
if (rmhome)
(void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir);
if (u.u_preserve) {
memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN));
(void) memset(password, '*', PasswordLength);
password[PasswordLength] = '\0';
memsave(&u.u_password, password, PasswordLength);
u.u_flags |= F_PASSWORD;
return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
}
return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#ifdef EXTENSIONS
#define GROUP_ADD_OPT_EXTENSIONS "v"
#else
#define GROUP_ADD_OPT_EXTENSIONS
#endif
/* add a group */
int
groupadd(int argc, char **argv)
{
int dupgid;
int gid;
int c;
gid = -1;
dupgid = 0;
while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) {
switch(c) {
case 'g':
if (!is_number(optarg)) {
errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
}
gid = atoi(optarg);
break;
case 'o':
dupgid = 1;
break;
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("groupadd");
}
checkeuid();
if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
err(EXIT_FAILURE, "can't add group: can't get next gid");
}
if (!dupgid && getgrgid((gid_t) gid) != NULL) {
errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
}
if (!valid_group(*argv)) {
warnx("warning - invalid group name `%s'", *argv);
}
if (!creategid(*argv, gid, "")) {
errx(EXIT_FAILURE, "can't add group: problems with %s file", _PATH_GROUP);
}
return EXIT_SUCCESS;
}
#ifdef EXTENSIONS
#define GROUP_DEL_OPT_EXTENSIONS "v"
#else
#define GROUP_DEL_OPT_EXTENSIONS
#endif
/* remove a group */
int
groupdel(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) {
switch(c) {
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("groupdel");
}
checkeuid();
if (!modify_gid(*argv, NULL)) {
err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
}
return EXIT_SUCCESS;
}
#ifdef EXTENSIONS
#define GROUP_MOD_OPT_EXTENSIONS "v"
#else
#define GROUP_MOD_OPT_EXTENSIONS
#endif
/* modify a group */
int
groupmod(int argc, char **argv)
{
struct group *grp;
char buf[MaxEntryLen];
char *newname;
char **cpp;
int dupgid;
int gid;
int cc;
int c;
gid = -1;
dupgid = 0;
newname = NULL;
while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) {
switch(c) {
case 'g':
if (!is_number(optarg)) {
errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
}
gid = atoi(optarg);
break;
case 'o':
dupgid = 1;
break;
case 'n':
memsave(&newname, optarg, strlen(optarg));
break;
#ifdef EXTENSIONS
case 'v':
verbose = 1;
break;
#endif
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("groupmod");
}
checkeuid();
if (gid < 0 && newname == NULL) {
err(EXIT_FAILURE, "Nothing to change");
}
if (dupgid && gid < 0) {
err(EXIT_FAILURE, "Duplicate which gid?");
}
if ((grp = getgrnam(*argv)) == NULL) {
err(EXIT_FAILURE, "can't find group `%s' to modify", *argv);
}
if (newname != NULL && !valid_group(newname)) {
warn("warning - invalid group name `%s'", newname);
}
cc = snprintf(buf, sizeof(buf), "%s:%s:%d:",
(newname) ? newname : grp->gr_name,
grp->gr_passwd,
(gid < 0) ? grp->gr_gid : gid);
for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) {
cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp,
(cpp[1] == NULL) ? "" : ",");
}
cc += snprintf(&buf[cc], sizeof(buf) - cc, "\n");
if (!modify_gid(*argv, buf)) {
err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
}
return EXIT_SUCCESS;
}
#ifdef EXTENSIONS
/* display user information */
int
userinfo(int argc, char **argv)
{
struct passwd *pwp;
struct group *grp;
char buf[MaxEntryLen];
char **cpp;
int exists;
int cc;
int i;
exists = 0;
while ((i = getopt(argc, argv, "ev")) != -1) {
switch(i) {
case 'e':
exists = 1;
break;
case 'v':
verbose = 1;
break;
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("userinfo");
}
pwp = find_user_info(*argv);
if (exists) {
exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
}
if (pwp == NULL) {
errx(EXIT_FAILURE, "can't find user `%s'", *argv);
}
(void) printf("login\t%s\n", pwp->pw_name);
(void) printf("passwd\t%s\n", pwp->pw_passwd);
(void) printf("uid\t%d\n", pwp->pw_uid);
for (cc = 0 ; (grp = getgrent()) != NULL ; ) {
for (cpp = grp->gr_mem ; *cpp ; cpp++) {
if (strcmp(*cpp, *argv) == 0 && grp->gr_gid != pwp->pw_gid) {
cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s ", grp->gr_name);
}
}
}
if ((grp = getgrgid(pwp->pw_gid)) == NULL) {
(void) printf("groups\t%d %s\n", pwp->pw_gid, buf);
} else {
(void) printf("groups\t%s %s\n", grp->gr_name, buf);
}
(void) printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n");
#ifdef EXTENSIONS
(void) printf("class\t%s\n", pwp->pw_class);
#endif
(void) printf("gecos\t%s\n", pwp->pw_gecos);
(void) printf("dir\t%s\n", pwp->pw_dir);
(void) printf("shell\t%s\n", pwp->pw_shell);
(void) printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n");
return EXIT_SUCCESS;
}
#endif
#ifdef EXTENSIONS
/* display user information */
int
groupinfo(int argc, char **argv)
{
struct group *grp;
char **cpp;
int exists;
int i;
exists = 0;
while ((i = getopt(argc, argv, "ev")) != -1) {
switch(i) {
case 'e':
exists = 1;
break;
case 'v':
verbose = 1;
break;
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usermgmt_usage("groupinfo");
}
grp = find_group_info(*argv);
if (exists) {
exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
}
if (grp == NULL) {
errx(EXIT_FAILURE, "can't find group `%s'", *argv);
}
(void) printf("name\t%s\n", grp->gr_name);
(void) printf("passwd\t%s\n", grp->gr_passwd);
(void) printf("gid\t%d\n", grp->gr_gid);
(void) printf("members\t");
for (cpp = grp->gr_mem ; *cpp ; cpp++) {
(void) printf("%s ", *cpp);
}
(void) fputc('\n', stdout);
return EXIT_SUCCESS;
}
#endif