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