xref: /freebsd/usr.sbin/pw/pw_conf.c (revision aee4c74cdd59cd847af5a02de8c8e93bb31ac9fa)
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 ((fp = fopen(file, "r")) == NULL)
259 		return (&config);
260 
261 	while ((linelen = getline(&buf, &linecap, fp)) > 0) {
262 		if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
263 			static char const toks[] = " \t\r\n,=";
264 			char           *q = strtok(NULL, toks);
265 			int             i = 0;
266 			mode_t          *modeset;
267 
268 			while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
269 				++i;
270 #if debugging
271 			if (i == _UC_FIELDS)
272 				printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
273 			else
274 				printf("Got kwd[%s]=%s\n", p, q);
275 #endif
276 			switch (i) {
277 			case _UC_DEFAULTPWD:
278 				config.default_password = passwd_val(q, 1);
279 				break;
280 			case _UC_REUSEUID:
281 				config.reuse_uids = boolean_val(q, 0);
282 				break;
283 			case _UC_REUSEGID:
284 				config.reuse_gids = boolean_val(q, 0);
285 				break;
286 			case _UC_NISPASSWD:
287 				config.nispasswd = (q == NULL || !boolean_val(q, 1))
288 					? NULL : newstr(q);
289 				break;
290 			case _UC_DOTDIR:
291 				config.dotdir = (q == NULL || !boolean_val(q, 1))
292 					? NULL : newstr(q);
293 				break;
294 				case _UC_NEWMAIL:
295 				config.newmail = (q == NULL || !boolean_val(q, 1))
296 					? NULL : newstr(q);
297 				break;
298 			case _UC_LOGFILE:
299 				config.logfile = (q == NULL || !boolean_val(q, 1))
300 					? NULL : newstr(q);
301 				break;
302 			case _UC_HOMEROOT:
303 				config.home = (q == NULL || !boolean_val(q, 1))
304 					? "/home" : newstr(q);
305 				break;
306 			case _UC_HOMEMODE:
307 				modeset = setmode(q);
308 				config.homemode = (q == NULL || !boolean_val(q, 1))
309 					? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
310 				free(modeset);
311 				break;
312 			case _UC_SHELLPATH:
313 				config.shelldir = (q == NULL || !boolean_val(q, 1))
314 					? "/bin" : newstr(q);
315 				break;
316 			case _UC_SHELLS:
317 				for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
318 					system_shells[i] = newstr(q);
319 				if (i > 0)
320 					while (i < _UC_MAXSHELLS)
321 						system_shells[i++] = NULL;
322 				break;
323 			case _UC_DEFAULTSHELL:
324 				config.shell_default = (q == NULL || !boolean_val(q, 1))
325 					? (char *) bourne_shell : newstr(q);
326 				break;
327 			case _UC_DEFAULTGROUP:
328 				q = unquote(q);
329 				config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
330 					? NULL : newstr(q);
331 				break;
332 			case _UC_EXTRAGROUPS:
333 				while ((q = strtok(NULL, toks)) != NULL) {
334 					if (config.groups == NULL)
335 						config.groups = sl_init();
336 					sl_add(config.groups, newstr(q));
337 				}
338 				break;
339 			case _UC_DEFAULTCLASS:
340 				config.default_class = (q == NULL || !boolean_val(q, 1))
341 					? NULL : newstr(q);
342 				break;
343 			case _UC_MINUID:
344 				if ((q = unquote(q)) != NULL) {
345 					config.min_uid = strtounum(q, 0,
346 					    UID_MAX, &errstr);
347 					if (errstr)
348 						warnx("Invalid min_uid: '%s';"
349 						    " ignoring", q);
350 				}
351 				break;
352 			case _UC_MAXUID:
353 				if ((q = unquote(q)) != NULL) {
354 					config.max_uid = strtounum(q, 0,
355 					    UID_MAX, &errstr);
356 					if (errstr)
357 						warnx("Invalid max_uid: '%s';"
358 						    " ignoring", q);
359 				}
360 				break;
361 			case _UC_MINGID:
362 				if ((q = unquote(q)) != NULL) {
363 					config.min_gid = strtounum(q, 0,
364 					    GID_MAX, &errstr);
365 					if (errstr)
366 						warnx("Invalid min_gid: '%s';"
367 						    " ignoring", q);
368 				}
369 				break;
370 			case _UC_MAXGID:
371 				if ((q = unquote(q)) != NULL) {
372 					config.max_gid = strtounum(q, 0,
373 					    GID_MAX, &errstr);
374 					if (errstr)
375 						warnx("Invalid max_gid: '%s';"
376 						    " ignoring", q);
377 				}
378 				break;
379 			case _UC_EXPIRE:
380 				if ((q = unquote(q)) != NULL) {
381 					config.expire_days = strtonum(q, 0,
382 					    INT_MAX, &errstr);
383 					if (errstr)
384 						warnx("Invalid expire days:"
385 						    " '%s'; ignoring", q);
386 				}
387 				break;
388 			case _UC_PASSWORD:
389 				if ((q = unquote(q)) != NULL) {
390 					config.password_days = strtonum(q, 0,
391 					    INT_MAX, &errstr);
392 					if (errstr)
393 						warnx("Invalid password days:"
394 						    " '%s'; ignoring", q);
395 				}
396 				break;
397 			case _UC_FIELDS:
398 			case _UC_NONE:
399 				break;
400 			}
401 		}
402 	}
403 	free(buf);
404 	fclose(fp);
405 
406 	return (&config);
407 }
408 
409 
410 int
411 write_userconfig(struct userconf *cnf, const char *file)
412 {
413 	int             fd;
414 	int             i, j;
415 	struct sbuf	*buf;
416 	FILE           *fp;
417 	char		cfgfile[MAXPATHLEN];
418 
419 	if (file == NULL) {
420 		snprintf(cfgfile, sizeof(cfgfile), "%s/" _PW_CONF,
421 		    conf.etcpath);
422 		file = cfgfile;
423 	}
424 
425 	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
426 		return (0);
427 
428 	if ((fp = fdopen(fd, "w")) == NULL) {
429 		close(fd);
430 		return (0);
431 	}
432 
433 	buf = sbuf_new_auto();
434 	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
435 		int             quote = 1;
436 
437 		sbuf_clear(buf);
438 		switch (i) {
439 		case _UC_DEFAULTPWD:
440 			sbuf_cat(buf, boolean_str(cnf->default_password));
441 			break;
442 		case _UC_REUSEUID:
443 			sbuf_cat(buf, boolean_str(cnf->reuse_uids));
444 			break;
445 		case _UC_REUSEGID:
446 			sbuf_cat(buf, boolean_str(cnf->reuse_gids));
447 			break;
448 		case _UC_NISPASSWD:
449 			sbuf_cat(buf, cnf->nispasswd ?  cnf->nispasswd : "");
450 			quote = 0;
451 			break;
452 		case _UC_DOTDIR:
453 			sbuf_cat(buf, cnf->dotdir ?  cnf->dotdir :
454 			    boolean_str(0));
455 			break;
456 		case _UC_NEWMAIL:
457 			sbuf_cat(buf, cnf->newmail ?  cnf->newmail :
458 			    boolean_str(0));
459 			break;
460 		case _UC_LOGFILE:
461 			sbuf_cat(buf, cnf->logfile ?  cnf->logfile :
462 			    boolean_str(0));
463 			break;
464 		case _UC_HOMEROOT:
465 			sbuf_cat(buf, cnf->home);
466 			break;
467 		case _UC_HOMEMODE:
468 			sbuf_printf(buf, "%04o", cnf->homemode);
469 			quote = 0;
470 			break;
471 		case _UC_SHELLPATH:
472 			sbuf_cat(buf, cnf->shelldir);
473 			break;
474 		case _UC_SHELLS:
475 			for (j = 0; j < _UC_MAXSHELLS &&
476 			    system_shells[j] != NULL; j++)
477 				sbuf_printf(buf, "%s\"%s\"", j ?
478 				    "," : "", system_shells[j]);
479 			quote = 0;
480 			break;
481 		case _UC_DEFAULTSHELL:
482 			sbuf_cat(buf, cnf->shell_default ?
483 			    cnf->shell_default : bourne_shell);
484 			break;
485 		case _UC_DEFAULTGROUP:
486 			sbuf_cat(buf, cnf->default_group ?
487 			    cnf->default_group : "");
488 			break;
489 		case _UC_EXTRAGROUPS:
490 			for (j = 0; cnf->groups != NULL &&
491 			    j < (int)cnf->groups->sl_cur; j++)
492 				sbuf_printf(buf, "%s\"%s\"", j ?
493 				    "," : "", cnf->groups->sl_str[j]);
494 			quote = 0;
495 			break;
496 		case _UC_DEFAULTCLASS:
497 			sbuf_cat(buf, cnf->default_class ?
498 			    cnf->default_class : "");
499 			break;
500 		case _UC_MINUID:
501 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid);
502 			quote = 0;
503 			break;
504 		case _UC_MAXUID:
505 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid);
506 			quote = 0;
507 			break;
508 		case _UC_MINGID:
509 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid);
510 			quote = 0;
511 			break;
512 		case _UC_MAXGID:
513 			sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid);
514 			quote = 0;
515 			break;
516 		case _UC_EXPIRE:
517 			sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days);
518 			quote = 0;
519 			break;
520 		case _UC_PASSWORD:
521 			sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days);
522 			quote = 0;
523 			break;
524 		case _UC_NONE:
525 			break;
526 		}
527 		sbuf_finish(buf);
528 
529 		if (comments[i])
530 			fputs(comments[i], fp);
531 
532 		if (*kwds[i]) {
533 			if (quote)
534 				fprintf(fp, "%s = \"%s\"\n", kwds[i],
535 				    sbuf_data(buf));
536 			else
537 				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
538 #if debugging
539 			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
540 #endif
541 		}
542 	}
543 	sbuf_delete(buf);
544 	return (fclose(fp) != EOF);
545 }
546