xref: /dragonfly/usr.sbin/pw/pw_conf.c (revision d4ef6694)
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.16 2011/03/08 20:13:29 jkim Exp $
27  */
28 
29 #include <string.h>
30 #include <ctype.h>
31 #include <fcntl.h>
32 
33 #include "pw.h"
34 
35 #define debugging 0
36 
37 enum {
38 	_UC_NONE,
39 	_UC_DEFAULTPWD,
40 	_UC_REUSEUID,
41 	_UC_REUSEGID,
42 	_UC_NISPASSWD,
43 	_UC_DOTDIR,
44 	_UC_NEWMAIL,
45 	_UC_LOGFILE,
46 	_UC_HOMEROOT,
47 	_UC_HOMEMODE,
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 	_DEF_DIRMODE,		/* Home directory perms, modified by umask */
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# Mode for the new $HOME directory, will be modified by umask\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 	"homemode",
143 	"shellpath",
144 	"shells",
145 	"defaultshell",
146 	"defaultgroup",
147 	"extragroups",
148 	"defaultclass",
149 	"minuid",
150 	"maxuid",
151 	"mingid",
152 	"maxgid",
153 	"expire_days",
154 	"password_days",
155 	NULL
156 };
157 
158 static char    *
159 unquote(char const * str)
160 {
161 	if (str && (*str == '"' || *str == '\'')) {
162 		char           *p = strchr(str + 1, *str);
163 
164 		if (p != NULL)
165 			*p = '\0';
166 		return (char *) (*++str ? str : NULL);
167 	}
168 	return (char *) str;
169 }
170 
171 int
172 boolean_val(char const * str, int dflt)
173 {
174 	if ((str = unquote(str)) != NULL) {
175 		int             i;
176 
177 		for (i = 0; booltrue[i]; i++)
178 			if (strcmp(str, booltrue[i]) == 0)
179 				return 1;
180 		for (i = 0; boolfalse[i]; i++)
181 			if (strcmp(str, boolfalse[i]) == 0)
182 				return 0;
183 
184 		/*
185 		 * Special cases for defaultpassword
186 		 */
187 		if (strcmp(str, "random") == 0)
188 			return -1;
189 		if (strcmp(str, "none") == 0)
190 			return -2;
191 	}
192 	return dflt;
193 }
194 
195 char const     *
196 boolean_str(int val)
197 {
198 	if (val == -1)
199 		return "random";
200 	else if (val == -2)
201 		return "none";
202 	else
203 		return val ? booltrue[0] : boolfalse[0];
204 }
205 
206 char           *
207 newstr(char const * p)
208 {
209 	char           *q = NULL;
210 
211 	if ((p = unquote(p)) != NULL) {
212 		int             l = strlen(p) + 1;
213 
214 		if ((q = malloc(l)) != NULL)
215 			memcpy(q, p, l);
216 	}
217 	return q;
218 }
219 
220 #define LNBUFSZ 1024
221 
222 
223 struct userconf *
224 read_userconfig(char const * file)
225 {
226 	FILE           *fp;
227 
228 	extendarray(&config.groups, &config.numgroups, 200);
229 	memset(config.groups, 0, config.numgroups * sizeof(char *));
230 	if (file == NULL)
231 		file = _PATH_PW_CONF;
232 	if ((fp = fopen(file, "r")) != NULL) {
233 		int	    buflen = LNBUFSZ;
234 		char       *buf = malloc(buflen);
235 
236 	nextline:
237 		while (fgets(buf, buflen, fp) != NULL) {
238 			char           *p;
239 
240 			while ((p = strchr(buf, '\n')) == NULL) {
241 				int	  l;
242 				if (extendline(&buf, &buflen, buflen + LNBUFSZ) == -1) {
243 					int	ch;
244 					while ((ch = fgetc(fp)) != '\n' && ch != EOF);
245 					goto nextline;	/* Ignore it */
246 				}
247 				l = strlen(buf);
248 				if (fgets(buf + l, buflen - l, fp) == NULL)
249 					break;	/* Unterminated last line */
250 			}
251 
252 			if (p != NULL)
253 				*p = '\0';
254 
255 			if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
256 				static char const toks[] = " \t\r\n,=";
257 				char           *q = strtok(NULL, toks);
258 				int             i = 0;
259 				mode_t          *modeset;
260 
261 				while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
262 					++i;
263 #if debugging
264 				if (i == _UC_FIELDS)
265 					printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
266 				else
267 					printf("Got kwd[%s]=%s\n", p, q);
268 #endif
269 				switch (i) {
270 				case _UC_DEFAULTPWD:
271 					config.default_password = boolean_val(q, 1);
272 					break;
273 				case _UC_REUSEUID:
274 					config.reuse_uids = boolean_val(q, 0);
275 					break;
276 				case _UC_REUSEGID:
277 					config.reuse_gids = boolean_val(q, 0);
278 					break;
279 				case _UC_NISPASSWD:
280 					config.nispasswd = (q == NULL || !boolean_val(q, 1))
281 						? NULL : newstr(q);
282 					break;
283 				case _UC_DOTDIR:
284 					config.dotdir = (q == NULL || !boolean_val(q, 1))
285 						? NULL : newstr(q);
286 					break;
287 				case _UC_NEWMAIL:
288 					config.newmail = (q == NULL || !boolean_val(q, 1))
289 						? NULL : newstr(q);
290 					break;
291 				case _UC_LOGFILE:
292 					config.logfile = (q == NULL || !boolean_val(q, 1))
293 						? NULL : newstr(q);
294 					break;
295 				case _UC_HOMEROOT:
296 					config.home = (q == NULL || !boolean_val(q, 1))
297 						? "/home" : newstr(q);
298 					break;
299 				case _UC_HOMEMODE:
300 					modeset = setmode(q);
301 					config.homemode = (q == NULL || !boolean_val(q, 1))
302 						? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
303 					free(modeset);
304 					break;
305 				case _UC_SHELLPATH:
306 					config.shelldir = (q == NULL || !boolean_val(q, 1))
307 						? "/bin" : newstr(q);
308 					break;
309 				case _UC_SHELLS:
310 					for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
311 						system_shells[i] = newstr(q);
312 					if (i > 0)
313 						while (i < _UC_MAXSHELLS)
314 							system_shells[i++] = NULL;
315 					break;
316 				case _UC_DEFAULTSHELL:
317 					config.shell_default = (q == NULL || !boolean_val(q, 1))
318 						? (char *) bourne_shell : newstr(q);
319 					break;
320 				case _UC_DEFAULTGROUP:
321 					q = unquote(q);
322 					config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
323 						? NULL : newstr(q);
324 					break;
325 				case _UC_EXTRAGROUPS:
326 					for (i = 0; q != NULL; q = strtok(NULL, toks)) {
327 						if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
328 							config.groups[i++] = newstr(q);
329 					}
330 					if (i > 0)
331 						while (i < config.numgroups)
332 							config.groups[i++] = NULL;
333 					break;
334 				case _UC_DEFAULTCLASS:
335 					config.default_class = (q == NULL || !boolean_val(q, 1))
336 						? NULL : newstr(q);
337 					break;
338 				case _UC_MINUID:
339 					if ((q = unquote(q)) != NULL && isdigit(*q))
340 						config.min_uid = (uid_t) atol(q);
341 					break;
342 				case _UC_MAXUID:
343 					if ((q = unquote(q)) != NULL && isdigit(*q))
344 						config.max_uid = (uid_t) atol(q);
345 					break;
346 				case _UC_MINGID:
347 					if ((q = unquote(q)) != NULL && isdigit(*q))
348 						config.min_gid = (gid_t) atol(q);
349 					break;
350 				case _UC_MAXGID:
351 					if ((q = unquote(q)) != NULL && isdigit(*q))
352 						config.max_gid = (gid_t) atol(q);
353 					break;
354 				case _UC_EXPIRE:
355 					if ((q = unquote(q)) != NULL && isdigit(*q))
356 						config.expire_days = atoi(q);
357 					break;
358 				case _UC_PASSWORD:
359 					if ((q = unquote(q)) != NULL && isdigit(*q))
360 						config.password_days = atoi(q);
361 					break;
362 				case _UC_FIELDS:
363 				case _UC_NONE:
364 					break;
365 				}
366 			}
367 		}
368 		free(buf);
369 		fclose(fp);
370 	}
371 	return &config;
372 }
373 
374 
375 int
376 write_userconfig(char const * file)
377 {
378 	int             fd;
379 
380 	if (file == NULL)
381 		file = _PATH_PW_CONF;
382 
383 	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
384 		FILE           *fp;
385 
386 		if ((fp = fdopen(fd, "w")) == NULL)
387 			close(fd);
388 		else {
389 			int             i, j, k;
390 			int		len = LNBUFSZ;
391 			char           *buf = malloc(len);
392 
393 			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
394 				int             quote = 1;
395 				char const     *val = buf;
396 
397 				*buf = '\0';
398 				switch (i) {
399 				case _UC_DEFAULTPWD:
400 					val = boolean_str(config.default_password);
401 					break;
402 				case _UC_REUSEUID:
403 					val = boolean_str(config.reuse_uids);
404 					break;
405 				case _UC_REUSEGID:
406 					val = boolean_str(config.reuse_gids);
407 					break;
408 				case _UC_NISPASSWD:
409 					val = config.nispasswd ? config.nispasswd : "";
410 					quote = 0;
411 					break;
412 				case _UC_DOTDIR:
413 					val = config.dotdir ? config.dotdir : boolean_str(0);
414 					break;
415 				case _UC_NEWMAIL:
416 					val = config.newmail ? config.newmail : boolean_str(0);
417 					break;
418 				case _UC_LOGFILE:
419 					val = config.logfile ? config.logfile : boolean_str(0);
420 					break;
421 				case _UC_HOMEROOT:
422 					val = config.home;
423 					break;
424 				case _UC_HOMEMODE:
425 					sprintf(buf, "%04o", config.homemode);
426 					quote = 0;
427 					break;
428 				case _UC_SHELLPATH:
429 					val = config.shelldir;
430 					break;
431 				case _UC_SHELLS:
432 					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) {
433 						char	lbuf[64];
434 						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]);
435 						if (l < 0)
436 							l = 0;
437 						if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) {
438 							strcpy(buf + k, lbuf);
439 							k += l;
440 						}
441 					}
442 					quote = 0;
443 					break;
444 				case _UC_DEFAULTSHELL:
445 					val = config.shell_default ? config.shell_default : bourne_shell;
446 					break;
447 				case _UC_DEFAULTGROUP:
448 					val = config.default_group ? config.default_group : "";
449 					break;
450 				case _UC_EXTRAGROUPS:
451 					extendarray(&config.groups, &config.numgroups, 200);
452 					for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) {
453 						char	lbuf[64];
454 						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]);
455 						if (l < 0)
456 							l = 0;
457 						if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) {
458 							strcpy(buf + k, lbuf);
459 							k +=  l;
460 						}
461 					}
462 					quote = 0;
463 					break;
464 				case _UC_DEFAULTCLASS:
465 					val = config.default_class ? config.default_class : "";
466 					break;
467 				case _UC_MINUID:
468 					sprintf(buf, "%lu", (unsigned long) config.min_uid);
469 					quote = 0;
470 					break;
471 				case _UC_MAXUID:
472 					sprintf(buf, "%lu", (unsigned long) config.max_uid);
473 					quote = 0;
474 					break;
475 				case _UC_MINGID:
476 					sprintf(buf, "%lu", (unsigned long) config.min_gid);
477 					quote = 0;
478 					break;
479 				case _UC_MAXGID:
480 					sprintf(buf, "%lu", (unsigned long) config.max_gid);
481 					quote = 0;
482 					break;
483 				case _UC_EXPIRE:
484 					sprintf(buf, "%d", config.expire_days);
485 					quote = 0;
486 					break;
487 				case _UC_PASSWORD:
488 					sprintf(buf, "%d", config.password_days);
489 					quote = 0;
490 					break;
491 				case _UC_NONE:
492 					break;
493 				}
494 
495 				if (comments[i])
496 					fputs(comments[i], fp);
497 
498 				if (*kwds[i]) {
499 					if (quote)
500 						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
501 					else
502 						fprintf(fp, "%s = %s\n", kwds[i], val);
503 #if debugging
504 					printf("WROTE: %s = %s\n", kwds[i], val);
505 #endif
506 				}
507 			}
508 			free(buf);
509 			return fclose(fp) != EOF;
510 		}
511 	}
512 	return 0;
513 }
514