99f1ebbac6
r285396,r285398,r285401,r285403,r285405,r285406,r285408,r285409,r285411, r285412,r285413,r285415,r285418,r285430,r285433,r285434,r285442,r285948, r285984,r285985,r285989,r285996,r285997,r286045,r286047,r286066,r286150, r286151,r286152,r286154,r286155,r286156,r286157,r286173,r286196,r286197, r286198,r286199,r286200,r286201,r286202,r286203,r286204,r286210,r286211, r286217,r286218,r286258,r286259,r286341,r286775,r286982,r286986,r286991, r286993 Validate most pw inputs. Rewrite the way parsing sub arguments is made to simplify code and improve maintenability Add -y (NIS) to userdel/usermod pw userdel -r <rootdir> now deletes directories in the rootdir Only parse pw.conf when needed Reject usermod and userdel if the user concerned is not on the user database supposed to be manipulated
525 lines
12 KiB
C
525 lines
12 KiB
C
/*-
|
|
* 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 <sys/types.h>
|
|
#include <sys/sbuf.h>
|
|
|
|
#include <err.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pw.h"
|
|
|
|
#define debugging 0
|
|
|
|
enum {
|
|
_UC_NONE,
|
|
_UC_DEFAULTPWD,
|
|
_UC_REUSEUID,
|
|
_UC_REUSEGID,
|
|
_UC_NISPASSWD,
|
|
_UC_DOTDIR,
|
|
_UC_NEWMAIL,
|
|
_UC_LOGFILE,
|
|
_UC_HOMEROOT,
|
|
_UC_HOMEMODE,
|
|
_UC_SHELLPATH,
|
|
_UC_SHELLS,
|
|
_UC_DEFAULTSHELL,
|
|
_UC_DEFAULTGROUP,
|
|
_UC_EXTRAGROUPS,
|
|
_UC_DEFAULTCLASS,
|
|
_UC_MINUID,
|
|
_UC_MAXUID,
|
|
_UC_MINGID,
|
|
_UC_MAXGID,
|
|
_UC_EXPIRE,
|
|
_UC_PASSWORD,
|
|
_UC_FIELDS
|
|
};
|
|
|
|
static char bourne_shell[] = "sh";
|
|
|
|
static char *system_shells[_UC_MAXSHELLS] =
|
|
{
|
|
bourne_shell,
|
|
"csh",
|
|
"tcsh"
|
|
};
|
|
|
|
static char const *booltrue[] =
|
|
{
|
|
"yes", "true", "1", "on", NULL
|
|
};
|
|
static char const *boolfalse[] =
|
|
{
|
|
"no", "false", "0", "off", NULL
|
|
};
|
|
|
|
static struct userconf config =
|
|
{
|
|
0, /* Default password for new users? (nologin) */
|
|
0, /* Reuse uids? */
|
|
0, /* Reuse gids? */
|
|
NULL, /* NIS version of the passwd file */
|
|
"/usr/share/skel", /* Where to obtain skeleton files */
|
|
NULL, /* Mail to send to new accounts */
|
|
"/var/log/userlog", /* Where to log changes */
|
|
"/home", /* Where to create home directory */
|
|
_DEF_DIRMODE, /* Home directory perms, modified by umask */
|
|
"/bin", /* Where shells are located */
|
|
system_shells, /* List of shells (first is default) */
|
|
bourne_shell, /* Default shell */
|
|
NULL, /* Default group name */
|
|
NULL, /* Default (additional) groups */
|
|
NULL, /* Default login class */
|
|
1000, 32000, /* Allowed range of uids */
|
|
1000, 32000, /* Allowed range of gids */
|
|
0, /* Days until account expires */
|
|
0 /* Days until password expires */
|
|
};
|
|
|
|
static char const *comments[_UC_FIELDS] =
|
|
{
|
|
"#\n# pw.conf - user/group configuration defaults\n#\n",
|
|
"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
|
|
"\n# Reuse gaps in uid sequence? (yes or no)\n",
|
|
"\n# Reuse gaps in gid sequence? (yes or no)\n",
|
|
"\n# Path to the NIS passwd file (blank or 'no' for none)\n",
|
|
"\n# Obtain default dotfiles from this directory\n",
|
|
"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
|
|
"\n# Log add/change/remove information in this file\n",
|
|
"\n# Root directory in which $HOME directory is created\n",
|
|
"\n# Mode for the new $HOME directory, will be modified by umask\n",
|
|
"\n# Colon separated list of directories containing valid shells\n",
|
|
"\n# Comma separated list of available shells (without paths)\n",
|
|
"\n# Default shell (without path)\n",
|
|
"\n# Default group (leave blank for new group per user)\n",
|
|
"\n# Extra groups for new users\n",
|
|
"\n# Default login class for new users\n",
|
|
"\n# Range of valid default user ids\n",
|
|
NULL,
|
|
"\n# Range of valid default group ids\n",
|
|
NULL,
|
|
"\n# Days after which account expires (0=disabled)\n",
|
|
"\n# Days after which password expires (0=disabled)\n"
|
|
};
|
|
|
|
static char const *kwds[] =
|
|
{
|
|
"",
|
|
"defaultpasswd",
|
|
"reuseuids",
|
|
"reusegids",
|
|
"nispasswd",
|
|
"skeleton",
|
|
"newmail",
|
|
"logfile",
|
|
"home",
|
|
"homemode",
|
|
"shellpath",
|
|
"shells",
|
|
"defaultshell",
|
|
"defaultgroup",
|
|
"extragroups",
|
|
"defaultclass",
|
|
"minuid",
|
|
"maxuid",
|
|
"mingid",
|
|
"maxgid",
|
|
"expire_days",
|
|
"password_days",
|
|
NULL
|
|
};
|
|
|
|
static char *
|
|
unquote(char const * str)
|
|
{
|
|
if (str && (*str == '"' || *str == '\'')) {
|
|
char *p = strchr(str + 1, *str);
|
|
|
|
if (p != NULL)
|
|
*p = '\0';
|
|
return (char *) (*++str ? str : NULL);
|
|
}
|
|
return (char *) str;
|
|
}
|
|
|
|
int
|
|
boolean_val(char const * str, int dflt)
|
|
{
|
|
if ((str = unquote(str)) != NULL) {
|
|
int i;
|
|
|
|
for (i = 0; booltrue[i]; i++)
|
|
if (strcmp(str, booltrue[i]) == 0)
|
|
return 1;
|
|
for (i = 0; boolfalse[i]; i++)
|
|
if (strcmp(str, boolfalse[i]) == 0)
|
|
return 0;
|
|
|
|
/*
|
|
* Special cases for defaultpassword
|
|
*/
|
|
if (strcmp(str, "random") == 0)
|
|
return -1;
|
|
if (strcmp(str, "none") == 0)
|
|
return -2;
|
|
}
|
|
return dflt;
|
|
}
|
|
|
|
char const *
|
|
boolean_str(int val)
|
|
{
|
|
if (val == -1)
|
|
return "random";
|
|
else if (val == -2)
|
|
return "none";
|
|
else
|
|
return val ? booltrue[0] : boolfalse[0];
|
|
}
|
|
|
|
char *
|
|
newstr(char const * p)
|
|
{
|
|
char *q;
|
|
|
|
if ((p = unquote(p)) == NULL)
|
|
return (NULL);
|
|
|
|
if ((q = strdup(p)) == NULL)
|
|
err(1, "strdup()");
|
|
|
|
return (q);
|
|
}
|
|
|
|
struct userconf *
|
|
read_userconfig(char const * file)
|
|
{
|
|
FILE *fp;
|
|
char *buf, *p;
|
|
const char *errstr;
|
|
size_t linecap;
|
|
ssize_t linelen;
|
|
|
|
buf = NULL;
|
|
linecap = 0;
|
|
|
|
if (file == NULL)
|
|
file = _PATH_PW_CONF;
|
|
|
|
if ((fp = fopen(file, "r")) == NULL)
|
|
return (&config);
|
|
|
|
while ((linelen = getline(&buf, &linecap, fp)) > 0) {
|
|
if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
|
|
static char const toks[] = " \t\r\n,=";
|
|
char *q = strtok(NULL, toks);
|
|
int i = 0;
|
|
mode_t *modeset;
|
|
|
|
while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
|
|
++i;
|
|
#if debugging
|
|
if (i == _UC_FIELDS)
|
|
printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
|
|
else
|
|
printf("Got kwd[%s]=%s\n", p, q);
|
|
#endif
|
|
switch (i) {
|
|
case _UC_DEFAULTPWD:
|
|
config.default_password = boolean_val(q, 1);
|
|
break;
|
|
case _UC_REUSEUID:
|
|
config.reuse_uids = boolean_val(q, 0);
|
|
break;
|
|
case _UC_REUSEGID:
|
|
config.reuse_gids = boolean_val(q, 0);
|
|
break;
|
|
case _UC_NISPASSWD:
|
|
config.nispasswd = (q == NULL || !boolean_val(q, 1))
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_DOTDIR:
|
|
config.dotdir = (q == NULL || !boolean_val(q, 1))
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_NEWMAIL:
|
|
config.newmail = (q == NULL || !boolean_val(q, 1))
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_LOGFILE:
|
|
config.logfile = (q == NULL || !boolean_val(q, 1))
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_HOMEROOT:
|
|
config.home = (q == NULL || !boolean_val(q, 1))
|
|
? "/home" : newstr(q);
|
|
break;
|
|
case _UC_HOMEMODE:
|
|
modeset = setmode(q);
|
|
config.homemode = (q == NULL || !boolean_val(q, 1))
|
|
? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
|
|
free(modeset);
|
|
break;
|
|
case _UC_SHELLPATH:
|
|
config.shelldir = (q == NULL || !boolean_val(q, 1))
|
|
? "/bin" : newstr(q);
|
|
break;
|
|
case _UC_SHELLS:
|
|
for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
|
|
system_shells[i] = newstr(q);
|
|
if (i > 0)
|
|
while (i < _UC_MAXSHELLS)
|
|
system_shells[i++] = NULL;
|
|
break;
|
|
case _UC_DEFAULTSHELL:
|
|
config.shell_default = (q == NULL || !boolean_val(q, 1))
|
|
? (char *) bourne_shell : newstr(q);
|
|
break;
|
|
case _UC_DEFAULTGROUP:
|
|
q = unquote(q);
|
|
config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_EXTRAGROUPS:
|
|
for (i = 0; q != NULL; q = strtok(NULL, toks)) {
|
|
if (config.groups == NULL)
|
|
config.groups = sl_init();
|
|
sl_add(config.groups, newstr(q));
|
|
}
|
|
break;
|
|
case _UC_DEFAULTCLASS:
|
|
config.default_class = (q == NULL || !boolean_val(q, 1))
|
|
? NULL : newstr(q);
|
|
break;
|
|
case _UC_MINUID:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.min_uid = strtounum(q, 0,
|
|
UID_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid min_uid: '%s';"
|
|
" ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_MAXUID:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.max_uid = strtounum(q, 0,
|
|
UID_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid max_uid: '%s';"
|
|
" ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_MINGID:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.min_gid = strtounum(q, 0,
|
|
GID_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid min_gid: '%s';"
|
|
" ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_MAXGID:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.max_gid = strtounum(q, 0,
|
|
GID_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid max_gid: '%s';"
|
|
" ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_EXPIRE:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.expire_days = strtonum(q, 0,
|
|
INT_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid expire days:"
|
|
" '%s'; ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_PASSWORD:
|
|
if ((q = unquote(q)) != NULL) {
|
|
config.password_days = strtonum(q, 0,
|
|
INT_MAX, &errstr);
|
|
if (errstr)
|
|
warnx("Invalid password days:"
|
|
" '%s'; ignoring", q);
|
|
}
|
|
break;
|
|
case _UC_FIELDS:
|
|
case _UC_NONE:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(buf);
|
|
fclose(fp);
|
|
|
|
return (&config);
|
|
}
|
|
|
|
|
|
int
|
|
write_userconfig(struct userconf *cnf, const char *file)
|
|
{
|
|
int fd;
|
|
int i, j;
|
|
struct sbuf *buf;
|
|
FILE *fp;
|
|
|
|
if (file == NULL)
|
|
file = _PATH_PW_CONF;
|
|
|
|
if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
|
|
return (0);
|
|
|
|
if ((fp = fdopen(fd, "w")) == NULL) {
|
|
close(fd);
|
|
return (0);
|
|
}
|
|
|
|
buf = sbuf_new_auto();
|
|
for (i = _UC_NONE; i < _UC_FIELDS; i++) {
|
|
int quote = 1;
|
|
|
|
sbuf_clear(buf);
|
|
switch (i) {
|
|
case _UC_DEFAULTPWD:
|
|
sbuf_cat(buf, boolean_str(cnf->default_password));
|
|
break;
|
|
case _UC_REUSEUID:
|
|
sbuf_cat(buf, boolean_str(cnf->reuse_uids));
|
|
break;
|
|
case _UC_REUSEGID:
|
|
sbuf_cat(buf, boolean_str(cnf->reuse_gids));
|
|
break;
|
|
case _UC_NISPASSWD:
|
|
sbuf_cat(buf, cnf->nispasswd ? cnf->nispasswd : "");
|
|
quote = 0;
|
|
break;
|
|
case _UC_DOTDIR:
|
|
sbuf_cat(buf, cnf->dotdir ? cnf->dotdir :
|
|
boolean_str(0));
|
|
break;
|
|
case _UC_NEWMAIL:
|
|
sbuf_cat(buf, cnf->newmail ? cnf->newmail :
|
|
boolean_str(0));
|
|
break;
|
|
case _UC_LOGFILE:
|
|
sbuf_cat(buf, cnf->logfile ? cnf->logfile :
|
|
boolean_str(0));
|
|
break;
|
|
case _UC_HOMEROOT:
|
|
sbuf_cat(buf, cnf->home);
|
|
break;
|
|
case _UC_HOMEMODE:
|
|
sbuf_printf(buf, "%04o", cnf->homemode);
|
|
quote = 0;
|
|
break;
|
|
case _UC_SHELLPATH:
|
|
sbuf_cat(buf, cnf->shelldir);
|
|
break;
|
|
case _UC_SHELLS:
|
|
for (j = 0; j < _UC_MAXSHELLS &&
|
|
system_shells[j] != NULL; j++)
|
|
sbuf_printf(buf, "%s\"%s\"", j ?
|
|
"," : "", system_shells[j]);
|
|
quote = 0;
|
|
break;
|
|
case _UC_DEFAULTSHELL:
|
|
sbuf_cat(buf, cnf->shell_default ?
|
|
cnf->shell_default : bourne_shell);
|
|
break;
|
|
case _UC_DEFAULTGROUP:
|
|
sbuf_cat(buf, cnf->default_group ?
|
|
cnf->default_group : "");
|
|
break;
|
|
case _UC_EXTRAGROUPS:
|
|
for (j = 0; cnf->groups != NULL &&
|
|
j < (int)cnf->groups->sl_cur; j++)
|
|
sbuf_printf(buf, "%s\"%s\"", j ?
|
|
"," : "", cnf->groups->sl_str[j]);
|
|
quote = 0;
|
|
break;
|
|
case _UC_DEFAULTCLASS:
|
|
sbuf_cat(buf, cnf->default_class ?
|
|
cnf->default_class : "");
|
|
break;
|
|
case _UC_MINUID:
|
|
sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid);
|
|
quote = 0;
|
|
break;
|
|
case _UC_MAXUID:
|
|
sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid);
|
|
quote = 0;
|
|
break;
|
|
case _UC_MINGID:
|
|
sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid);
|
|
quote = 0;
|
|
break;
|
|
case _UC_MAXGID:
|
|
sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid);
|
|
quote = 0;
|
|
break;
|
|
case _UC_EXPIRE:
|
|
sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days);
|
|
quote = 0;
|
|
break;
|
|
case _UC_PASSWORD:
|
|
sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days);
|
|
quote = 0;
|
|
break;
|
|
case _UC_NONE:
|
|
break;
|
|
}
|
|
sbuf_finish(buf);
|
|
|
|
if (comments[i])
|
|
fputs(comments[i], fp);
|
|
|
|
if (*kwds[i]) {
|
|
if (quote)
|
|
fprintf(fp, "%s = \"%s\"\n", kwds[i],
|
|
sbuf_data(buf));
|
|
else
|
|
fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
|
|
#if debugging
|
|
printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
|
|
#endif
|
|
}
|
|
}
|
|
sbuf_delete(buf);
|
|
return (fclose(fp) != EOF);
|
|
}
|