pw_conf.c revision 70133
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  "$FreeBSD: head/usr.sbin/pw/pw_conf.c 70133 2000-12-18 01:35:56Z dougb $";
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
40enum {
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_SHELLPATH,
51	_UC_SHELLS,
52	_UC_DEFAULTSHELL,
53	_UC_DEFAULTGROUP,
54	_UC_EXTRAGROUPS,
55	_UC_DEFAULTCLASS,
56	_UC_MINUID,
57	_UC_MAXUID,
58	_UC_MINGID,
59	_UC_MAXGID,
60	_UC_EXPIRE,
61	_UC_PASSWORD,
62	_UC_FIELDS
63};
64
65static char     bourne_shell[] = "sh";
66
67static char    *system_shells[_UC_MAXSHELLS] =
68{
69	bourne_shell,
70	"csh",
71	"tcsh"
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	0			/* size of default_group array */
104};
105
106static char const *comments[_UC_FIELDS] =
107{
108	"#\n# pw.conf - user/group configuration defaults\n#\n",
109	"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
110	"\n# Reuse gaps in uid sequence? (yes or no)\n",
111	"\n# Reuse gaps in gid sequence? (yes or no)\n",
112	"\n# Path to the NIS passwd file (blank or 'no' for none)\n",
113	"\n# Obtain default dotfiles from this directory\n",
114	"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
115	"\n# Log add/change/remove information in this file\n",
116	"\n# Root directory in which $HOME directory is created\n",
117	"\n# Colon separated list of directories containing valid shells\n",
118	"\n# Comma separated list of available shells (without paths)\n",
119	"\n# Default shell (without path)\n",
120	"\n# Default group (leave blank for new group per user)\n",
121	"\n# Extra groups for new users\n",
122	"\n# Default login class for new users\n",
123	"\n# Range of valid default user ids\n",
124	NULL,
125	"\n# Range of valid default group ids\n",
126	NULL,
127	"\n# Days after which account expires (0=disabled)\n",
128	"\n# Days after which password expires (0=disabled)\n"
129};
130
131static char const *kwds[] =
132{
133	"",
134	"defaultpasswd",
135	"reuseuids",
136	"reusegids",
137	"nispasswd",
138	"skeleton",
139	"newmail",
140	"logfile",
141	"home",
142	"shellpath",
143	"shells",
144	"defaultshell",
145	"defaultgroup",
146	"extragroups",
147	"defaultclass",
148	"minuid",
149	"maxuid",
150	"mingid",
151	"maxgid",
152	"expire_days",
153	"password_days",
154	NULL
155};
156
157static char    *
158unquote(char const * str)
159{
160	if (str && (*str == '"' || *str == '\'')) {
161		char           *p = strchr(str + 1, *str);
162
163		if (p != NULL)
164			*p = '\0';
165		return (char *) (*++str ? str : NULL);
166	}
167	return (char *) str;
168}
169
170int
171boolean_val(char const * str, int dflt)
172{
173	if ((str = unquote(str)) != NULL) {
174		int             i;
175
176		for (i = 0; booltrue[i]; i++)
177			if (strcmp(str, booltrue[i]) == 0)
178				return 1;
179		for (i = 0; boolfalse[i]; i++)
180			if (strcmp(str, boolfalse[i]) == 0)
181				return 0;
182
183		/*
184		 * Special cases for defaultpassword
185		 */
186		if (strcmp(str, "random") == 0)
187			return -1;
188		if (strcmp(str, "none") == 0)
189			return -2;
190	}
191	return dflt;
192}
193
194char const     *
195boolean_str(int val)
196{
197	if (val == -1)
198		return "random";
199	else if (val == -2)
200		return "none";
201	else
202		return val ? booltrue[0] : boolfalse[0];
203}
204
205char           *
206newstr(char const * p)
207{
208	char           *q = NULL;
209
210	if ((p = unquote(p)) != NULL) {
211		int             l = strlen(p) + 1;
212
213		if ((q = malloc(l)) != NULL)
214			memcpy(q, p, l);
215	}
216	return q;
217}
218
219#define LNBUFSZ 1024
220
221
222struct userconf *
223read_userconfig(char const * file)
224{
225	FILE           *fp;
226
227	extendarray(&config.groups, &config.numgroups, 200);
228	memset(config.groups, 0, config.numgroups * sizeof(char *));
229	if (file == NULL)
230		file = _PATH_PW_CONF;
231	if ((fp = fopen(file, "r")) != NULL) {
232		int	    buflen = LNBUFSZ;
233		char       *buf = malloc(buflen);
234
235	nextline:
236		while (fgets(buf, buflen, fp) != NULL) {
237			char           *p;
238
239			while ((p = strchr(buf, '\n')) == NULL) {
240				int	  l;
241				if (extendline(&buf, &buflen, buflen + LNBUFSZ) == -1) {
242					int	ch;
243					while ((ch = fgetc(fp)) != '\n' && ch != EOF);
244					goto nextline;	/* Ignore it */
245				}
246				l = strlen(buf);
247				if (fgets(buf + l, buflen - l, fp) == NULL)
248					break;	/* Unterminated last line */
249			}
250
251			if (p != NULL)
252				*p = '\0';
253
254			if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
255				static char const toks[] = " \t\r\n,=";
256				char           *q = strtok(NULL, toks);
257				int             i = 0;
258
259				while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
260					++i;
261#if debugging
262				if (i == _UC_FIELDS)
263					printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
264				else
265					printf("Got kwd[%s]=%s\n", p, q);
266#endif
267				switch (i) {
268				case _UC_DEFAULTPWD:
269					config.default_password = boolean_val(q, 1);
270					break;
271				case _UC_REUSEUID:
272					config.reuse_uids = boolean_val(q, 0);
273					break;
274				case _UC_REUSEGID:
275					config.reuse_gids = boolean_val(q, 0);
276					break;
277				case _UC_NISPASSWD:
278					config.nispasswd = (q == NULL || !boolean_val(q, 1))
279						? NULL : newstr(q);
280					break;
281				case _UC_DOTDIR:
282					config.dotdir = (q == NULL || !boolean_val(q, 1))
283						? NULL : newstr(q);
284					break;
285				case _UC_NEWMAIL:
286					config.newmail = (q == NULL || !boolean_val(q, 1))
287						? NULL : newstr(q);
288					break;
289				case _UC_LOGFILE:
290					config.logfile = (q == NULL || !boolean_val(q, 1))
291						? NULL : newstr(q);
292					break;
293				case _UC_HOMEROOT:
294					config.home = (q == NULL || !boolean_val(q, 1))
295						? "/home" : newstr(q);
296					break;
297				case _UC_SHELLPATH:
298					config.shelldir = (q == NULL || !boolean_val(q, 1))
299						? "/bin" : newstr(q);
300					break;
301				case _UC_SHELLS:
302					for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
303						system_shells[i] = newstr(q);
304					if (i > 0)
305						while (i < _UC_MAXSHELLS)
306							system_shells[i++] = NULL;
307					break;
308				case _UC_DEFAULTSHELL:
309					config.shell_default = (q == NULL || !boolean_val(q, 1))
310						? (char *) bourne_shell : newstr(q);
311					break;
312				case _UC_DEFAULTGROUP:
313					q = unquote(q);
314					config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
315						? NULL : newstr(q);
316					break;
317				case _UC_EXTRAGROUPS:
318					for (i = 0; q != NULL; q = strtok(NULL, toks)) {
319						if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
320							config.groups[i++] = newstr(q);
321					}
322					if (i > 0)
323						while (i < config.numgroups)
324							config.groups[i++] = NULL;
325					break;
326				case _UC_DEFAULTCLASS:
327					config.default_class = (q == NULL || !boolean_val(q, 1))
328						? NULL : newstr(q);
329					break;
330				case _UC_MINUID:
331					if ((q = unquote(q)) != NULL && isdigit(*q))
332						config.min_uid = (uid_t) atol(q);
333					break;
334				case _UC_MAXUID:
335					if ((q = unquote(q)) != NULL && isdigit(*q))
336						config.max_uid = (uid_t) atol(q);
337					break;
338				case _UC_MINGID:
339					if ((q = unquote(q)) != NULL && isdigit(*q))
340						config.min_gid = (gid_t) atol(q);
341					break;
342				case _UC_MAXGID:
343					if ((q = unquote(q)) != NULL && isdigit(*q))
344						config.max_gid = (gid_t) atol(q);
345					break;
346				case _UC_EXPIRE:
347					if ((q = unquote(q)) != NULL && isdigit(*q))
348						config.expire_days = atoi(q);
349					break;
350				case _UC_PASSWORD:
351					if ((q = unquote(q)) != NULL && isdigit(*q))
352						config.password_days = atoi(q);
353					break;
354				case _UC_FIELDS:
355				case _UC_NONE:
356					break;
357				}
358			}
359		}
360		free(buf);
361		fclose(fp);
362	}
363	return &config;
364}
365
366
367int
368write_userconfig(char const * file)
369{
370	int             fd;
371
372	if (file == NULL)
373		file = _PATH_PW_CONF;
374
375	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
376		FILE           *fp;
377
378		if ((fp = fdopen(fd, "w")) == NULL)
379			close(fd);
380		else {
381			int             i, j, k;
382			int		len = LNBUFSZ;
383			char           *buf = malloc(len);
384
385			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
386				int             quote = 1;
387				char const     *val = buf;
388
389				*buf = '\0';
390				switch (i) {
391				case _UC_DEFAULTPWD:
392					val = boolean_str(config.default_password);
393					break;
394				case _UC_REUSEUID:
395					val = boolean_str(config.reuse_uids);
396					break;
397				case _UC_REUSEGID:
398					val = boolean_str(config.reuse_gids);
399					break;
400				case _UC_NISPASSWD:
401					val = config.nispasswd ? config.nispasswd : "";
402					quote = 0;
403					break;
404				case _UC_DOTDIR:
405					val = config.dotdir ? config.dotdir : boolean_str(0);
406					break;
407				case _UC_NEWMAIL:
408					val = config.newmail ? config.newmail : boolean_str(0);
409					break;
410				case _UC_LOGFILE:
411					val = config.logfile ? config.logfile : boolean_str(0);
412					break;
413				case _UC_HOMEROOT:
414					val = config.home;
415					break;
416				case _UC_SHELLPATH:
417					val = config.shelldir;
418					break;
419				case _UC_SHELLS:
420					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) {
421						char	lbuf[64];
422						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]);
423						if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) {
424							strcpy(buf + k, lbuf);
425							k += l;
426						}
427					}
428					quote = 0;
429					break;
430				case _UC_DEFAULTSHELL:
431					val = config.shell_default ? config.shell_default : bourne_shell;
432					break;
433				case _UC_DEFAULTGROUP:
434					val = config.default_group ? config.default_group : "";
435					break;
436				case _UC_EXTRAGROUPS:
437					extendarray(&config.groups, &config.numgroups, 200);
438					for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) {
439						char	lbuf[64];
440						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]);
441						if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) {
442							strcpy(buf + k, lbuf);
443							k +=  l;
444						}
445					}
446					quote = 0;
447					break;
448				case _UC_DEFAULTCLASS:
449					val = config.default_class ? config.default_class : "";
450					break;
451				case _UC_MINUID:
452					sprintf(buf, "%lu", (unsigned long) config.min_uid);
453					quote = 0;
454					break;
455				case _UC_MAXUID:
456					sprintf(buf, "%lu", (unsigned long) config.max_uid);
457					quote = 0;
458					break;
459				case _UC_MINGID:
460					sprintf(buf, "%lu", (unsigned long) config.min_gid);
461					quote = 0;
462					break;
463				case _UC_MAXGID:
464					sprintf(buf, "%lu", (unsigned long) config.max_gid);
465					quote = 0;
466					break;
467				case _UC_EXPIRE:
468					sprintf(buf, "%d", config.expire_days);
469					quote = 0;
470					break;
471				case _UC_PASSWORD:
472					sprintf(buf, "%d", config.password_days);
473					quote = 0;
474					break;
475				case _UC_NONE:
476					break;
477				}
478
479				if (comments[i])
480					fputs(comments[i], fp);
481
482				if (*kwds[i]) {
483					if (quote)
484						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
485					else
486						fprintf(fp, "%s = %s\n", kwds[i], val);
487#if debugging
488					printf("WROTE: %s = %s\n", kwds[i], val);
489#endif
490				}
491			}
492			free(buf);
493			return fclose(fp) != EOF;
494		}
495	}
496	return 0;
497}
498