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