xref: /freebsd/usr.sbin/pw/pw_conf.c (revision b00fe64f4acfe315181f65999af16e9a7bdc600b)
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 #include <string.h>
35 #include <ctype.h>
36 #include <fcntl.h>
37 #include <err.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 	0			/* size of default_group array */
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 	size_t	linecap;
232 	ssize_t	linelen;
233 
234 	buf = NULL;
235 	linecap = 0;
236 
237 	config.numgroups = 200;
238 	config.groups = calloc(config.numgroups, sizeof(char *));
239 	if (config.groups == NULL)
240 		err(1, "calloc()");
241 	if (file == NULL)
242 		file = _PATH_PW_CONF;
243 
244 	if ((fp = fopen(file, "r")) != NULL) {
245 		while ((linelen = getline(&buf, &linecap, fp)) > 0) {
246 			if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
247 				static char const toks[] = " \t\r\n,=";
248 				char           *q = strtok(NULL, toks);
249 				int             i = 0;
250 				mode_t          *modeset;
251 
252 				while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
253 					++i;
254 #if debugging
255 				if (i == _UC_FIELDS)
256 					printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
257 				else
258 					printf("Got kwd[%s]=%s\n", p, q);
259 #endif
260 				switch (i) {
261 				case _UC_DEFAULTPWD:
262 					config.default_password = boolean_val(q, 1);
263 					break;
264 				case _UC_REUSEUID:
265 					config.reuse_uids = boolean_val(q, 0);
266 					break;
267 				case _UC_REUSEGID:
268 					config.reuse_gids = boolean_val(q, 0);
269 					break;
270 				case _UC_NISPASSWD:
271 					config.nispasswd = (q == NULL || !boolean_val(q, 1))
272 						? NULL : newstr(q);
273 					break;
274 				case _UC_DOTDIR:
275 					config.dotdir = (q == NULL || !boolean_val(q, 1))
276 						? NULL : newstr(q);
277 					break;
278 				case _UC_NEWMAIL:
279 					config.newmail = (q == NULL || !boolean_val(q, 1))
280 						? NULL : newstr(q);
281 					break;
282 				case _UC_LOGFILE:
283 					config.logfile = (q == NULL || !boolean_val(q, 1))
284 						? NULL : newstr(q);
285 					break;
286 				case _UC_HOMEROOT:
287 					config.home = (q == NULL || !boolean_val(q, 1))
288 						? "/home" : newstr(q);
289 					break;
290 				case _UC_HOMEMODE:
291 					modeset = setmode(q);
292 					config.homemode = (q == NULL || !boolean_val(q, 1))
293 						? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
294 					free(modeset);
295 					break;
296 				case _UC_SHELLPATH:
297 					config.shelldir = (q == NULL || !boolean_val(q, 1))
298 						? "/bin" : newstr(q);
299 					break;
300 				case _UC_SHELLS:
301 					for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
302 						system_shells[i] = newstr(q);
303 					if (i > 0)
304 						while (i < _UC_MAXSHELLS)
305 							system_shells[i++] = NULL;
306 					break;
307 				case _UC_DEFAULTSHELL:
308 					config.shell_default = (q == NULL || !boolean_val(q, 1))
309 						? (char *) bourne_shell : newstr(q);
310 					break;
311 				case _UC_DEFAULTGROUP:
312 					q = unquote(q);
313 					config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
314 						? NULL : newstr(q);
315 					break;
316 				case _UC_EXTRAGROUPS:
317 					for (i = 0; q != NULL; q = strtok(NULL, toks)) {
318 						if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
319 							config.groups[i++] = newstr(q);
320 					}
321 					if (i > 0)
322 						while (i < config.numgroups)
323 							config.groups[i++] = NULL;
324 					break;
325 				case _UC_DEFAULTCLASS:
326 					config.default_class = (q == NULL || !boolean_val(q, 1))
327 						? NULL : newstr(q);
328 					break;
329 				case _UC_MINUID:
330 					if ((q = unquote(q)) != NULL && isdigit(*q))
331 						config.min_uid = (uid_t) atol(q);
332 					break;
333 				case _UC_MAXUID:
334 					if ((q = unquote(q)) != NULL && isdigit(*q))
335 						config.max_uid = (uid_t) atol(q);
336 					break;
337 				case _UC_MINGID:
338 					if ((q = unquote(q)) != NULL && isdigit(*q))
339 						config.min_gid = (gid_t) atol(q);
340 					break;
341 				case _UC_MAXGID:
342 					if ((q = unquote(q)) != NULL && isdigit(*q))
343 						config.max_gid = (gid_t) atol(q);
344 					break;
345 				case _UC_EXPIRE:
346 					if ((q = unquote(q)) != NULL && isdigit(*q))
347 						config.expire_days = atoi(q);
348 					break;
349 				case _UC_PASSWORD:
350 					if ((q = unquote(q)) != NULL && isdigit(*q))
351 						config.password_days = atoi(q);
352 					break;
353 				case _UC_FIELDS:
354 				case _UC_NONE:
355 					break;
356 				}
357 			}
358 		}
359 		if (linecap > 0)
360 			free(buf);
361 		fclose(fp);
362 	}
363 	return &config;
364 }
365 
366 
367 int
368 write_userconfig(char const * file)
369 {
370 	int             fd;
371 	int             i, j;
372 	struct sbuf	*buf;
373 	FILE           *fp;
374 
375 	if (file == NULL)
376 		file = _PATH_PW_CONF;
377 
378 	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
379 		return (0);
380 
381 	if ((fp = fdopen(fd, "w")) == NULL) {
382 		close(fd);
383 		return (0);
384 	}
385 
386 	buf = sbuf_new_auto();
387 	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
388 		int             quote = 1;
389 
390 		sbuf_clear(buf);
391 		switch (i) {
392 		case _UC_DEFAULTPWD:
393 			sbuf_cat(buf, boolean_str(config.default_password));
394 			break;
395 		case _UC_REUSEUID:
396 			sbuf_cat(buf, boolean_str(config.reuse_uids));
397 			break;
398 		case _UC_REUSEGID:
399 			sbuf_cat(buf, boolean_str(config.reuse_gids));
400 			break;
401 		case _UC_NISPASSWD:
402 			sbuf_cat(buf, config.nispasswd ?  config.nispasswd :
403 			    "");
404 			quote = 0;
405 			break;
406 		case _UC_DOTDIR:
407 			sbuf_cat(buf, config.dotdir ?  config.dotdir :
408 			    boolean_str(0));
409 			break;
410 		case _UC_NEWMAIL:
411 			sbuf_cat(buf, config.newmail ?  config.newmail :
412 			    boolean_str(0));
413 			break;
414 		case _UC_LOGFILE:
415 			sbuf_cat(buf, config.logfile ?  config.logfile :
416 			    boolean_str(0));
417 			break;
418 		case _UC_HOMEROOT:
419 			sbuf_cat(buf, config.home);
420 			break;
421 		case _UC_HOMEMODE:
422 			sbuf_printf(buf, "%04o", config.homemode);
423 			quote = 0;
424 			break;
425 		case _UC_SHELLPATH:
426 			sbuf_cat(buf, config.shelldir);
427 			break;
428 		case _UC_SHELLS:
429 			for (j = 0; j < _UC_MAXSHELLS &&
430 			    system_shells[j] != NULL; j++)
431 				sbuf_printf(buf, "%s\"%s\"", j ?
432 				    "," : "", system_shells[j]);
433 			quote = 0;
434 			break;
435 		case _UC_DEFAULTSHELL:
436 			sbuf_cat(buf, config.shell_default ?
437 			    config.shell_default : bourne_shell);
438 			break;
439 		case _UC_DEFAULTGROUP:
440 			sbuf_cat(buf, config.default_group ?
441 			    config.default_group : "");
442 			break;
443 		case _UC_EXTRAGROUPS:
444 			for (j = 0; j < config.numgroups &&
445 			    config.groups[j] != NULL; j++)
446 				sbuf_printf(buf, "%s\"%s\"", j ?
447 				    "," : "", config.groups[j]);
448 			quote = 0;
449 			break;
450 		case _UC_DEFAULTCLASS:
451 			sbuf_cat(buf, config.default_class ?
452 			    config.default_class : "");
453 			break;
454 		case _UC_MINUID:
455 			sbuf_printf(buf, "%lu", (unsigned long) config.min_uid);
456 			quote = 0;
457 			break;
458 		case _UC_MAXUID:
459 			sbuf_printf(buf, "%lu", (unsigned long) config.max_uid);
460 			quote = 0;
461 			break;
462 		case _UC_MINGID:
463 			sbuf_printf(buf, "%lu", (unsigned long) config.min_gid);
464 			quote = 0;
465 			break;
466 		case _UC_MAXGID:
467 			sbuf_printf(buf, "%lu", (unsigned long) config.max_gid);
468 			quote = 0;
469 			break;
470 		case _UC_EXPIRE:
471 			sbuf_printf(buf, "%d", config.expire_days);
472 			quote = 0;
473 			break;
474 		case _UC_PASSWORD:
475 			sbuf_printf(buf, "%d", config.password_days);
476 			quote = 0;
477 			break;
478 		case _UC_NONE:
479 			break;
480 		}
481 		sbuf_finish(buf);
482 
483 		if (comments[i])
484 			fputs(comments[i], fp);
485 
486 		if (*kwds[i]) {
487 			if (quote)
488 				fprintf(fp, "%s = \"%s\"\n", kwds[i],
489 				    sbuf_data(buf));
490 			else
491 				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
492 #if debugging
493 			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
494 #endif
495 		}
496 	}
497 	sbuf_delete(buf);
498 	return (fclose(fp) != EOF);
499 }
500