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