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