1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (C) 1996
5 *	David L. Nugent.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#ifndef lint
30static const char rcsid[] =
31  "$FreeBSD$";
32#endif /* not lint */
33
34#include <err.h>
35#include <fcntl.h>
36#include <string.h>
37#include <unistd.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};
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	return dflt;
190}
191
192int
193passwd_val(char const * str, int dflt)
194{
195	if ((str = unquote(str)) != NULL) {
196		int             i;
197
198		for (i = 0; booltrue[i]; i++)
199			if (strcmp(str, booltrue[i]) == 0)
200				return P_YES;
201		for (i = 0; boolfalse[i]; i++)
202			if (strcmp(str, boolfalse[i]) == 0)
203				return P_NO;
204
205		/*
206		 * Special cases for defaultpassword
207		 */
208		if (strcmp(str, "random") == 0)
209			return P_RANDOM;
210		if (strcmp(str, "none") == 0)
211			return P_NONE;
212
213		errx(1, "Invalid value for default password");
214	}
215	return dflt;
216}
217
218char const     *
219boolean_str(int val)
220{
221	if (val == -1)
222		return "random";
223	else if (val == -2)
224		return "none";
225	else
226		return val ? booltrue[0] : boolfalse[0];
227}
228
229char           *
230newstr(char const * p)
231{
232	char	*q;
233
234	if ((p = unquote(p)) == NULL)
235		return (NULL);
236
237	if ((q = strdup(p)) == NULL)
238		err(1, "strdup()");
239
240	return (q);
241}
242
243struct userconf *
244read_userconfig(char const * file)
245{
246	FILE	*fp;
247	char	*buf, *p;
248	const char *errstr;
249	size_t	linecap;
250	ssize_t	linelen;
251
252	buf = NULL;
253	linecap = 0;
254
255	if ((fp = fopen(file, "r")) == NULL)
256		return (&config);
257
258	while ((linelen = getline(&buf, &linecap, fp)) > 0) {
259		if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
260			static char const toks[] = " \t\r\n,=";
261			char           *q = strtok(NULL, toks);
262			int             i = 0;
263			mode_t          *modeset;
264
265			while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
266				++i;
267#if debugging
268			if (i == _UC_FIELDS)
269				printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
270			else
271				printf("Got kwd[%s]=%s\n", p, q);
272#endif
273			switch (i) {
274			case _UC_DEFAULTPWD:
275				config.default_password = passwd_val(q, 1);
276				break;
277			case _UC_REUSEUID:
278				config.reuse_uids = boolean_val(q, 0);
279				break;
280			case _UC_REUSEGID:
281				config.reuse_gids = boolean_val(q, 0);
282				break;
283			case _UC_NISPASSWD:
284				config.nispasswd = (q == NULL || !boolean_val(q, 1))
285					? NULL : newstr(q);
286				break;
287			case _UC_DOTDIR:
288				config.dotdir = (q == NULL || !boolean_val(q, 1))
289					? NULL : newstr(q);
290				break;
291				case _UC_NEWMAIL:
292				config.newmail = (q == NULL || !boolean_val(q, 1))
293					? NULL : newstr(q);
294				break;
295			case _UC_LOGFILE:
296				config.logfile = (q == NULL || !boolean_val(q, 1))
297					? NULL : newstr(q);
298				break;
299			case _UC_HOMEROOT:
300				config.home = (q == NULL || !boolean_val(q, 1))
301					? "/home" : newstr(q);
302				break;
303			case _UC_HOMEMODE:
304				modeset = setmode(q);
305				config.homemode = (q == NULL || !boolean_val(q, 1))
306					? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
307				free(modeset);
308				break;
309			case _UC_SHELLPATH:
310				config.shelldir = (q == NULL || !boolean_val(q, 1))
311					? "/bin" : newstr(q);
312				break;
313			case _UC_SHELLS:
314				for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
315					system_shells[i] = newstr(q);
316				if (i > 0)
317					while (i < _UC_MAXSHELLS)
318						system_shells[i++] = NULL;
319				break;
320			case _UC_DEFAULTSHELL:
321				config.shell_default = (q == NULL || !boolean_val(q, 1))
322					? (char *) bourne_shell : newstr(q);
323				break;
324			case _UC_DEFAULTGROUP:
325				q = unquote(q);
326				config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
327					? NULL : newstr(q);
328				break;
329			case _UC_EXTRAGROUPS:
330				while ((q = strtok(NULL, toks)) != NULL) {
331					if (config.groups == NULL)
332						config.groups = sl_init();
333					sl_add(config.groups, newstr(q));
334				}
335				break;
336			case _UC_DEFAULTCLASS:
337				config.default_class = (q == NULL || !boolean_val(q, 1))
338					? NULL : newstr(q);
339				break;
340			case _UC_MINUID:
341				if ((q = unquote(q)) != NULL) {
342					config.min_uid = strtounum(q, 0,
343					    UID_MAX, &errstr);
344					if (errstr)
345						warnx("Invalid min_uid: '%s';"
346						    " ignoring", q);
347				}
348				break;
349			case _UC_MAXUID:
350				if ((q = unquote(q)) != NULL) {
351					config.max_uid = strtounum(q, 0,
352					    UID_MAX, &errstr);
353					if (errstr)
354						warnx("Invalid max_uid: '%s';"
355						    " ignoring", q);
356				}
357				break;
358			case _UC_MINGID:
359				if ((q = unquote(q)) != NULL) {
360					config.min_gid = strtounum(q, 0,
361					    GID_MAX, &errstr);
362					if (errstr)
363						warnx("Invalid min_gid: '%s';"
364						    " ignoring", q);
365				}
366				break;
367			case _UC_MAXGID:
368				if ((q = unquote(q)) != NULL) {
369					config.max_gid = strtounum(q, 0,
370					    GID_MAX, &errstr);
371					if (errstr)
372						warnx("Invalid max_gid: '%s';"
373						    " ignoring", q);
374				}
375				break;
376			case _UC_EXPIRE:
377				if ((q = unquote(q)) != NULL) {
378					config.expire_days = strtonum(q, 0,
379					    INT_MAX, &errstr);
380					if (errstr)
381						warnx("Invalid expire days:"
382						    " '%s'; ignoring", q);
383				}
384				break;
385			case _UC_PASSWORD:
386				if ((q = unquote(q)) != NULL) {
387					config.password_days = strtonum(q, 0,
388					    INT_MAX, &errstr);
389					if (errstr)
390						warnx("Invalid password days:"
391						    " '%s'; ignoring", q);
392				}
393				break;
394			case _UC_FIELDS:
395			case _UC_NONE:
396				break;
397			}
398		}
399	}
400	free(buf);
401	fclose(fp);
402
403	return (&config);
404}
405
406
407int
408write_userconfig(struct userconf *cnf, const char *file)
409{
410	int             fd;
411	int             i, j;
412	FILE           *buffp;
413	FILE           *fp;
414	char		cfgfile[MAXPATHLEN];
415	char           *buf;
416	size_t          sz;
417
418	if (file == NULL) {
419		snprintf(cfgfile, sizeof(cfgfile), "%s/" _PW_CONF,
420		    conf.etcpath);
421		file = cfgfile;
422	}
423
424	if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
425		return (0);
426
427	if ((fp = fdopen(fd, "w")) == NULL) {
428		close(fd);
429		return (0);
430	}
431
432	sz = 0;
433	buf = NULL;
434	buffp = open_memstream(&buf, &sz);
435	if (buffp == NULL)
436		err(EXIT_FAILURE, "open_memstream()");
437
438	for (i = _UC_NONE; i < _UC_FIELDS; i++) {
439		int             quote = 1;
440
441		if (buf != NULL)
442			memset(buf, 0, sz);
443		rewind(buffp);
444		switch (i) {
445		case _UC_DEFAULTPWD:
446			fputs(boolean_str(cnf->default_password), buffp);
447			break;
448		case _UC_REUSEUID:
449			fputs(boolean_str(cnf->reuse_uids), buffp);
450			break;
451		case _UC_REUSEGID:
452			fputs(boolean_str(cnf->reuse_gids), buffp);
453			break;
454		case _UC_NISPASSWD:
455			fputs(cnf->nispasswd ?  cnf->nispasswd : "", buffp);
456			quote = 0;
457			break;
458		case _UC_DOTDIR:
459			fputs(cnf->dotdir ?  cnf->dotdir : boolean_str(0),
460			    buffp);
461			break;
462		case _UC_NEWMAIL:
463			fputs(cnf->newmail ?  cnf->newmail : boolean_str(0),
464			    buffp);
465			break;
466		case _UC_LOGFILE:
467			fputs(cnf->logfile ?  cnf->logfile : boolean_str(0),
468			    buffp);
469			break;
470		case _UC_HOMEROOT:
471			fputs(cnf->home, buffp);
472			break;
473		case _UC_HOMEMODE:
474			fprintf(buffp, "%04o", cnf->homemode);
475			quote = 0;
476			break;
477		case _UC_SHELLPATH:
478			fputs(cnf->shelldir, buffp);
479			break;
480		case _UC_SHELLS:
481			for (j = 0; j < _UC_MAXSHELLS &&
482			    system_shells[j] != NULL; j++)
483				fprintf(buffp, "%s\"%s\"", j ?
484				    "," : "", system_shells[j]);
485			quote = 0;
486			break;
487		case _UC_DEFAULTSHELL:
488			fputs(cnf->shell_default ?  cnf->shell_default :
489			    bourne_shell, buffp);
490			break;
491		case _UC_DEFAULTGROUP:
492			fputs(cnf->default_group ?  cnf->default_group : "",
493			    buffp);
494			break;
495		case _UC_EXTRAGROUPS:
496			for (j = 0; cnf->groups != NULL &&
497			    j < (int)cnf->groups->sl_cur; j++)
498				fprintf(buffp, "%s\"%s\"", j ?
499				    "," : "", cnf->groups->sl_str[j]);
500			quote = 0;
501			break;
502		case _UC_DEFAULTCLASS:
503			fputs(cnf->default_class ?  cnf->default_class : "",
504			    buffp);
505			break;
506		case _UC_MINUID:
507			fprintf(buffp, "%ju", (uintmax_t)cnf->min_uid);
508			quote = 0;
509			break;
510		case _UC_MAXUID:
511			fprintf(buffp, "%ju", (uintmax_t)cnf->max_uid);
512			quote = 0;
513			break;
514		case _UC_MINGID:
515			fprintf(buffp, "%ju", (uintmax_t)cnf->min_gid);
516			quote = 0;
517			break;
518		case _UC_MAXGID:
519			fprintf(buffp, "%ju", (uintmax_t)cnf->max_gid);
520			quote = 0;
521			break;
522		case _UC_EXPIRE:
523			fprintf(buffp, "%jd", (intmax_t)cnf->expire_days);
524			quote = 0;
525			break;
526		case _UC_PASSWORD:
527			fprintf(buffp, "%jd", (intmax_t)cnf->password_days);
528			quote = 0;
529			break;
530		case _UC_NONE:
531			break;
532		}
533		fflush(buffp);
534
535		if (comments[i])
536			fputs(comments[i], fp);
537
538		if (*kwds[i]) {
539			if (quote)
540				fprintf(fp, "%s = \"%s\"\n", kwds[i], buf);
541			else
542				fprintf(fp, "%s = %s\n", kwds[i], buf);
543#if debugging
544			printf("WROTE: %s = %s\n", kwds[i], buf);
545#endif
546		}
547	}
548	fclose(buffp);
549	free(buf);
550	return (fclose(fp) != EOF);
551}
552