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