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