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