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