2 * SPDX-License-Identifier: BSD-2-Clause
5 * David L. Nugent. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
64 static char bourne_shell[] = "sh";
66 static char *system_shells[_UC_MAXSHELLS] =
73 static char const *booltrue[] =
75 "yes", "true", "1", "on", NULL
77 static char const *boolfalse[] =
79 "no", "false", "0", "off", NULL
82 static struct userconf config =
84 0, /* Default password for new users? (nologin) */
87 NULL, /* NIS version of the passwd file */
88 "/usr/share/skel", /* Where to obtain skeleton files */
89 NULL, /* Mail to send to new accounts */
90 "/var/log/userlog", /* Where to log changes */
91 "/home", /* Where to create home directory */
92 _DEF_DIRMODE, /* Home directory perms, modified by umask */
93 "/bin", /* Where shells are located */
94 system_shells, /* List of shells (first is default) */
95 bourne_shell, /* Default shell */
96 NULL, /* Default group name */
97 NULL, /* Default (additional) groups */
98 NULL, /* Default login class */
99 1000, 32000, /* Allowed range of uids */
100 1000, 32000, /* Allowed range of gids */
101 0, /* Days until account expires */
102 0 /* Days until password expires */
105 static char const *comments[_UC_FIELDS] =
107 "#\n# pw.conf - user/group configuration defaults\n#\n",
108 "\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
109 "\n# Reuse gaps in uid sequence? (yes or no)\n",
110 "\n# Reuse gaps in gid sequence? (yes or no)\n",
111 "\n# Path to the NIS passwd file (blank or 'no' for none)\n",
112 "\n# Obtain default dotfiles from this directory\n",
113 "\n# Mail this file to new user (/etc/newuser.msg or no)\n",
114 "\n# Log add/change/remove information in this file\n",
115 "\n# Root directory in which $HOME directory is created\n",
116 "\n# Mode for the new $HOME directory, will be modified by umask\n",
117 "\n# Colon separated list of directories containing valid shells\n",
118 "\n# Comma separated list of available shells (without paths)\n",
119 "\n# Default shell (without path)\n",
120 "\n# Default group (leave blank for new group per user)\n",
121 "\n# Extra groups for new users\n",
122 "\n# Default login class for new users\n",
123 "\n# Range of valid default user ids\n",
125 "\n# Range of valid default group ids\n",
127 "\n# Days after which account expires (0=disabled)\n",
128 "\n# Days after which password expires (0=disabled)\n"
131 static char const *kwds[] =
159 unquote(char const * str)
161 if (str && (*str == '"' || *str == '\'')) {
162 char *p = strchr(str + 1, *str);
166 return (char *) (*++str ? str : NULL);
172 boolean_val(char const * str, int dflt)
174 if ((str = unquote(str)) != NULL) {
177 for (i = 0; booltrue[i]; i++)
178 if (strcmp(str, booltrue[i]) == 0)
180 for (i = 0; boolfalse[i]; i++)
181 if (strcmp(str, boolfalse[i]) == 0)
188 passwd_val(char const * str, int dflt)
190 if ((str = unquote(str)) != NULL) {
193 for (i = 0; booltrue[i]; i++)
194 if (strcmp(str, booltrue[i]) == 0)
196 for (i = 0; boolfalse[i]; i++)
197 if (strcmp(str, boolfalse[i]) == 0)
201 * Special cases for defaultpassword
203 if (strcmp(str, "random") == 0)
205 if (strcmp(str, "none") == 0)
208 errx(1, "Invalid value for default password");
217 return (boolfalse[0]);
218 else if (val == P_RANDOM)
220 else if (val == P_NONE)
223 return (booltrue[0]);
227 newstr(char const * p)
231 if ((p = unquote(p)) == NULL)
234 if ((q = strdup(p)) == NULL)
241 read_userconfig(char const * file)
252 if ((fp = fopen(file, "r")) == NULL)
255 while ((linelen = getline(&buf, &linecap, fp)) > 0) {
256 if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
257 static char const toks[] = " \t\r\n,=";
258 char *q = strtok(NULL, toks);
262 while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
266 printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
268 printf("Got kwd[%s]=%s\n", p, q);
272 config.default_password = passwd_val(q, 1);
275 config.reuse_uids = boolean_val(q, 0);
278 config.reuse_gids = boolean_val(q, 0);
281 config.nispasswd = (q == NULL || !boolean_val(q, 1))
285 config.dotdir = (q == NULL || !boolean_val(q, 1))
289 config.newmail = (q == NULL || !boolean_val(q, 1))
293 config.logfile = (q == NULL || !boolean_val(q, 1))
297 config.home = (q == NULL || !boolean_val(q, 1))
298 ? "/home" : newstr(q);
301 modeset = setmode(q);
302 config.homemode = (q == NULL || !boolean_val(q, 1))
303 ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
307 config.shelldir = (q == NULL || !boolean_val(q, 1))
308 ? "/bin" : newstr(q);
311 for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
312 system_shells[i] = newstr(q);
314 while (i < _UC_MAXSHELLS)
315 system_shells[i++] = NULL;
317 case _UC_DEFAULTSHELL:
318 config.shell_default = (q == NULL || !boolean_val(q, 1))
319 ? (char *) bourne_shell : newstr(q);
321 case _UC_DEFAULTGROUP:
323 config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
326 case _UC_EXTRAGROUPS:
327 while ((q = strtok(NULL, toks)) != NULL) {
328 if (config.groups == NULL)
329 config.groups = sl_init();
330 sl_add(config.groups, newstr(q));
333 case _UC_DEFAULTCLASS:
334 config.default_class = (q == NULL || !boolean_val(q, 1))
338 if ((q = unquote(q)) != NULL) {
339 config.min_uid = strtounum(q, 0,
342 warnx("Invalid min_uid: '%s';"
347 if ((q = unquote(q)) != NULL) {
348 config.max_uid = strtounum(q, 0,
351 warnx("Invalid max_uid: '%s';"
356 if ((q = unquote(q)) != NULL) {
357 config.min_gid = strtounum(q, 0,
360 warnx("Invalid min_gid: '%s';"
365 if ((q = unquote(q)) != NULL) {
366 config.max_gid = strtounum(q, 0,
369 warnx("Invalid max_gid: '%s';"
374 if ((q = unquote(q)) != NULL) {
375 config.expire_days = strtonum(q, 0,
378 warnx("Invalid expire days:"
379 " '%s'; ignoring", q);
383 if ((q = unquote(q)) != NULL) {
384 config.password_days = strtonum(q, 0,
387 warnx("Invalid password days:"
388 " '%s'; ignoring", q);
405 write_userconfig(struct userconf *cnf, const char *file)
411 char cfgfile[MAXPATHLEN];
416 snprintf(cfgfile, sizeof(cfgfile), "%s/" _PW_CONF,
421 if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
424 if ((fp = fdopen(fd, "w")) == NULL) {
431 buffp = open_memstream(&buf, &sz);
433 err(EXIT_FAILURE, "open_memstream()");
435 for (i = _UC_NONE; i < _UC_FIELDS; i++) {
443 fputs(boolean_str(cnf->default_password), buffp);
446 fputs(boolean_str(cnf->reuse_uids), buffp);
449 fputs(boolean_str(cnf->reuse_gids), buffp);
452 fputs(cnf->nispasswd ? cnf->nispasswd : "", buffp);
456 fputs(cnf->dotdir ? cnf->dotdir : boolean_str(0),
460 fputs(cnf->newmail ? cnf->newmail : boolean_str(0),
464 fputs(cnf->logfile ? cnf->logfile : boolean_str(0),
468 fputs(cnf->home, buffp);
471 fprintf(buffp, "%04o", cnf->homemode);
475 fputs(cnf->shelldir, buffp);
478 for (j = 0; j < _UC_MAXSHELLS &&
479 system_shells[j] != NULL; j++)
480 fprintf(buffp, "%s\"%s\"", j ?
481 "," : "", system_shells[j]);
484 case _UC_DEFAULTSHELL:
485 fputs(cnf->shell_default ? cnf->shell_default :
486 bourne_shell, buffp);
488 case _UC_DEFAULTGROUP:
489 fputs(cnf->default_group ? cnf->default_group : "",
492 case _UC_EXTRAGROUPS:
493 for (j = 0; cnf->groups != NULL &&
494 j < (int)cnf->groups->sl_cur; j++)
495 fprintf(buffp, "%s\"%s\"", j ?
496 "," : "", cnf->groups->sl_str[j]);
499 case _UC_DEFAULTCLASS:
500 fputs(cnf->default_class ? cnf->default_class : "",
504 fprintf(buffp, "%ju", (uintmax_t)cnf->min_uid);
508 fprintf(buffp, "%ju", (uintmax_t)cnf->max_uid);
512 fprintf(buffp, "%ju", (uintmax_t)cnf->min_gid);
516 fprintf(buffp, "%ju", (uintmax_t)cnf->max_gid);
520 fprintf(buffp, "%jd", (intmax_t)cnf->expire_days);
524 fprintf(buffp, "%jd", (intmax_t)cnf->password_days);
533 fputs(comments[i], fp);
537 fprintf(fp, "%s = \"%s\"\n", kwds[i], buf);
539 fprintf(fp, "%s = %s\n", kwds[i], buf);
541 printf("WROTE: %s = %s\n", kwds[i], buf);
547 return (fclose(fp) != EOF);