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