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