xref: /freebsd/usr.sbin/pw/pw_conf.c (revision f1951fd745b894fe6586c298874af98544a5e272)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (C) 1996
5  *	David L. Nugent.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
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.
15  *
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
26  * SUCH DAMAGE.
27  */
28 
29 #ifndef lint
30 static const char rcsid[] =
31   "$FreeBSD$";
32 #endif /* not lint */
33 
34 #include <sys/types.h>
35 #include <sys/sbuf.h>
36 
37 #include <err.h>
38 #include <fcntl.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "pw.h"
43 
44 #define debugging 0
45 
46 enum {
47 	_UC_NONE,
48 	_UC_DEFAULTPWD,
49 	_UC_REUSEUID,
50 	_UC_REUSEGID,
51 	_UC_NISPASSWD,
52 	_UC_DOTDIR,
53 	_UC_NEWMAIL,
54 	_UC_LOGFILE,
55 	_UC_HOMEROOT,
56 	_UC_HOMEMODE,
57 	_UC_SHELLPATH,
58 	_UC_SHELLS,
59 	_UC_DEFAULTSHELL,
60 	_UC_DEFAULTGROUP,
61 	_UC_EXTRAGROUPS,
62 	_UC_DEFAULTCLASS,
63 	_UC_MINUID,
64 	_UC_MAXUID,
65 	_UC_MINGID,
66 	_UC_MAXGID,
67 	_UC_EXPIRE,
68 	_UC_PASSWORD,
69 	_UC_FIELDS
70 };
71 
72 static char     bourne_shell[] = "sh";
73 
74 static char    *system_shells[_UC_MAXSHELLS] =
75 {
76 	bourne_shell,
77 	"csh",
78 	"tcsh"
79 };
80 
81 static char const *booltrue[] =
82 {
83 	"yes", "true", "1", "on", NULL
84 };
85 static char const *boolfalse[] =
86 {
87 	"no", "false", "0", "off", NULL
88 };
89 
90 static struct userconf config =
91 {
92 	0,			/* Default password for new users? (nologin) */
93 	0,			/* Reuse uids? */
94 	0,			/* Reuse gids? */
95 	NULL,			/* NIS version of the passwd file */
96 	"/usr/share/skel",	/* Where to obtain skeleton files */
97 	NULL,			/* Mail to send to new accounts */
98 	"/var/log/userlog",	/* Where to log changes */
99 	"/home",		/* Where to create home directory */
100 	_DEF_DIRMODE,		/* Home directory perms, modified by umask */
101 	"/bin",			/* Where shells are located */
102 	system_shells,		/* List of shells (first is default) */
103 	bourne_shell,		/* Default shell */
104 	NULL,			/* Default group name */
105 	NULL,			/* Default (additional) groups */
106 	NULL,			/* Default login class */
107 	1000, 32000,		/* Allowed range of uids */
108 	1000, 32000,		/* Allowed range of gids */
109 	0,			/* Days until account expires */
110 	0			/* Days until password expires */
111 };
112 
113 static char const *comments[_UC_FIELDS] =
114 {
115 	"#\n# pw.conf - user/group configuration defaults\n#\n",
116 	"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
117 	"\n# Reuse gaps in uid sequence? (yes or no)\n",
118 	"\n# Reuse gaps in gid sequence? (yes or no)\n",
119 	"\n# Path to the NIS passwd file (blank or 'no' for none)\n",
120 	"\n# Obtain default dotfiles from this directory\n",
121 	"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
122 	"\n# Log add/change/remove information in this file\n",
123 	"\n# Root directory in which $HOME directory is created\n",
124 	"\n# Mode for the new $HOME directory, will be modified by umask\n",
125 	"\n# Colon separated list of directories containing valid shells\n",
126 	"\n# Comma separated list of available shells (without paths)\n",
127 	"\n# Default shell (without path)\n",
128 	"\n# Default group (leave blank for new group per user)\n",
129 	"\n# Extra groups for new users\n",
130 	"\n# Default login class for new users\n",
131 	"\n# Range of valid default user ids\n",
132 	NULL,
133 	"\n# Range of valid default group ids\n",
134 	NULL,
135 	"\n# Days after which account expires (0=disabled)\n",
136 	"\n# Days after which password expires (0=disabled)\n"
137 };
138 
139 static char const *kwds[] =
140 {
141 	"",
142 	"defaultpasswd",
143 	"reuseuids",
144 	"reusegids",
145 	"nispasswd",
146 	"skeleton",
147 	"newmail",
148 	"logfile",
149 	"home",
150 	"homemode",
151 	"shellpath",
152 	"shells",
153 	"defaultshell",
154 	"defaultgroup",
155 	"extragroups",
156 	"defaultclass",
157 	"minuid",
158 	"maxuid",
159 	"mingid",
160 	"maxgid",
161 	"expire_days",
162 	"password_days",
163 	NULL
164 };
165 
166 static char    *
167 unquote(char const * str)
168 {
169 	if (str && (*str == '"' || *str == '\'')) {
170 		char           *p = strchr(str + 1, *str);
171 
172 		if (p != NULL)
173 			*p = '\0';
174 		return (char *) (*++str ? str : NULL);
175 	}
176 	return (char *) str;
177 }
178 
179 int
180 boolean_val(char const * str, int dflt)
181 {
182 	if ((str = unquote(str)) != NULL) {
183 		int             i;
184 
185 		for (i = 0; booltrue[i]; i++)
186 			if (strcmp(str, booltrue[i]) == 0)
187 				return 1;
188 		for (i = 0; boolfalse[i]; i++)
189 			if (strcmp(str, boolfalse[i]) == 0)
190 				return 0;
191 	}
192 	return dflt;
193 }
194 
195 int
196 passwd_val(char const * str, int dflt)
197 {
198 	if ((str = unquote(str)) != NULL) {
199 		int             i;
200 
201 		for (i = 0; booltrue[i]; i++)
202 			if (strcmp(str, booltrue[i]) == 0)
203 				return P_YES;
204 		for (i = 0; boolfalse[i]; i++)
205 			if (strcmp(str, boolfalse[i]) == 0)
206 				return P_NO;
207 
208 		/*
209 		 * Special cases for defaultpassword
210 		 */
211 		if (strcmp(str, "random") == 0)
212 			return P_RANDOM;
213 		if (strcmp(str, "none") == 0)
214 			return P_NONE;
215 
216 		errx(1, "Invalid value for default password");
217 	}
218 	return dflt;
219 }
220 
221 char const     *
222 boolean_str(int val)
223 {
224 	if (val == -1)
225 		return "random";
226 	else if (val == -2)
227 		return "none";
228 	else
229 		return val ? booltrue[0] : boolfalse[0];
230 }
231 
232 char           *
233 newstr(char const * p)
234 {
235 	char	*q;
236 
237 	if ((p = unquote(p)) == NULL)
238 		return (NULL);
239 
240 	if ((q = strdup(p)) == NULL)
241 		err(1, "strdup()");
242 
243 	return (q);
244 }
245 
246 struct userconf *
247 read_userconfig(char const * file)
248 {
249 	FILE	*fp;
250 	char	*buf, *p;
251 	const char *errstr;
252 	size_t	linecap;
253 	ssize_t	linelen;
254 
255 	buf = NULL;
256 	linecap = 0;
257 
258 	if (file == NULL)
259 		file = _PATH_PW_CONF;
260 
261 	if ((fp = fopen(file, "r")) == NULL)
262 		return (&config);
263 
264 	while ((linelen = getline(&buf, &linecap, fp)) > 0) {
265 		if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
266 			static char const toks[] = " \t\r\n,=";
267 			char           *q = strtok(NULL, toks);
268 			int             i = 0;
269 			mode_t          *modeset;
270 
271 			while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
272 				++i;
273 #if debugging
274 			if (i == _UC_FIELDS)
275 				printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
276 			else
277 				printf("Got kwd[%s]=%s\n", p, q);
278 #endif
279 			switch (i) {
280 			case _UC_DEFAULTPWD:
281 				config.default_password = passwd_val(q, 1);
282 				break;
283 			case _UC_REUSEUID:
284 				config.reuse_uids = boolean_val(q, 0);
285 				break;
286 			case _UC_REUSEGID:
287 				config.reuse_gids = boolean_val(q, 0);
288 				break;
289 			case _UC_NISPASSWD:
290 				config.nispasswd = (q == NULL || !boolean_val(q, 1))
291 					? NULL : newstr(q);
292 				break;
293 			case _UC_DOTDIR:
294 				config.dotdir = (q == NULL || !boolean_val(q, 1))
295 					? NULL : newstr(q);
296 				break;
297 				case _UC_NEWMAIL:
298 				config.newmail = (q == NULL || !boolean_val(q, 1))
299 					? NULL : newstr(q);
300 				break;
301 			case _UC_LOGFILE:
302 				config.logfile = (q == NULL || !boolean_val(q, 1))
303 					? NULL : newstr(q);
304 				break;
305 			case _UC_HOMEROOT:
306 				config.home = (q == NULL || !boolean_val(q, 1))
307 					? "/home" : newstr(q);
308 				break;
309 			case _UC_HOMEMODE:
310 				modeset = setmode(q);
311 				config.homemode = (q == NULL || !boolean_val(q, 1))
312 					? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
313 				free(modeset);
314 				break;
315 			case _UC_SHELLPATH:
316 				config.shelldir = (q == NULL || !boolean_val(q, 1))
317 					? "/bin" : newstr(q);
318 				break;
319 			case _UC_SHELLS:
320 				for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
321 					system_shells[i] = newstr(q);
322 				if (i > 0)
323 					while (i < _UC_MAXSHELLS)
324 						system_shells[i++] = NULL;
325 				break;
326 			case _UC_DEFAULTSHELL:
327 				config.shell_default = (q == NULL || !boolean_val(q, 1))
328 					? (char *) bourne_shell : newstr(q);
329 				break;
330 			case _UC_DEFAULTGROUP:
331 				q = unquote(q);
332 				config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
333 					? NULL : newstr(q);
334 				break;
335 			case _UC_EXTRAGROUPS:
336 				while ((q = strtok(NULL, toks)) != NULL) {
337 					if (config.groups == NULL)
338 						config.groups = sl_init();
339 					sl_add(config.groups, newstr(q));
340 				}
341 				break;
342 			case _UC_DEFAULTCLASS:
343 				config.default_class = (q == NULL || !boolean_val(q, 1))
344 					? NULL : newstr(q);
345 				break;
346 			case _UC_MINUID:
347 				if ((q = unquote(q)) != NULL) {
348 					config.min_uid = strtounum(q, 0,
349 					    UID_MAX, &errstr);
350 					if (errstr)
351 						warnx("Invalid min_uid: '%s';"
352 						    " ignoring", q);
353 				}
354 				break;
355 			case _UC_MAXUID:
356 				if ((q = unquote(q)) != NULL) {
357 					config.max_uid = strtounum(q, 0,
358 					    UID_MAX, &errstr);
359 					if (errstr)
360 						warnx("Invalid max_uid: '%s';"
361 						    " ignoring", q);
362 				}
363 				break;
364 			case _UC_MINGID:
365 				if ((q = unquote(q)) != NULL) {
366 					config.min_gid = strtounum(q, 0,
367 					    GID_MAX, &errstr);
368 					if (errstr)
369 						warnx("Invalid min_gid: '%s';"
370 						    " ignoring", q);
371 				}
372 				break;
373 			case _UC_MAXGID:
374 				if ((q = unquote(q)) != NULL) {
375 					config.max_gid = strtounum(q, 0,
376 					    GID_MAX, &errstr);
377 					if (errstr)
378 						warnx("Invalid max_gid: '%s';"
379 						    " ignoring", q);
380 				}
381 				break;
382 			case _UC_EXPIRE:
383 				if ((q = unquote(q)) != NULL) {
384 					config.expire_days = strtonum(q, 0,
385 					    INT_MAX, &errstr);
386 					if (errstr)
387 						warnx("Invalid expire days:"
388 						    " '%s'; ignoring", q);
389 				}
390 				break;
391 			case _UC_PASSWORD:
392 				if ((q = unquote(q)) != NULL) {
393 					config.password_days = strtonum(q, 0,
394 					    INT_MAX, &errstr);
395 					if (errstr)
396 						warnx("Invalid password days:"
397 						    " '%s'; ignoring", q);
398 				}
399 				break;
400 			case _UC_FIELDS:
401 			case _UC_NONE:
402 				break;
403 			}
404 		}
405 	}
406 	free(buf);
407 	fclose(fp);
408 
409 	return (&config);
410 }
411 
412 
413 int
414 write_userconfig(struct userconf *cnf, const char *file)
415 {
416 	int             fd;
417 	int             i, j;
418 	struct sbuf	*buf;
419 	FILE           *fp;
420 
421 	if (file == NULL)
422 		file = _PATH_PW_CONF;
423 
424 	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
425 		return (0);
426 
427 	if ((fp = fdopen(fd, "w")) == NULL) {
428 		close(fd);
429 		return (0);
430 	}
431 
432 	buf = sbuf_new_auto();
433 	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
434 		int             quote = 1;
435 
436 		sbuf_clear(buf);
437 		switch (i) {
438 		case _UC_DEFAULTPWD:
439 			sbuf_cat(buf, boolean_str(cnf->default_password));
440 			break;
441 		case _UC_REUSEUID:
442 			sbuf_cat(buf, boolean_str(cnf->reuse_uids));
443 			break;
444 		case _UC_REUSEGID:
445 			sbuf_cat(buf, boolean_str(cnf->reuse_gids));
446 			break;
447 		case _UC_NISPASSWD:
448 			sbuf_cat(buf, cnf->nispasswd ?  cnf->nispasswd : "");
449 			quote = 0;
450 			break;
451 		case _UC_DOTDIR:
452 			sbuf_cat(buf, cnf->dotdir ?  cnf->dotdir :
453 			    boolean_str(0));
454 			break;
455 		case _UC_NEWMAIL:
456 			sbuf_cat(buf, cnf->newmail ?  cnf->newmail :
457 			    boolean_str(0));
458 			break;
459 		case _UC_LOGFILE:
460 			sbuf_cat(buf, cnf->logfile ?  cnf->logfile :
461 			    boolean_str(0));
462 			break;
463 		case _UC_HOMEROOT:
464 			sbuf_cat(buf, cnf->home);
465 			break;
466 		case _UC_HOMEMODE:
467 			sbuf_printf(buf, "%04o", cnf->homemode);
468 			quote = 0;
469 			break;
470 		case _UC_SHELLPATH:
471 			sbuf_cat(buf, cnf->shelldir);
472 			break;
473 		case _UC_SHELLS:
474 			for (j = 0; j < _UC_MAXSHELLS &&
475 			    system_shells[j] != NULL; j++)
476 				sbuf_printf(buf, "%s\"%s\"", j ?
477 				    "," : "", system_shells[j]);
478 			quote = 0;
479 			break;
480 		case _UC_DEFAULTSHELL:
481 			sbuf_cat(buf, cnf->shell_default ?
482 			    cnf->shell_default : bourne_shell);
483 			break;
484 		case _UC_DEFAULTGROUP:
485 			sbuf_cat(buf, cnf->default_group ?
486 			    cnf->default_group : "");
487 			break;
488 		case _UC_EXTRAGROUPS:
489 			for (j = 0; cnf->groups != NULL &&
490 			    j < (int)cnf->groups->sl_cur; j++)
491 				sbuf_printf(buf, "%s\"%s\"", j ?
492 				    "," : "", cnf->groups->sl_str[j]);
493 			quote = 0;
494 			break;
495 		case _UC_DEFAULTCLASS:
496 			sbuf_cat(buf, cnf->default_class ?
497 			    cnf->default_class : "");
498 			break;
499 		case _UC_MINUID:
500 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid);
501 			quote = 0;
502 			break;
503 		case _UC_MAXUID:
504 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid);
505 			quote = 0;
506 			break;
507 		case _UC_MINGID:
508 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid);
509 			quote = 0;
510 			break;
511 		case _UC_MAXGID:
512 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid);
513 			quote = 0;
514 			break;
515 		case _UC_EXPIRE:
516 			sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days);
517 			quote = 0;
518 			break;
519 		case _UC_PASSWORD:
520 			sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days);
521 			quote = 0;
522 			break;
523 		case _UC_NONE:
524 			break;
525 		}
526 		sbuf_finish(buf);
527 
528 		if (comments[i])
529 			fputs(comments[i], fp);
530 
531 		if (*kwds[i]) {
532 			if (quote)
533 				fprintf(fp, "%s = \"%s\"\n", kwds[i],
534 				    sbuf_data(buf));
535 			else
536 				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
537 #if debugging
538 			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
539 #endif
540 		}
541 	}
542 	sbuf_delete(buf);
543 	return (fclose(fp) != EOF);
544 }
545