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