pw_conf.c revision 29002
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
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					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
363int
364write_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