pw_conf.c revision 264781
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 264781 2014-04-22 21:07:56Z bapt $";
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	char	*buf, *p;
231	size_t	linecap;
232	ssize_t	linelen;
233
234	buf = NULL;
235	linecap = 0;
236
237	extendarray(&config.groups, &config.numgroups, 200);
238	memset(config.groups, 0, config.numgroups * sizeof(char *));
239	if (file == NULL)
240		file = _PATH_PW_CONF;
241
242	if ((fp = fopen(file, "r")) != NULL) {
243		while ((linelen = getline(&buf, &linecap, fp)) > 0) {
244			if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
245				static char const toks[] = " \t\r\n,=";
246				char           *q = strtok(NULL, toks);
247				int             i = 0;
248				mode_t          *modeset;
249
250				while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
251					++i;
252#if debugging
253				if (i == _UC_FIELDS)
254					printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
255				else
256					printf("Got kwd[%s]=%s\n", p, q);
257#endif
258				switch (i) {
259				case _UC_DEFAULTPWD:
260					config.default_password = boolean_val(q, 1);
261					break;
262				case _UC_REUSEUID:
263					config.reuse_uids = boolean_val(q, 0);
264					break;
265				case _UC_REUSEGID:
266					config.reuse_gids = boolean_val(q, 0);
267					break;
268				case _UC_NISPASSWD:
269					config.nispasswd = (q == NULL || !boolean_val(q, 1))
270						? NULL : newstr(q);
271					break;
272				case _UC_DOTDIR:
273					config.dotdir = (q == NULL || !boolean_val(q, 1))
274						? NULL : newstr(q);
275					break;
276				case _UC_NEWMAIL:
277					config.newmail = (q == NULL || !boolean_val(q, 1))
278						? NULL : newstr(q);
279					break;
280				case _UC_LOGFILE:
281					config.logfile = (q == NULL || !boolean_val(q, 1))
282						? NULL : newstr(q);
283					break;
284				case _UC_HOMEROOT:
285					config.home = (q == NULL || !boolean_val(q, 1))
286						? "/home" : newstr(q);
287					break;
288				case _UC_HOMEMODE:
289					modeset = setmode(q);
290					config.homemode = (q == NULL || !boolean_val(q, 1))
291						? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
292					free(modeset);
293					break;
294				case _UC_SHELLPATH:
295					config.shelldir = (q == NULL || !boolean_val(q, 1))
296						? "/bin" : newstr(q);
297					break;
298				case _UC_SHELLS:
299					for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
300						system_shells[i] = newstr(q);
301					if (i > 0)
302						while (i < _UC_MAXSHELLS)
303							system_shells[i++] = NULL;
304					break;
305				case _UC_DEFAULTSHELL:
306					config.shell_default = (q == NULL || !boolean_val(q, 1))
307						? (char *) bourne_shell : newstr(q);
308					break;
309				case _UC_DEFAULTGROUP:
310					q = unquote(q);
311					config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
312						? NULL : newstr(q);
313					break;
314				case _UC_EXTRAGROUPS:
315					for (i = 0; q != NULL; q = strtok(NULL, toks)) {
316						if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
317							config.groups[i++] = newstr(q);
318					}
319					if (i > 0)
320						while (i < config.numgroups)
321							config.groups[i++] = NULL;
322					break;
323				case _UC_DEFAULTCLASS:
324					config.default_class = (q == NULL || !boolean_val(q, 1))
325						? NULL : newstr(q);
326					break;
327				case _UC_MINUID:
328					if ((q = unquote(q)) != NULL && isdigit(*q))
329						config.min_uid = (uid_t) atol(q);
330					break;
331				case _UC_MAXUID:
332					if ((q = unquote(q)) != NULL && isdigit(*q))
333						config.max_uid = (uid_t) atol(q);
334					break;
335				case _UC_MINGID:
336					if ((q = unquote(q)) != NULL && isdigit(*q))
337						config.min_gid = (gid_t) atol(q);
338					break;
339				case _UC_MAXGID:
340					if ((q = unquote(q)) != NULL && isdigit(*q))
341						config.max_gid = (gid_t) atol(q);
342					break;
343				case _UC_EXPIRE:
344					if ((q = unquote(q)) != NULL && isdigit(*q))
345						config.expire_days = atoi(q);
346					break;
347				case _UC_PASSWORD:
348					if ((q = unquote(q)) != NULL && isdigit(*q))
349						config.password_days = atoi(q);
350					break;
351				case _UC_FIELDS:
352				case _UC_NONE:
353					break;
354				}
355			}
356		}
357		if (linecap > 0)
358			free(buf);
359		fclose(fp);
360	}
361	return &config;
362}
363
364
365int
366write_userconfig(char const * file)
367{
368	int             fd;
369
370	if (file == NULL)
371		file = _PATH_PW_CONF;
372
373	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
374		FILE           *fp;
375
376		if ((fp = fdopen(fd, "w")) == NULL)
377			close(fd);
378		else {
379			int             i, j, k;
380			int		len = LNBUFSZ;
381			char           *buf = malloc(len);
382
383			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
384				int             quote = 1;
385				char const     *val = buf;
386
387				*buf = '\0';
388				switch (i) {
389				case _UC_DEFAULTPWD:
390					val = boolean_str(config.default_password);
391					break;
392				case _UC_REUSEUID:
393					val = boolean_str(config.reuse_uids);
394					break;
395				case _UC_REUSEGID:
396					val = boolean_str(config.reuse_gids);
397					break;
398				case _UC_NISPASSWD:
399					val = config.nispasswd ? config.nispasswd : "";
400					quote = 0;
401					break;
402				case _UC_DOTDIR:
403					val = config.dotdir ? config.dotdir : boolean_str(0);
404					break;
405				case _UC_NEWMAIL:
406					val = config.newmail ? config.newmail : boolean_str(0);
407					break;
408				case _UC_LOGFILE:
409					val = config.logfile ? config.logfile : boolean_str(0);
410					break;
411				case _UC_HOMEROOT:
412					val = config.home;
413					break;
414				case _UC_HOMEMODE:
415					sprintf(buf, "%04o", config.homemode);
416					quote = 0;
417					break;
418				case _UC_SHELLPATH:
419					val = config.shelldir;
420					break;
421				case _UC_SHELLS:
422					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) {
423						char	lbuf[64];
424						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]);
425						if (l < 0)
426							l = 0;
427						if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) {
428							strcpy(buf + k, lbuf);
429							k += l;
430						}
431					}
432					quote = 0;
433					break;
434				case _UC_DEFAULTSHELL:
435					val = config.shell_default ? config.shell_default : bourne_shell;
436					break;
437				case _UC_DEFAULTGROUP:
438					val = config.default_group ? config.default_group : "";
439					break;
440				case _UC_EXTRAGROUPS:
441					extendarray(&config.groups, &config.numgroups, 200);
442					for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) {
443						char	lbuf[64];
444						int	l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]);
445						if (l < 0)
446							l = 0;
447						if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) {
448							strcpy(buf + k, lbuf);
449							k +=  l;
450						}
451					}
452					quote = 0;
453					break;
454				case _UC_DEFAULTCLASS:
455					val = config.default_class ? config.default_class : "";
456					break;
457				case _UC_MINUID:
458					sprintf(buf, "%lu", (unsigned long) config.min_uid);
459					quote = 0;
460					break;
461				case _UC_MAXUID:
462					sprintf(buf, "%lu", (unsigned long) config.max_uid);
463					quote = 0;
464					break;
465				case _UC_MINGID:
466					sprintf(buf, "%lu", (unsigned long) config.min_gid);
467					quote = 0;
468					break;
469				case _UC_MAXGID:
470					sprintf(buf, "%lu", (unsigned long) config.max_gid);
471					quote = 0;
472					break;
473				case _UC_EXPIRE:
474					sprintf(buf, "%d", config.expire_days);
475					quote = 0;
476					break;
477				case _UC_PASSWORD:
478					sprintf(buf, "%d", config.password_days);
479					quote = 0;
480					break;
481				case _UC_NONE:
482					break;
483				}
484
485				if (comments[i])
486					fputs(comments[i], fp);
487
488				if (*kwds[i]) {
489					if (quote)
490						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
491					else
492						fprintf(fp, "%s = %s\n", kwds[i], val);
493#if debugging
494					printf("WROTE: %s = %s\n", kwds[i], val);
495#endif
496				}
497			}
498			free(buf);
499			return fclose(fp) != EOF;
500		}
501	}
502	return 0;
503}
504