pw_conf.c revision 282719
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 282719 2015-05-10 10:15:36Z 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
217	if ((p = unquote(p)) == NULL)
218		return (NULL);
219
220	if ((q = strdup(p)) == NULL)
221		err(1, "strdup()");
222
223	return (q);
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	int             i, j;
370	struct sbuf	*buf;
371	FILE           *fp;
372
373	if (file == NULL)
374		file = _PATH_PW_CONF;
375
376	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
377		return (0);
378
379	if ((fp = fdopen(fd, "w")) == NULL) {
380		close(fd);
381		return (0);
382	}
383
384	buf = sbuf_new_auto();
385	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
386		int             quote = 1;
387
388		sbuf_clear(buf);
389		switch (i) {
390		case _UC_DEFAULTPWD:
391			sbuf_cat(buf, boolean_str(config.default_password));
392			break;
393		case _UC_REUSEUID:
394			sbuf_cat(buf, boolean_str(config.reuse_uids));
395			break;
396		case _UC_REUSEGID:
397			sbuf_cat(buf, boolean_str(config.reuse_gids));
398			break;
399		case _UC_NISPASSWD:
400			sbuf_cat(buf, config.nispasswd ?  config.nispasswd :
401			    "");
402			quote = 0;
403			break;
404		case _UC_DOTDIR:
405			sbuf_cat(buf, config.dotdir ?  config.dotdir :
406			    boolean_str(0));
407			break;
408		case _UC_NEWMAIL:
409			sbuf_cat(buf, config.newmail ?  config.newmail :
410			    boolean_str(0));
411			break;
412		case _UC_LOGFILE:
413			sbuf_cat(buf, config.logfile ?  config.logfile :
414			    boolean_str(0));
415			break;
416		case _UC_HOMEROOT:
417			sbuf_cat(buf, config.home);
418			break;
419		case _UC_HOMEMODE:
420			sbuf_printf(buf, "%04o", config.homemode);
421			quote = 0;
422			break;
423		case _UC_SHELLPATH:
424			sbuf_cat(buf, config.shelldir);
425			break;
426		case _UC_SHELLS:
427			for (j = 0; j < _UC_MAXSHELLS &&
428			    system_shells[j] != NULL; j++)
429				sbuf_printf(buf, "%s\"%s\"", j ?
430				    "," : "", system_shells[j]);
431			quote = 0;
432			break;
433		case _UC_DEFAULTSHELL:
434			sbuf_cat(buf, config.shell_default ?
435			    config.shell_default : bourne_shell);
436			break;
437		case _UC_DEFAULTGROUP:
438			sbuf_cat(buf, config.default_group ?
439			    config.default_group : "");
440			break;
441		case _UC_EXTRAGROUPS:
442			for (j = 0; j < config.numgroups &&
443			    config.groups[j] != NULL; j++)
444				sbuf_printf(buf, "%s\"%s\"", j ?
445				    "," : "", config.groups[j]);
446			quote = 0;
447			break;
448		case _UC_DEFAULTCLASS:
449			sbuf_cat(buf, config.default_class ?
450			    config.default_class : "");
451			break;
452		case _UC_MINUID:
453			sbuf_printf(buf, "%lu", (unsigned long) config.min_uid);
454			quote = 0;
455			break;
456		case _UC_MAXUID:
457			sbuf_printf(buf, "%lu", (unsigned long) config.max_uid);
458			quote = 0;
459			break;
460		case _UC_MINGID:
461			sbuf_printf(buf, "%lu", (unsigned long) config.min_gid);
462			quote = 0;
463			break;
464		case _UC_MAXGID:
465			sbuf_printf(buf, "%lu", (unsigned long) config.max_gid);
466			quote = 0;
467			break;
468		case _UC_EXPIRE:
469			sbuf_printf(buf, "%d", config.expire_days);
470			quote = 0;
471			break;
472		case _UC_PASSWORD:
473			sbuf_printf(buf, "%d", config.password_days);
474			quote = 0;
475			break;
476		case _UC_NONE:
477			break;
478		}
479		sbuf_finish(buf);
480
481		if (comments[i])
482			fputs(comments[i], fp);
483
484		if (*kwds[i]) {
485			if (quote)
486				fprintf(fp, "%s = \"%s\"\n", kwds[i],
487				    sbuf_data(buf));
488			else
489				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
490#if debugging
491			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
492#endif
493		}
494	}
495	sbuf_delete(buf);
496	return (fclose(fp) != EOF);
497}
498