pw_conf.c revision 286150
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 286150 2015-08-01 09:55:47Z bapt $";
30#endif /* not lint */
31
32#include <sys/types.h>
33#include <sys/sbuf.h>
34#include <inttypes.h>
35#include <string.h>
36#include <ctype.h>
37#include <fcntl.h>
38#include <err.h>
39
40#include "pw.h"
41
42#define debugging 0
43
44enum {
45	_UC_NONE,
46	_UC_DEFAULTPWD,
47	_UC_REUSEUID,
48	_UC_REUSEGID,
49	_UC_NISPASSWD,
50	_UC_DOTDIR,
51	_UC_NEWMAIL,
52	_UC_LOGFILE,
53	_UC_HOMEROOT,
54	_UC_HOMEMODE,
55	_UC_SHELLPATH,
56	_UC_SHELLS,
57	_UC_DEFAULTSHELL,
58	_UC_DEFAULTGROUP,
59	_UC_EXTRAGROUPS,
60	_UC_DEFAULTCLASS,
61	_UC_MINUID,
62	_UC_MAXUID,
63	_UC_MINGID,
64	_UC_MAXGID,
65	_UC_EXPIRE,
66	_UC_PASSWORD,
67	_UC_FIELDS
68};
69
70static char     bourne_shell[] = "sh";
71
72static char    *system_shells[_UC_MAXSHELLS] =
73{
74	bourne_shell,
75	"csh",
76	"tcsh"
77};
78
79static char const *booltrue[] =
80{
81	"yes", "true", "1", "on", NULL
82};
83static char const *boolfalse[] =
84{
85	"no", "false", "0", "off", NULL
86};
87
88static struct userconf config =
89{
90	0,			/* Default password for new users? (nologin) */
91	0,			/* Reuse uids? */
92	0,			/* Reuse gids? */
93	NULL,			/* NIS version of the passwd file */
94	"/usr/share/skel",	/* Where to obtain skeleton files */
95	NULL,			/* Mail to send to new accounts */
96	"/var/log/userlog",	/* Where to log changes */
97	"/home",		/* Where to create home directory */
98	_DEF_DIRMODE,		/* Home directory perms, modified by umask */
99	"/bin",			/* Where shells are located */
100	system_shells,		/* List of shells (first is default) */
101	bourne_shell,		/* Default shell */
102	NULL,			/* Default group name */
103	NULL,			/* Default (additional) groups */
104	NULL,			/* Default login class */
105	1000, 32000,		/* Allowed range of uids */
106	1000, 32000,		/* Allowed range of gids */
107	0,			/* Days until account expires */
108	0			/* Days until password expires */
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	config.groups = sl_init();
238	if (config.groups == NULL)
239		err(1, "sl_init()");
240	if (file == NULL)
241		file = _PATH_PW_CONF;
242
243	if ((fp = fopen(file, "r")) == NULL)
244		return (&config);
245
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					sl_add(config.groups, newstr(q));
320				break;
321			case _UC_DEFAULTCLASS:
322				config.default_class = (q == NULL || !boolean_val(q, 1))
323					? NULL : newstr(q);
324				break;
325			case _UC_MINUID:
326				if ((q = unquote(q)) != NULL && isdigit(*q))
327					config.min_uid = (uid_t) atol(q);
328				break;
329			case _UC_MAXUID:
330				if ((q = unquote(q)) != NULL && isdigit(*q))
331					config.max_uid = (uid_t) atol(q);
332				break;
333			case _UC_MINGID:
334				if ((q = unquote(q)) != NULL && isdigit(*q))
335					config.min_gid = (gid_t) atol(q);
336				break;
337			case _UC_MAXGID:
338				if ((q = unquote(q)) != NULL && isdigit(*q))
339					config.max_gid = (gid_t) atol(q);
340				break;
341			case _UC_EXPIRE:
342				if ((q = unquote(q)) != NULL && isdigit(*q))
343					config.expire_days = atoi(q);
344				break;
345			case _UC_PASSWORD:
346				if ((q = unquote(q)) != NULL && isdigit(*q))
347					config.password_days = atoi(q);
348				break;
349			case _UC_FIELDS:
350			case _UC_NONE:
351				break;
352			}
353		}
354	}
355	free(buf);
356	fclose(fp);
357
358	return (&config);
359}
360
361
362int
363write_userconfig(char const * file)
364{
365	int             fd;
366	int             i, j;
367	struct sbuf	*buf;
368	FILE           *fp;
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		return (0);
375
376	if ((fp = fdopen(fd, "w")) == NULL) {
377		close(fd);
378		return (0);
379	}
380
381	buf = sbuf_new_auto();
382	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
383		int             quote = 1;
384
385		sbuf_clear(buf);
386		switch (i) {
387		case _UC_DEFAULTPWD:
388			sbuf_cat(buf, boolean_str(config.default_password));
389			break;
390		case _UC_REUSEUID:
391			sbuf_cat(buf, boolean_str(config.reuse_uids));
392			break;
393		case _UC_REUSEGID:
394			sbuf_cat(buf, boolean_str(config.reuse_gids));
395			break;
396		case _UC_NISPASSWD:
397			sbuf_cat(buf, config.nispasswd ?  config.nispasswd :
398			    "");
399			quote = 0;
400			break;
401		case _UC_DOTDIR:
402			sbuf_cat(buf, config.dotdir ?  config.dotdir :
403			    boolean_str(0));
404			break;
405		case _UC_NEWMAIL:
406			sbuf_cat(buf, config.newmail ?  config.newmail :
407			    boolean_str(0));
408			break;
409		case _UC_LOGFILE:
410			sbuf_cat(buf, config.logfile ?  config.logfile :
411			    boolean_str(0));
412			break;
413		case _UC_HOMEROOT:
414			sbuf_cat(buf, config.home);
415			break;
416		case _UC_HOMEMODE:
417			sbuf_printf(buf, "%04o", config.homemode);
418			quote = 0;
419			break;
420		case _UC_SHELLPATH:
421			sbuf_cat(buf, config.shelldir);
422			break;
423		case _UC_SHELLS:
424			for (j = 0; j < _UC_MAXSHELLS &&
425			    system_shells[j] != NULL; j++)
426				sbuf_printf(buf, "%s\"%s\"", j ?
427				    "," : "", system_shells[j]);
428			quote = 0;
429			break;
430		case _UC_DEFAULTSHELL:
431			sbuf_cat(buf, config.shell_default ?
432			    config.shell_default : bourne_shell);
433			break;
434		case _UC_DEFAULTGROUP:
435			sbuf_cat(buf, config.default_group ?
436			    config.default_group : "");
437			break;
438		case _UC_EXTRAGROUPS:
439			for (j = 0; config.groups != NULL &&
440			    j < (int)config.groups->sl_cur; j++)
441				sbuf_printf(buf, "%s\"%s\"", j ?
442				    "," : "", config.groups->sl_str[j]);
443			quote = 0;
444			break;
445		case _UC_DEFAULTCLASS:
446			sbuf_cat(buf, config.default_class ?
447			    config.default_class : "");
448			break;
449		case _UC_MINUID:
450			sbuf_printf(buf, "%ju", (uintmax_t)config.min_uid);
451			quote = 0;
452			break;
453		case _UC_MAXUID:
454			sbuf_printf(buf, "%ju", (uintmax_t)config.max_uid);
455			quote = 0;
456			break;
457		case _UC_MINGID:
458			sbuf_printf(buf, "%ju", (uintmax_t)config.min_gid);
459			quote = 0;
460			break;
461		case _UC_MAXGID:
462			sbuf_printf(buf, "%ju", (uintmax_t)config.max_gid);
463			quote = 0;
464			break;
465		case _UC_EXPIRE:
466			sbuf_printf(buf, "%d", config.expire_days);
467			quote = 0;
468			break;
469		case _UC_PASSWORD:
470			sbuf_printf(buf, "%d", config.password_days);
471			quote = 0;
472			break;
473		case _UC_NONE:
474			break;
475		}
476		sbuf_finish(buf);
477
478		if (comments[i])
479			fputs(comments[i], fp);
480
481		if (*kwds[i]) {
482			if (quote)
483				fprintf(fp, "%s = \"%s\"\n", kwds[i],
484				    sbuf_data(buf));
485			else
486				fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
487#if debugging
488			printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
489#endif
490		}
491	}
492	sbuf_delete(buf);
493	return (fclose(fp) != EOF);
494}
495