xref: /freebsd/usr.sbin/pw/pw_conf.c (revision 0de89efe5c443f213c7ea28773ef2dc6cf3af2ed)
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.5 1997/02/22 16:12:27 peter 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_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 					q = unquote(q);
310 					config.default_group = (q == NULL || !boolean_val(q, 1) || getgrnam(q) == NULL)
311 						? NULL : newstr(q);
312 					break;
313 				case _UC_EXTRAGROUPS:
314 					for (i = 0; q != NULL; q = strtok(NULL, toks)) {
315 						if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
316 							config.groups[i++] = newstr(q);
317 					}
318 					if (i > 0)
319 						while (i < config.numgroups)
320 							config.groups[i++] = NULL;
321 					break;
322 				case _UC_DEFAULTCLASS:
323 					config.default_class = (q == NULL || !boolean_val(q, 1))
324 						? NULL : newstr(q);
325 					break;
326 				case _UC_MINUID:
327 					if ((q = unquote(q)) != NULL && isdigit(*q))
328 						config.min_uid = (uid_t) atol(q);
329 					break;
330 				case _UC_MAXUID:
331 					if ((q = unquote(q)) != NULL && isdigit(*q))
332 						config.max_uid = (uid_t) atol(q);
333 					break;
334 				case _UC_MINGID:
335 					if ((q = unquote(q)) != NULL && isdigit(*q))
336 						config.min_gid = (gid_t) atol(q);
337 					break;
338 				case _UC_MAXGID:
339 					if ((q = unquote(q)) != NULL && isdigit(*q))
340 						config.max_gid = (gid_t) atol(q);
341 					break;
342 				case _UC_EXPIRE:
343 					if ((q = unquote(q)) != NULL && isdigit(*q))
344 						config.expire_days = atoi(q);
345 					break;
346 				case _UC_PASSWORD:
347 					if ((q = unquote(q)) != NULL && isdigit(*q))
348 						config.password_days = atoi(q);
349 					break;
350 				case _UC_FIELDS:
351 				case _UC_NONE:
352 					break;
353 				}
354 			}
355 		}
356 		free(buf);
357 		fclose(fp);
358 	}
359 	return &config;
360 }
361 
362 
363 int
364 write_userconfig(char const * file)
365 {
366 	int             fd;
367 
368 	if (file == NULL)
369 		file = _PATH_PW_CONF;
370 
371 	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
372 		FILE           *fp;
373 
374 		if ((fp = fdopen(fd, "w")) == NULL)
375 			close(fd);
376 		else {
377 			int             i, j, k;
378 			int		len = LNBUFSZ;
379 			char           *buf = malloc(len);
380 
381 			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
382 				int             quote = 1;
383 				char const     *val = buf;
384 
385 				*buf = '\0';
386 				switch (i) {
387 				case _UC_DEFAULTPWD:
388 					val = boolean_str(config.default_password);
389 					break;
390 				case _UC_REUSEUID:
391 					val = boolean_str(config.reuse_uids);
392 					break;
393 				case _UC_REUSEGID:
394 					val = boolean_str(config.reuse_gids);
395 					break;
396 				case _UC_NISPASSWD:
397 					val = config.nispasswd ? config.nispasswd : "";
398 					quote = 0;
399 					break;
400 				case _UC_DOTDIR:
401 					val = config.dotdir ? config.dotdir : boolean_str(0);
402 					break;
403 				case _UC_NEWMAIL:
404 					val = config.newmail ? config.newmail : boolean_str(0);
405 					break;
406 				case _UC_LOGFILE:
407 					val = config.logfile ? config.logfile : boolean_str(0);
408 					break;
409 				case _UC_HOMEROOT:
410 					val = config.home;
411 					break;
412 				case _UC_SHELLPATH:
413 					val = config.shelldir;
414 					break;
415 				case _UC_SHELLS:
416 					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) {
417 						char	lbuf[64];
418 						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]);
419 						if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) {
420 							strcpy(buf + k, lbuf);
421 							k += l;
422 						}
423 					}
424 					quote = 0;
425 					break;
426 				case _UC_DEFAULTSHELL:
427 					val = config.shell_default ? config.shell_default : bourne_shell;
428 					break;
429 				case _UC_DEFAULTGROUP:
430 					val = config.default_group ? config.default_group : "";
431 					break;
432 				case _UC_EXTRAGROUPS:
433 					extendarray(&config.groups, &config.numgroups, 200);
434 					for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) {
435 						char	lbuf[64];
436 						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]);
437 						if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) {
438 							strcpy(buf + k, lbuf);
439 							k +=  l;
440 						}
441 					}
442 					quote = 0;
443 					break;
444 				case _UC_DEFAULTCLASS:
445 					val = config.default_class ? config.default_class : "";
446 					break;
447 				case _UC_MINUID:
448 					sprintf(buf, "%lu", (unsigned long) config.min_uid);
449 					quote = 0;
450 					break;
451 				case _UC_MAXUID:
452 					sprintf(buf, "%lu", (unsigned long) config.max_uid);
453 					quote = 0;
454 					break;
455 				case _UC_MINGID:
456 					sprintf(buf, "%lu", (unsigned long) config.min_gid);
457 					quote = 0;
458 					break;
459 				case _UC_MAXGID:
460 					sprintf(buf, "%lu", (unsigned long) config.max_gid);
461 					quote = 0;
462 					break;
463 				case _UC_EXPIRE:
464 					sprintf(buf, "%d", config.expire_days);
465 					quote = 0;
466 					break;
467 				case _UC_PASSWORD:
468 					sprintf(buf, "%d", config.password_days);
469 					quote = 0;
470 					break;
471 				case _UC_NONE:
472 					break;
473 				}
474 
475 				if (comments[i])
476 					fputs(comments[i], fp);
477 
478 				if (*kwds[i]) {
479 					if (quote)
480 						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
481 					else
482 						fprintf(fp, "%s = %s\n", kwds[i], val);
483 #if debugging
484 					printf("WROTE: %s = %s\n", kwds[i], val);
485 #endif
486 				}
487 			}
488 			free(buf);
489 			return fclose(fp) != EOF;
490 		}
491 	}
492 	return 0;
493 }
494