xref: /freebsd/usr.sbin/pw/pw_conf.c (revision a03411e84728e9b267056fd31c7d1d9d1dc1b01e)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 #include <err.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #include "pw.h"
35 
36 #define debugging 0
37 
38 enum {
39 	_UC_NONE,
40 	_UC_DEFAULTPWD,
41 	_UC_REUSEUID,
42 	_UC_REUSEGID,
43 	_UC_NISPASSWD,
44 	_UC_DOTDIR,
45 	_UC_NEWMAIL,
46 	_UC_LOGFILE,
47 	_UC_HOMEROOT,
48 	_UC_HOMEMODE,
49 	_UC_SHELLPATH,
50 	_UC_SHELLS,
51 	_UC_DEFAULTSHELL,
52 	_UC_DEFAULTGROUP,
53 	_UC_EXTRAGROUPS,
54 	_UC_DEFAULTCLASS,
55 	_UC_MINUID,
56 	_UC_MAXUID,
57 	_UC_MINGID,
58 	_UC_MAXGID,
59 	_UC_EXPIRE,
60 	_UC_PASSWORD,
61 	_UC_FIELDS
62 };
63 
64 static char     bourne_shell[] = "sh";
65 
66 static char    *system_shells[_UC_MAXSHELLS] =
67 {
68 	bourne_shell,
69 	"csh",
70 	"tcsh"
71 };
72 
73 static char const *booltrue[] =
74 {
75 	"yes", "true", "1", "on", NULL
76 };
77 static char const *boolfalse[] =
78 {
79 	"no", "false", "0", "off", NULL
80 };
81 
82 static struct userconf config =
83 {
84 	0,			/* Default password for new users? (nologin) */
85 	0,			/* Reuse uids? */
86 	0,			/* Reuse gids? */
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 */
103 };
104 
105 static char const *comments[_UC_FIELDS] =
106 {
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",
124 	NULL,
125 	"\n# Range of valid default group ids\n",
126 	NULL,
127 	"\n# Days after which account expires (0=disabled)\n",
128 	"\n# Days after which password expires (0=disabled)\n"
129 };
130 
131 static char const *kwds[] =
132 {
133 	"",
134 	"defaultpasswd",
135 	"reuseuids",
136 	"reusegids",
137 	"nispasswd",
138 	"skeleton",
139 	"newmail",
140 	"logfile",
141 	"home",
142 	"homemode",
143 	"shellpath",
144 	"shells",
145 	"defaultshell",
146 	"defaultgroup",
147 	"extragroups",
148 	"defaultclass",
149 	"minuid",
150 	"maxuid",
151 	"mingid",
152 	"maxgid",
153 	"expire_days",
154 	"password_days",
155 	NULL
156 };
157 
158 static char    *
159 unquote(char const * str)
160 {
161 	if (str && (*str == '"' || *str == '\'')) {
162 		char           *p = strchr(str + 1, *str);
163 
164 		if (p != NULL)
165 			*p = '\0';
166 		return (char *) (*++str ? str : NULL);
167 	}
168 	return (char *) str;
169 }
170 
171 int
172 boolean_val(char const * str, int dflt)
173 {
174 	if ((str = unquote(str)) != NULL) {
175 		int             i;
176 
177 		for (i = 0; booltrue[i]; i++)
178 			if (strcmp(str, booltrue[i]) == 0)
179 				return 1;
180 		for (i = 0; boolfalse[i]; i++)
181 			if (strcmp(str, boolfalse[i]) == 0)
182 				return 0;
183 	}
184 	return dflt;
185 }
186 
187 int
188 passwd_val(char const * str, int dflt)
189 {
190 	if ((str = unquote(str)) != NULL) {
191 		int             i;
192 
193 		for (i = 0; booltrue[i]; i++)
194 			if (strcmp(str, booltrue[i]) == 0)
195 				return P_YES;
196 		for (i = 0; boolfalse[i]; i++)
197 			if (strcmp(str, boolfalse[i]) == 0)
198 				return P_NO;
199 
200 		/*
201 		 * Special cases for defaultpassword
202 		 */
203 		if (strcmp(str, "random") == 0)
204 			return P_RANDOM;
205 		if (strcmp(str, "none") == 0)
206 			return P_NONE;
207 
208 		errx(1, "Invalid value for default password");
209 	}
210 	return dflt;
211 }
212 
213 char const     *
214 boolean_str(int val)
215 {
216 	if (val == P_NO)
217 		return (boolfalse[0]);
218 	else if (val == P_RANDOM)
219 		return ("random");
220 	else if (val == P_NONE)
221 		return ("none");
222 	else
223 		return (booltrue[0]);
224 }
225 
226 char           *
227 newstr(char const * p)
228 {
229 	char	*q;
230 
231 	if ((p = unquote(p)) == NULL)
232 		return (NULL);
233 
234 	if ((q = strdup(p)) == NULL)
235 		err(1, "strdup()");
236 
237 	return (q);
238 }
239 
240 struct userconf *
241 read_userconfig(char const * file)
242 {
243 	FILE	*fp;
244 	char	*buf, *p;
245 	const char *errstr;
246 	size_t	linecap;
247 	ssize_t	linelen;
248 
249 	buf = NULL;
250 	linecap = 0;
251 
252 	if ((fp = fopen(file, "r")) == NULL)
253 		return (&config);
254 
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);
259 			int             i = 0;
260 			mode_t          *modeset;
261 
262 			while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
263 				++i;
264 #if debugging
265 			if (i == _UC_FIELDS)
266 				printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
267 			else
268 				printf("Got kwd[%s]=%s\n", p, q);
269 #endif
270 			switch (i) {
271 			case _UC_DEFAULTPWD:
272 				config.default_password = passwd_val(q, 1);
273 				break;
274 			case _UC_REUSEUID:
275 				config.reuse_uids = boolean_val(q, 0);
276 				break;
277 			case _UC_REUSEGID:
278 				config.reuse_gids = boolean_val(q, 0);
279 				break;
280 			case _UC_NISPASSWD:
281 				config.nispasswd = (q == NULL || !boolean_val(q, 1))
282 					? NULL : newstr(q);
283 				break;
284 			case _UC_DOTDIR:
285 				config.dotdir = (q == NULL || !boolean_val(q, 1))
286 					? NULL : newstr(q);
287 				break;
288 				case _UC_NEWMAIL:
289 				config.newmail = (q == NULL || !boolean_val(q, 1))
290 					? NULL : newstr(q);
291 				break;
292 			case _UC_LOGFILE:
293 				config.logfile = (q == NULL || !boolean_val(q, 1))
294 					? NULL : newstr(q);
295 				break;
296 			case _UC_HOMEROOT:
297 				config.home = (q == NULL || !boolean_val(q, 1))
298 					? "/home" : newstr(q);
299 				break;
300 			case _UC_HOMEMODE:
301 				modeset = setmode(q);
302 				config.homemode = (q == NULL || !boolean_val(q, 1))
303 					? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
304 				free(modeset);
305 				break;
306 			case _UC_SHELLPATH:
307 				config.shelldir = (q == NULL || !boolean_val(q, 1))
308 					? "/bin" : newstr(q);
309 				break;
310 			case _UC_SHELLS:
311 				for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
312 					system_shells[i] = newstr(q);
313 				if (i > 0)
314 					while (i < _UC_MAXSHELLS)
315 						system_shells[i++] = NULL;
316 				break;
317 			case _UC_DEFAULTSHELL:
318 				config.shell_default = (q == NULL || !boolean_val(q, 1))
319 					? (char *) bourne_shell : newstr(q);
320 				break;
321 			case _UC_DEFAULTGROUP:
322 				q = unquote(q);
323 				config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
324 					? NULL : newstr(q);
325 				break;
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));
331 				}
332 				break;
333 			case _UC_DEFAULTCLASS:
334 				config.default_class = (q == NULL || !boolean_val(q, 1))
335 					? NULL : newstr(q);
336 				break;
337 			case _UC_MINUID:
338 				if ((q = unquote(q)) != NULL) {
339 					config.min_uid = strtounum(q, 0,
340 					    UID_MAX, &errstr);
341 					if (errstr)
342 						warnx("Invalid min_uid: '%s';"
343 						    " ignoring", q);
344 				}
345 				break;
346 			case _UC_MAXUID:
347 				if ((q = unquote(q)) != NULL) {
348 					config.max_uid = strtounum(q, 0,
349 					    UID_MAX, &errstr);
350 					if (errstr)
351 						warnx("Invalid max_uid: '%s';"
352 						    " ignoring", q);
353 				}
354 				break;
355 			case _UC_MINGID:
356 				if ((q = unquote(q)) != NULL) {
357 					config.min_gid = strtounum(q, 0,
358 					    GID_MAX, &errstr);
359 					if (errstr)
360 						warnx("Invalid min_gid: '%s';"
361 						    " ignoring", q);
362 				}
363 				break;
364 			case _UC_MAXGID:
365 				if ((q = unquote(q)) != NULL) {
366 					config.max_gid = strtounum(q, 0,
367 					    GID_MAX, &errstr);
368 					if (errstr)
369 						warnx("Invalid max_gid: '%s';"
370 						    " ignoring", q);
371 				}
372 				break;
373 			case _UC_EXPIRE:
374 				if ((q = unquote(q)) != NULL) {
375 					config.expire_days = strtonum(q, 0,
376 					    INT_MAX, &errstr);
377 					if (errstr)
378 						warnx("Invalid expire days:"
379 						    " '%s'; ignoring", q);
380 				}
381 				break;
382 			case _UC_PASSWORD:
383 				if ((q = unquote(q)) != NULL) {
384 					config.password_days = strtonum(q, 0,
385 					    INT_MAX, &errstr);
386 					if (errstr)
387 						warnx("Invalid password days:"
388 						    " '%s'; ignoring", q);
389 				}
390 				break;
391 			case _UC_FIELDS:
392 			case _UC_NONE:
393 				break;
394 			}
395 		}
396 	}
397 	free(buf);
398 	fclose(fp);
399 
400 	return (&config);
401 }
402 
403 
404 int
405 write_userconfig(struct userconf *cnf, const char *file)
406 {
407 	int             fd;
408 	int             i, j;
409 	FILE           *buffp;
410 	FILE           *fp;
411 	char		cfgfile[MAXPATHLEN];
412 	char           *buf;
413 	size_t          sz;
414 
415 	if (file == NULL) {
416 		snprintf(cfgfile, sizeof(cfgfile), "%s/" _PW_CONF,
417 		    conf.etcpath);
418 		file = cfgfile;
419 	}
420 
421 	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
422 		return (0);
423 
424 	if ((fp = fdopen(fd, "w")) == NULL) {
425 		close(fd);
426 		return (0);
427 	}
428 
429 	sz = 0;
430 	buf = NULL;
431 	buffp = open_memstream(&buf, &sz);
432 	if (buffp == NULL)
433 		err(EXIT_FAILURE, "open_memstream()");
434 
435 	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
436 		int             quote = 1;
437 
438 		if (buf != NULL)
439 			memset(buf, 0, sz);
440 		rewind(buffp);
441 		switch (i) {
442 		case _UC_DEFAULTPWD:
443 			fputs(boolean_str(cnf->default_password), buffp);
444 			break;
445 		case _UC_REUSEUID:
446 			fputs(boolean_str(cnf->reuse_uids), buffp);
447 			break;
448 		case _UC_REUSEGID:
449 			fputs(boolean_str(cnf->reuse_gids), buffp);
450 			break;
451 		case _UC_NISPASSWD:
452 			fputs(cnf->nispasswd ?  cnf->nispasswd : "", buffp);
453 			quote = 0;
454 			break;
455 		case _UC_DOTDIR:
456 			fputs(cnf->dotdir ?  cnf->dotdir : boolean_str(0),
457 			    buffp);
458 			break;
459 		case _UC_NEWMAIL:
460 			fputs(cnf->newmail ?  cnf->newmail : boolean_str(0),
461 			    buffp);
462 			break;
463 		case _UC_LOGFILE:
464 			fputs(cnf->logfile ?  cnf->logfile : boolean_str(0),
465 			    buffp);
466 			break;
467 		case _UC_HOMEROOT:
468 			fputs(cnf->home, buffp);
469 			break;
470 		case _UC_HOMEMODE:
471 			fprintf(buffp, "%04o", cnf->homemode);
472 			quote = 0;
473 			break;
474 		case _UC_SHELLPATH:
475 			fputs(cnf->shelldir, buffp);
476 			break;
477 		case _UC_SHELLS:
478 			for (j = 0; j < _UC_MAXSHELLS &&
479 			    system_shells[j] != NULL; j++)
480 				fprintf(buffp, "%s\"%s\"", j ?
481 				    "," : "", system_shells[j]);
482 			quote = 0;
483 			break;
484 		case _UC_DEFAULTSHELL:
485 			fputs(cnf->shell_default ?  cnf->shell_default :
486 			    bourne_shell, buffp);
487 			break;
488 		case _UC_DEFAULTGROUP:
489 			fputs(cnf->default_group ?  cnf->default_group : "",
490 			    buffp);
491 			break;
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]);
497 			quote = 0;
498 			break;
499 		case _UC_DEFAULTCLASS:
500 			fputs(cnf->default_class ?  cnf->default_class : "",
501 			    buffp);
502 			break;
503 		case _UC_MINUID:
504 			fprintf(buffp, "%ju", (uintmax_t)cnf->min_uid);
505 			quote = 0;
506 			break;
507 		case _UC_MAXUID:
508 			fprintf(buffp, "%ju", (uintmax_t)cnf->max_uid);
509 			quote = 0;
510 			break;
511 		case _UC_MINGID:
512 			fprintf(buffp, "%ju", (uintmax_t)cnf->min_gid);
513 			quote = 0;
514 			break;
515 		case _UC_MAXGID:
516 			fprintf(buffp, "%ju", (uintmax_t)cnf->max_gid);
517 			quote = 0;
518 			break;
519 		case _UC_EXPIRE:
520 			fprintf(buffp, "%jd", (intmax_t)cnf->expire_days);
521 			quote = 0;
522 			break;
523 		case _UC_PASSWORD:
524 			fprintf(buffp, "%jd", (intmax_t)cnf->password_days);
525 			quote = 0;
526 			break;
527 		case _UC_NONE:
528 			break;
529 		}
530 		fflush(buffp);
531 
532 		if (comments[i])
533 			fputs(comments[i], fp);
534 
535 		if (*kwds[i]) {
536 			if (quote)
537 				fprintf(fp, "%s = \"%s\"\n", kwds[i], buf);
538 			else
539 				fprintf(fp, "%s = %s\n", kwds[i], buf);
540 #if debugging
541 			printf("WROTE: %s = %s\n", kwds[i], buf);
542 #endif
543 		}
544 	}
545 	fclose(buffp);
546 	free(buf);
547 	return (fclose(fp) != EOF);
548 }
549