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