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