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