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