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