xref: /freebsd/usr.sbin/pw/pw_conf.c (revision ca2e4ecd7395ba655ab4bebe7262a06e634216ce)
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 };
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 		/*
190 		 * Special cases for defaultpassword
191 		 */
192 		if (strcmp(str, "random") == 0)
193 			return -1;
194 		if (strcmp(str, "none") == 0)
195 			return -2;
196 	}
197 	return dflt;
198 }
199 
200 char const     *
201 boolean_str(int val)
202 {
203 	if (val == -1)
204 		return "random";
205 	else if (val == -2)
206 		return "none";
207 	else
208 		return val ? booltrue[0] : boolfalse[0];
209 }
210 
211 char           *
212 newstr(char const * p)
213 {
214 	char	*q;
215 
216 	if ((p = unquote(p)) == NULL)
217 		return (NULL);
218 
219 	if ((q = strdup(p)) == NULL)
220 		err(1, "strdup()");
221 
222 	return (q);
223 }
224 
225 struct userconf *
226 read_userconfig(char const * file)
227 {
228 	FILE	*fp;
229 	char	*buf, *p;
230 	size_t	linecap;
231 	ssize_t	linelen;
232 
233 	buf = NULL;
234 	linecap = 0;
235 
236 	config.groups = sl_init();
237 	if (config.groups == NULL)
238 		err(1, "sl_init()");
239 	if (file == NULL)
240 		file = _PATH_PW_CONF;
241 
242 	if ((fp = fopen(file, "r")) == NULL)
243 		return (&config);
244 
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 					sl_add(config.groups, newstr(q));
319 				break;
320 			case _UC_DEFAULTCLASS:
321 				config.default_class = (q == NULL || !boolean_val(q, 1))
322 					? NULL : newstr(q);
323 				break;
324 			case _UC_MINUID:
325 				if ((q = unquote(q)) != NULL && isdigit(*q))
326 					config.min_uid = (uid_t) atol(q);
327 				break;
328 			case _UC_MAXUID:
329 				if ((q = unquote(q)) != NULL && isdigit(*q))
330 					config.max_uid = (uid_t) atol(q);
331 				break;
332 			case _UC_MINGID:
333 				if ((q = unquote(q)) != NULL && isdigit(*q))
334 					config.min_gid = (gid_t) atol(q);
335 				break;
336 			case _UC_MAXGID:
337 				if ((q = unquote(q)) != NULL && isdigit(*q))
338 					config.max_gid = (gid_t) atol(q);
339 				break;
340 			case _UC_EXPIRE:
341 				if ((q = unquote(q)) != NULL && isdigit(*q))
342 					config.expire_days = atoi(q);
343 				break;
344 			case _UC_PASSWORD:
345 				if ((q = unquote(q)) != NULL && isdigit(*q))
346 					config.password_days = atoi(q);
347 				break;
348 			case _UC_FIELDS:
349 			case _UC_NONE:
350 				break;
351 			}
352 		}
353 	}
354 	free(buf);
355 	fclose(fp);
356 
357 	return (&config);
358 }
359 
360 
361 int
362 write_userconfig(char const * file)
363 {
364 	int             fd;
365 	int             i, j;
366 	struct sbuf	*buf;
367 	FILE           *fp;
368 
369 	if (file == NULL)
370 		file = _PATH_PW_CONF;
371 
372 	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
373 		return (0);
374 
375 	if ((fp = fdopen(fd, "w")) == NULL) {
376 		close(fd);
377 		return (0);
378 	}
379 
380 	buf = sbuf_new_auto();
381 	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
382 		int             quote = 1;
383 
384 		sbuf_clear(buf);
385 		switch (i) {
386 		case _UC_DEFAULTPWD:
387 			sbuf_cat(buf, boolean_str(config.default_password));
388 			break;
389 		case _UC_REUSEUID:
390 			sbuf_cat(buf, boolean_str(config.reuse_uids));
391 			break;
392 		case _UC_REUSEGID:
393 			sbuf_cat(buf, boolean_str(config.reuse_gids));
394 			break;
395 		case _UC_NISPASSWD:
396 			sbuf_cat(buf, config.nispasswd ?  config.nispasswd :
397 			    "");
398 			quote = 0;
399 			break;
400 		case _UC_DOTDIR:
401 			sbuf_cat(buf, config.dotdir ?  config.dotdir :
402 			    boolean_str(0));
403 			break;
404 		case _UC_NEWMAIL:
405 			sbuf_cat(buf, config.newmail ?  config.newmail :
406 			    boolean_str(0));
407 			break;
408 		case _UC_LOGFILE:
409 			sbuf_cat(buf, config.logfile ?  config.logfile :
410 			    boolean_str(0));
411 			break;
412 		case _UC_HOMEROOT:
413 			sbuf_cat(buf, config.home);
414 			break;
415 		case _UC_HOMEMODE:
416 			sbuf_printf(buf, "%04o", config.homemode);
417 			quote = 0;
418 			break;
419 		case _UC_SHELLPATH:
420 			sbuf_cat(buf, config.shelldir);
421 			break;
422 		case _UC_SHELLS:
423 			for (j = 0; j < _UC_MAXSHELLS &&
424 			    system_shells[j] != NULL; j++)
425 				sbuf_printf(buf, "%s\"%s\"", j ?
426 				    "," : "", system_shells[j]);
427 			quote = 0;
428 			break;
429 		case _UC_DEFAULTSHELL:
430 			sbuf_cat(buf, config.shell_default ?
431 			    config.shell_default : bourne_shell);
432 			break;
433 		case _UC_DEFAULTGROUP:
434 			sbuf_cat(buf, config.default_group ?
435 			    config.default_group : "");
436 			break;
437 		case _UC_EXTRAGROUPS:
438 			for (j = 0; config.groups != NULL &&
439 			    j < (int)config.groups->sl_cur; j++)
440 				sbuf_printf(buf, "%s\"%s\"", j ?
441 				    "," : "", config.groups->sl_str[j]);
442 			quote = 0;
443 			break;
444 		case _UC_DEFAULTCLASS:
445 			sbuf_cat(buf, config.default_class ?
446 			    config.default_class : "");
447 			break;
448 		case _UC_MINUID:
449 			sbuf_printf(buf, "%u", config.min_uid);
450 			quote = 0;
451 			break;
452 		case _UC_MAXUID:
453 			sbuf_printf(buf, "%u", config.max_uid);
454 			quote = 0;
455 			break;
456 		case _UC_MINGID:
457 			sbuf_printf(buf, "%u", config.min_gid);
458 			quote = 0;
459 			break;
460 		case _UC_MAXGID:
461 			sbuf_printf(buf, "%u", config.max_gid);
462 			quote = 0;
463 			break;
464 		case _UC_EXPIRE:
465 			sbuf_printf(buf, "%d", config.expire_days);
466 			quote = 0;
467 			break;
468 		case _UC_PASSWORD:
469 			sbuf_printf(buf, "%d", config.password_days);
470 			quote = 0;
471 			break;
472 		case _UC_NONE:
473 			break;
474 		}
475 		sbuf_finish(buf);
476 
477 		if (comments[i])
478 			fputs(comments[i], fp);
479 
480 		if (*kwds[i]) {
481 			if (quote)
482 				fprintf(fp, "%s = \"%s\"\n", kwds[i],
483 				    sbuf_data(buf));
484 			else
485 				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
486 #if debugging
487 			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
488 #endif
489 		}
490 	}
491 	sbuf_delete(buf);
492 	return (fclose(fp) != EOF);
493 }
494