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