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