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