pw_conf.c revision 282718
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 282718 2015-05-10 10:02:09Z bapt $";
30#endif /* not lint */
31
32#include <sys/types.h>
33#include <sys/sbuf.h>
34#include <string.h>
35#include <ctype.h>
36#include <fcntl.h>
37#include <err.h>
38
39#include "pw.h"
40
41#define debugging 0
42
43enum {
44	_UC_NONE,
45	_UC_DEFAULTPWD,
46	_UC_REUSEUID,
47	_UC_REUSEGID,
48	_UC_NISPASSWD,
49	_UC_DOTDIR,
50	_UC_NEWMAIL,
51	_UC_LOGFILE,
52	_UC_HOMEROOT,
53	_UC_HOMEMODE,
54	_UC_SHELLPATH,
55	_UC_SHELLS,
56	_UC_DEFAULTSHELL,
57	_UC_DEFAULTGROUP,
58	_UC_EXTRAGROUPS,
59	_UC_DEFAULTCLASS,
60	_UC_MINUID,
61	_UC_MAXUID,
62	_UC_MINGID,
63	_UC_MAXGID,
64	_UC_EXPIRE,
65	_UC_PASSWORD,
66	_UC_FIELDS
67};
68
69static char     bourne_shell[] = "sh";
70
71static char    *system_shells[_UC_MAXSHELLS] =
72{
73	bourne_shell,
74	"csh",
75	"tcsh"
76};
77
78static char const *booltrue[] =
79{
80	"yes", "true", "1", "on", NULL
81};
82static char const *boolfalse[] =
83{
84	"no", "false", "0", "off", NULL
85};
86
87static struct userconf config =
88{
89	0,			/* Default password for new users? (nologin) */
90	0,			/* Reuse uids? */
91	0,			/* Reuse gids? */
92	NULL,			/* NIS version of the passwd file */
93	"/usr/share/skel",	/* Where to obtain skeleton files */
94	NULL,			/* Mail to send to new accounts */
95	"/var/log/userlog",	/* Where to log changes */
96	"/home",		/* Where to create home directory */
97	_DEF_DIRMODE,		/* Home directory perms, modified by umask */
98	"/bin",			/* Where shells are located */
99	system_shells,		/* List of shells (first is default) */
100	bourne_shell,		/* Default shell */
101	NULL,			/* Default group name */
102	NULL,			/* Default (additional) groups */
103	NULL,			/* Default login class */
104	1000, 32000,		/* Allowed range of uids */
105	1000, 32000,		/* Allowed range of gids */
106	0,			/* Days until account expires */
107	0,			/* Days until password expires */
108	0			/* size of default_group array */
109};
110
111static char const *comments[_UC_FIELDS] =
112{
113	"#\n# pw.conf - user/group configuration defaults\n#\n",
114	"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
115	"\n# Reuse gaps in uid sequence? (yes or no)\n",
116	"\n# Reuse gaps in gid sequence? (yes or no)\n",
117	"\n# Path to the NIS passwd file (blank or 'no' for none)\n",
118	"\n# Obtain default dotfiles from this directory\n",
119	"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
120	"\n# Log add/change/remove information in this file\n",
121	"\n# Root directory in which $HOME directory is created\n",
122	"\n# Mode for the new $HOME directory, will be modified by umask\n",
123	"\n# Colon separated list of directories containing valid shells\n",
124	"\n# Comma separated list of available shells (without paths)\n",
125	"\n# Default shell (without path)\n",
126	"\n# Default group (leave blank for new group per user)\n",
127	"\n# Extra groups for new users\n",
128	"\n# Default login class for new users\n",
129	"\n# Range of valid default user ids\n",
130	NULL,
131	"\n# Range of valid default group ids\n",
132	NULL,
133	"\n# Days after which account expires (0=disabled)\n",
134	"\n# Days after which password expires (0=disabled)\n"
135};
136
137static char const *kwds[] =
138{
139	"",
140	"defaultpasswd",
141	"reuseuids",
142	"reusegids",
143	"nispasswd",
144	"skeleton",
145	"newmail",
146	"logfile",
147	"home",
148	"homemode",
149	"shellpath",
150	"shells",
151	"defaultshell",
152	"defaultgroup",
153	"extragroups",
154	"defaultclass",
155	"minuid",
156	"maxuid",
157	"mingid",
158	"maxgid",
159	"expire_days",
160	"password_days",
161	NULL
162};
163
164static char    *
165unquote(char const * str)
166{
167	if (str && (*str == '"' || *str == '\'')) {
168		char           *p = strchr(str + 1, *str);
169
170		if (p != NULL)
171			*p = '\0';
172		return (char *) (*++str ? str : NULL);
173	}
174	return (char *) str;
175}
176
177int
178boolean_val(char const * str, int dflt)
179{
180	if ((str = unquote(str)) != NULL) {
181		int             i;
182
183		for (i = 0; booltrue[i]; i++)
184			if (strcmp(str, booltrue[i]) == 0)
185				return 1;
186		for (i = 0; boolfalse[i]; i++)
187			if (strcmp(str, boolfalse[i]) == 0)
188				return 0;
189
190		/*
191		 * Special cases for defaultpassword
192		 */
193		if (strcmp(str, "random") == 0)
194			return -1;
195		if (strcmp(str, "none") == 0)
196			return -2;
197	}
198	return dflt;
199}
200
201char const     *
202boolean_str(int val)
203{
204	if (val == -1)
205		return "random";
206	else if (val == -2)
207		return "none";
208	else
209		return val ? booltrue[0] : boolfalse[0];
210}
211
212char           *
213newstr(char const * p)
214{
215	char	*q;
216	size_t	 l;
217
218	if ((p = unquote(p)) == NULL)
219		return (NULL);
220
221	l = strlen(p) + 1;
222
223	if ((q = strndup(p, l)) == NULL)
224		err(1, "strndup()");
225
226	return (q);
227}
228
229struct userconf *
230read_userconfig(char const * file)
231{
232	FILE	*fp;
233	char	*buf, *p;
234	size_t	linecap;
235	ssize_t	linelen;
236
237	buf = NULL;
238	linecap = 0;
239
240	extendarray(&config.groups, &config.numgroups, 200);
241	memset(config.groups, 0, config.numgroups * sizeof(char *));
242	if (file == NULL)
243		file = _PATH_PW_CONF;
244
245	if ((fp = fopen(file, "r")) != NULL) {
246		while ((linelen = getline(&buf, &linecap, fp)) > 0) {
247			if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
248				static char const toks[] = " \t\r\n,=";
249				char           *q = strtok(NULL, toks);
250				int             i = 0;
251				mode_t          *modeset;
252
253				while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
254					++i;
255#if debugging
256				if (i == _UC_FIELDS)
257					printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
258				else
259					printf("Got kwd[%s]=%s\n", p, q);
260#endif
261				switch (i) {
262				case _UC_DEFAULTPWD:
263					config.default_password = boolean_val(q, 1);
264					break;
265				case _UC_REUSEUID:
266					config.reuse_uids = boolean_val(q, 0);
267					break;
268				case _UC_REUSEGID:
269					config.reuse_gids = boolean_val(q, 0);
270					break;
271				case _UC_NISPASSWD:
272					config.nispasswd = (q == NULL || !boolean_val(q, 1))
273						? NULL : newstr(q);
274					break;
275				case _UC_DOTDIR:
276					config.dotdir = (q == NULL || !boolean_val(q, 1))
277						? NULL : newstr(q);
278					break;
279				case _UC_NEWMAIL:
280					config.newmail = (q == NULL || !boolean_val(q, 1))
281						? NULL : newstr(q);
282					break;
283				case _UC_LOGFILE:
284					config.logfile = (q == NULL || !boolean_val(q, 1))
285						? NULL : newstr(q);
286					break;
287				case _UC_HOMEROOT:
288					config.home = (q == NULL || !boolean_val(q, 1))
289						? "/home" : newstr(q);
290					break;
291				case _UC_HOMEMODE:
292					modeset = setmode(q);
293					config.homemode = (q == NULL || !boolean_val(q, 1))
294						? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
295					free(modeset);
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		if (linecap > 0)
361			free(buf);
362		fclose(fp);
363	}
364	return &config;
365}
366
367
368int
369write_userconfig(char const * file)
370{
371	int             fd;
372	int             i, j;
373	struct sbuf	*buf;
374	FILE           *fp;
375
376	if (file == NULL)
377		file = _PATH_PW_CONF;
378
379	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
380		return (0);
381
382	if ((fp = fdopen(fd, "w")) == NULL) {
383		close(fd);
384		return (0);
385	}
386
387	buf = sbuf_new_auto();
388	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
389		int             quote = 1;
390
391		sbuf_clear(buf);
392		switch (i) {
393		case _UC_DEFAULTPWD:
394			sbuf_cat(buf, boolean_str(config.default_password));
395			break;
396		case _UC_REUSEUID:
397			sbuf_cat(buf, boolean_str(config.reuse_uids));
398			break;
399		case _UC_REUSEGID:
400			sbuf_cat(buf, boolean_str(config.reuse_gids));
401			break;
402		case _UC_NISPASSWD:
403			sbuf_cat(buf, config.nispasswd ?  config.nispasswd :
404			    "");
405			quote = 0;
406			break;
407		case _UC_DOTDIR:
408			sbuf_cat(buf, config.dotdir ?  config.dotdir :
409			    boolean_str(0));
410			break;
411		case _UC_NEWMAIL:
412			sbuf_cat(buf, config.newmail ?  config.newmail :
413			    boolean_str(0));
414			break;
415		case _UC_LOGFILE:
416			sbuf_cat(buf, config.logfile ?  config.logfile :
417			    boolean_str(0));
418			break;
419		case _UC_HOMEROOT:
420			sbuf_cat(buf, config.home);
421			break;
422		case _UC_HOMEMODE:
423			sbuf_printf(buf, "%04o", config.homemode);
424			quote = 0;
425			break;
426		case _UC_SHELLPATH:
427			sbuf_cat(buf, config.shelldir);
428			break;
429		case _UC_SHELLS:
430			for (j = 0; j < _UC_MAXSHELLS &&
431			    system_shells[j] != NULL; j++)
432				sbuf_printf(buf, "%s\"%s\"", j ?
433				    "," : "", system_shells[j]);
434			quote = 0;
435			break;
436		case _UC_DEFAULTSHELL:
437			sbuf_cat(buf, config.shell_default ?
438			    config.shell_default : bourne_shell);
439			break;
440		case _UC_DEFAULTGROUP:
441			sbuf_cat(buf, config.default_group ?
442			    config.default_group : "");
443			break;
444		case _UC_EXTRAGROUPS:
445			for (j = 0; j < config.numgroups &&
446			    config.groups[j] != NULL; j++)
447				sbuf_printf(buf, "%s\"%s\"", j ?
448				    "," : "", config.groups[j]);
449			quote = 0;
450			break;
451		case _UC_DEFAULTCLASS:
452			sbuf_cat(buf, config.default_class ?
453			    config.default_class : "");
454			break;
455		case _UC_MINUID:
456			sbuf_printf(buf, "%lu", (unsigned long) config.min_uid);
457			quote = 0;
458			break;
459		case _UC_MAXUID:
460			sbuf_printf(buf, "%lu", (unsigned long) config.max_uid);
461			quote = 0;
462			break;
463		case _UC_MINGID:
464			sbuf_printf(buf, "%lu", (unsigned long) config.min_gid);
465			quote = 0;
466			break;
467		case _UC_MAXGID:
468			sbuf_printf(buf, "%lu", (unsigned long) config.max_gid);
469			quote = 0;
470			break;
471		case _UC_EXPIRE:
472			sbuf_printf(buf, "%d", config.expire_days);
473			quote = 0;
474			break;
475		case _UC_PASSWORD:
476			sbuf_printf(buf, "%d", config.password_days);
477			quote = 0;
478			break;
479		case _UC_NONE:
480			break;
481		}
482		sbuf_finish(buf);
483
484		if (comments[i])
485			fputs(comments[i], fp);
486
487		if (*kwds[i]) {
488			if (quote)
489				fprintf(fp, "%s = \"%s\"\n", kwds[i],
490				    sbuf_data(buf));
491			else
492				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
493#if debugging
494			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
495#endif
496		}
497	}
498	sbuf_delete(buf);
499	return (fclose(fp) != EOF);
500}
501