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