pw_conf.c revision 20253
120253Sjoerg/*-
220253Sjoerg * Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
320253Sjoerg * All rights reserved.
420253Sjoerg *
520253Sjoerg * Redistribution and use in source and binary forms, with or without
620253Sjoerg * modification, are permitted provided that the following conditions
720253Sjoerg * are met:
820253Sjoerg * 1. Redistributions of source code must retain the above copyright
920253Sjoerg *    notice, this list of conditions and the following disclaimer as
1020253Sjoerg *    the first lines of this file unmodified.
1120253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1220253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1320253Sjoerg *    documentation and/or other materials provided with the distribution.
1420253Sjoerg * 3. All advertising materials mentioning features or use of this software
1520253Sjoerg *    must display the following acknowledgement:
1620253Sjoerg *	This product includes software developed by David L. Nugent.
1720253Sjoerg * 4. The name of the author may not be used to endorse or promote products
1820253Sjoerg *    derived from this software without specific prior written permission.
1920253Sjoerg *
2020253Sjoerg * THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
2120253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2220253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2320253Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
2420253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2520253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2620253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2720253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2820253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2920253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3020253Sjoerg * SUCH DAMAGE.
3120253Sjoerg *
3220253Sjoerg *	$Id$
3320253Sjoerg */
3420253Sjoerg
3520253Sjoerg#include <string.h>
3620253Sjoerg#include <ctype.h>
3720253Sjoerg#include <fcntl.h>
3820253Sjoerg
3920253Sjoerg#include "pw.h"
4020253Sjoerg
4120253Sjoerg#define debugging 0
4220253Sjoerg
4320253Sjoergenum {
4420253Sjoerg	_UC_NONE,
4520253Sjoerg	_UC_DEFAULTPWD,
4620253Sjoerg	_UC_REUSEUID,
4720253Sjoerg	_UC_REUSEGID,
4820253Sjoerg	_UC_DOTDIR,
4920253Sjoerg	_UC_NEWMAIL,
5020253Sjoerg	_UC_LOGFILE,
5120253Sjoerg	_UC_HOMEROOT,
5220253Sjoerg	_UC_SHELLPATH,
5320253Sjoerg	_UC_SHELLS,
5420253Sjoerg	_UC_DEFAULTSHELL,
5520253Sjoerg	_UC_DEFAULTGROUP,
5620253Sjoerg	_UC_EXTRAGROUPS,
5720253Sjoerg	_UC_DEFAULTCLASS,
5820253Sjoerg	_UC_MINUID,
5920253Sjoerg	_UC_MAXUID,
6020253Sjoerg	_UC_MINGID,
6120253Sjoerg	_UC_MAXGID,
6220253Sjoerg	_UC_EXPIRE,
6320253Sjoerg	_UC_PASSWORD,
6420253Sjoerg	_UC_FIELDS
6520253Sjoerg};
6620253Sjoerg
6720253Sjoergstatic char     bourne_shell[] = "sh";
6820253Sjoerg
6920253Sjoergstatic char    *system_shells[_UC_MAXSHELLS] =
7020253Sjoerg{
7120253Sjoerg	bourne_shell,
7220253Sjoerg	"csh"
7320253Sjoerg};
7420253Sjoerg
7520253Sjoergstatic char    *default_groups[_UC_MAXGROUPS] =
7620253Sjoerg{
7720253Sjoerg	NULL
7820253Sjoerg};
7920253Sjoerg
8020253Sjoergstatic char const *booltrue[] =
8120253Sjoerg{
8220253Sjoerg	"yes", "true", "1", "on", NULL
8320253Sjoerg};
8420253Sjoergstatic char const *boolfalse[] =
8520253Sjoerg{
8620253Sjoerg	"no", "false", "0", "off", NULL
8720253Sjoerg};
8820253Sjoerg
8920253Sjoergstatic struct userconf config =
9020253Sjoerg{
9120253Sjoerg	0,			/* Default password for new users? (nologin) */
9220253Sjoerg	0,			/* Reuse uids? */
9320253Sjoerg	0,			/* Reuse gids? */
9420253Sjoerg	"/usr/share/skel",	/* Where to obtain skeleton files */
9520253Sjoerg	NULL,			/* Mail to send to new accounts */
9620253Sjoerg	"/var/log/userlog",	/* Where to log changes */
9720253Sjoerg	"/home",		/* Where to create home directory */
9820253Sjoerg	"/bin",			/* Where shells are located */
9920253Sjoerg	system_shells,		/* List of shells (first is default) */
10020253Sjoerg	bourne_shell,		/* Default shell */
10120253Sjoerg	NULL,			/* Default group name */
10220253Sjoerg	default_groups,		/* Default (additional) groups */
10320253Sjoerg	NULL,			/* Default login class */
10420253Sjoerg	1000, 32000,		/* Allowed range of uids */
10520253Sjoerg	1000, 32000,		/* Allowed range of gids */
10620253Sjoerg	0,			/* Days until account expires */
10720253Sjoerg	0			/* Days until password expires */
10820253Sjoerg};
10920253Sjoerg
11020253Sjoergstatic char const *comments[_UC_FIELDS] =
11120253Sjoerg{
11220253Sjoerg	"#\n# pw.conf - user/group configuration defaults\n#\n",
11320253Sjoerg	"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
11420253Sjoerg	"\n# Reuse gaps in uid sequence? (yes or no)\n",
11520253Sjoerg	"\n# Reuse gaps in gid sequence? (yes or no)\n",
11620253Sjoerg	"\n# Obtain default dotfiles from this directory\n",
11720253Sjoerg	"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
11820253Sjoerg	"\n# Log add/change/remove information in this file\n",
11920253Sjoerg	"\n# Root directory in which $HOME directory is created\n",
12020253Sjoerg	"\n# Colon separated list of directories containing valid shells\n",
12120253Sjoerg	"\n# Space separated list of available shells (without paths)\n",
12220253Sjoerg	"\n# Default shell (without path)\n",
12320253Sjoerg	"\n# Default group (leave blank for new group per user)\n",
12420253Sjoerg	"\n# Extra groups for new users\n",
12520253Sjoerg	"\n# Default login class for new users\n",
12620253Sjoerg	"\n# Range of valid default user ids\n",
12720253Sjoerg	NULL,
12820253Sjoerg	"\n# Range of valid default group ids\n",
12920253Sjoerg	NULL,
13020253Sjoerg	"\n# Days after which account expires (0=disabled)\n",
13120253Sjoerg	"\n# Days after which password expires (0=disabled)\n"
13220253Sjoerg};
13320253Sjoerg
13420253Sjoergstatic char const *kwds[] =
13520253Sjoerg{
13620253Sjoerg	"",
13720253Sjoerg	"defaultpasswd",
13820253Sjoerg	"reuseuids",
13920253Sjoerg	"reusegids",
14020253Sjoerg	"skeleton",
14120253Sjoerg	"newmail",
14220253Sjoerg	"logfile",
14320253Sjoerg	"home",
14420253Sjoerg	"shellpath",
14520253Sjoerg	"shells",
14620253Sjoerg	"defaultshell",
14720253Sjoerg	"defaultgroup",
14820253Sjoerg	"extragroups",
14920253Sjoerg	"defaultclass",
15020253Sjoerg	"minuid",
15120253Sjoerg	"maxuid",
15220253Sjoerg	"mingid",
15320253Sjoerg	"maxgid",
15420253Sjoerg	"expire_days",
15520253Sjoerg	"password_days",
15620253Sjoerg	NULL
15720253Sjoerg};
15820253Sjoerg
15920253Sjoergstatic char    *
16020253Sjoergunquote(char const * str)
16120253Sjoerg{
16220253Sjoerg	if (str && (*str == '"' || *str == '\'')) {
16320253Sjoerg		char           *p = strchr(str + 1, *str);
16420253Sjoerg
16520253Sjoerg		if (p != NULL)
16620253Sjoerg			*p = '\0';
16720253Sjoerg		return (char *) (*++str ? str : NULL);
16820253Sjoerg	}
16920253Sjoerg	return (char *) str;
17020253Sjoerg}
17120253Sjoerg
17220253Sjoergint
17320253Sjoergboolean_val(char const * str, int dflt)
17420253Sjoerg{
17520253Sjoerg	if ((str = unquote(str)) != NULL) {
17620253Sjoerg		int             i;
17720253Sjoerg
17820253Sjoerg		for (i = 0; booltrue[i]; i++)
17920253Sjoerg			if (strcmp(str, booltrue[i]) == 0)
18020253Sjoerg				return 1;
18120253Sjoerg		for (i = 0; boolfalse[i]; i++)
18220253Sjoerg			if (strcmp(str, boolfalse[i]) == 0)
18320253Sjoerg				return 0;
18420253Sjoerg
18520253Sjoerg		/*
18620253Sjoerg		 * Special cases for defaultpassword
18720253Sjoerg		 */
18820253Sjoerg		if (strcmp(str, "random") == 0)
18920253Sjoerg			return -1;
19020253Sjoerg		if (strcmp(str, "none") == 0)
19120253Sjoerg			return -2;
19220253Sjoerg	}
19320253Sjoerg	return dflt;
19420253Sjoerg}
19520253Sjoerg
19620253Sjoergchar const     *
19720253Sjoergboolean_str(int val)
19820253Sjoerg{
19920253Sjoerg	if (val == -1)
20020253Sjoerg		return "random";
20120253Sjoerg	else if (val == -2)
20220253Sjoerg		return "none";
20320253Sjoerg	else
20420253Sjoerg		return val ? booltrue[0] : boolfalse[0];
20520253Sjoerg}
20620253Sjoerg
20720253Sjoergchar           *
20820253Sjoergnewstr(char const * p)
20920253Sjoerg{
21020253Sjoerg	char           *q = NULL;
21120253Sjoerg
21220253Sjoerg	if ((p = unquote(p)) != NULL) {
21320253Sjoerg		int             l = strlen(p) + 1;
21420253Sjoerg
21520253Sjoerg		if ((q = malloc(l)) != NULL)
21620253Sjoerg			memcpy(q, p, l);
21720253Sjoerg	}
21820253Sjoerg	return q;
21920253Sjoerg}
22020253Sjoerg
22120253Sjoerg
22220253Sjoergstruct userconf *
22320253Sjoergread_userconfig(char const * file)
22420253Sjoerg{
22520253Sjoerg	FILE           *fp;
22620253Sjoerg
22720253Sjoerg	if (file == NULL)
22820253Sjoerg		file = _PATH_PW_CONF;
22920253Sjoerg	if ((fp = fopen(file, "r")) != NULL) {
23020253Sjoerg		char            buf[_UC_MAXLINE];
23120253Sjoerg
23220253Sjoerg		while (fgets(buf, sizeof buf, fp) != NULL) {
23320253Sjoerg			char           *p = strchr(buf, '\n');
23420253Sjoerg
23520253Sjoerg			if (p == NULL) {	/* Line too long */
23620253Sjoerg				int             ch;
23720253Sjoerg
23820253Sjoerg				while ((ch = fgetc(fp)) != '\n' && ch != EOF);
23920253Sjoerg			} else {
24020253Sjoerg				*p = '\0';
24120253Sjoerg				if (*buf && *buf != '\n' && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
24220253Sjoerg					static char const toks[] = " \t\r\n,=";
24320253Sjoerg					char           *q = strtok(NULL, toks);
24420253Sjoerg					int             i = 0;
24520253Sjoerg
24620253Sjoerg					while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
24720253Sjoerg						++i;
24820253Sjoerg#if debugging
24920253Sjoerg					if (i == _UC_FIELDS)
25020253Sjoerg						printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
25120253Sjoerg					else
25220253Sjoerg						printf("Got kwd[%s]=%s\n", p, q);
25320253Sjoerg#endif
25420253Sjoerg					switch (i) {
25520253Sjoerg					case _UC_DEFAULTPWD:
25620253Sjoerg						config.default_password = boolean_val(q, 1);
25720253Sjoerg						break;
25820253Sjoerg					case _UC_REUSEUID:
25920253Sjoerg						config.reuse_uids = boolean_val(q, 0);
26020253Sjoerg						break;
26120253Sjoerg					case _UC_REUSEGID:
26220253Sjoerg						config.reuse_gids = boolean_val(q, 0);
26320253Sjoerg						break;
26420253Sjoerg					case _UC_DOTDIR:
26520253Sjoerg						config.dotdir = (q == NULL || !boolean_val(q, 1))
26620253Sjoerg							? NULL : newstr(q);
26720253Sjoerg						break;
26820253Sjoerg					case _UC_NEWMAIL:
26920253Sjoerg						config.newmail = (q == NULL || !boolean_val(q, 1))
27020253Sjoerg							? NULL : newstr(q);
27120253Sjoerg						break;
27220253Sjoerg					case _UC_LOGFILE:
27320253Sjoerg						config.logfile = (q == NULL || !boolean_val(q, 1))
27420253Sjoerg							? NULL : newstr(q);
27520253Sjoerg						break;
27620253Sjoerg					case _UC_HOMEROOT:
27720253Sjoerg						config.home = (q == NULL || !boolean_val(q, 1))
27820253Sjoerg							? "/home" : newstr(q);
27920253Sjoerg						break;
28020253Sjoerg					case _UC_SHELLPATH:
28120253Sjoerg						config.shelldir = (q == NULL || !boolean_val(q, 1))
28220253Sjoerg							? "/bin" : newstr(q);
28320253Sjoerg						break;
28420253Sjoerg					case _UC_SHELLS:
28520253Sjoerg						for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
28620253Sjoerg							system_shells[i] = newstr(q);
28720253Sjoerg						if (i > 0)
28820253Sjoerg							while (i < _UC_MAXSHELLS)
28920253Sjoerg								system_shells[i++] = NULL;
29020253Sjoerg						break;
29120253Sjoerg					case _UC_DEFAULTSHELL:
29220253Sjoerg						config.shell_default = (q == NULL || !boolean_val(q, 1))
29320253Sjoerg							? (char *) bourne_shell : newstr(q);
29420253Sjoerg						break;
29520253Sjoerg					case _UC_DEFAULTGROUP:
29620253Sjoerg						config.default_group = (q == NULL || !boolean_val(q, 1) || getgrnam(q) == NULL)
29720253Sjoerg							? NULL : newstr(q);
29820253Sjoerg						break;
29920253Sjoerg					case _UC_EXTRAGROUPS:
30020253Sjoerg						for (i = 0; i < _UC_MAXGROUPS && q != NULL; i++, q = strtok(NULL, toks))
30120253Sjoerg							default_groups[i] = newstr(q);
30220253Sjoerg						if (i > 0)
30320253Sjoerg							while (i < _UC_MAXGROUPS)
30420253Sjoerg								default_groups[i++] = NULL;
30520253Sjoerg						break;
30620253Sjoerg					case _UC_DEFAULTCLASS:
30720253Sjoerg						config.default_class = (q == NULL || !boolean_val(q, 1))
30820253Sjoerg							? NULL : newstr(q);
30920253Sjoerg						break;
31020253Sjoerg					case _UC_MINUID:
31120253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
31220253Sjoerg							config.min_uid = (uid_t) atol(q);
31320253Sjoerg						break;
31420253Sjoerg					case _UC_MAXUID:
31520253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
31620253Sjoerg							config.max_uid = (uid_t) atol(q);
31720253Sjoerg						break;
31820253Sjoerg					case _UC_MINGID:
31920253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
32020253Sjoerg							config.min_gid = (gid_t) atol(q);
32120253Sjoerg						break;
32220253Sjoerg					case _UC_MAXGID:
32320253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
32420253Sjoerg							config.max_gid = (gid_t) atol(q);
32520253Sjoerg						break;
32620253Sjoerg					case _UC_EXPIRE:
32720253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
32820253Sjoerg							config.expire_days = atoi(q);
32920253Sjoerg						break;
33020253Sjoerg					case _UC_PASSWORD:
33120253Sjoerg						if ((q = unquote(q)) != NULL && isdigit(*q))
33220253Sjoerg							config.password_days = atoi(q);
33320253Sjoerg						break;
33420253Sjoerg					case _UC_FIELDS:
33520253Sjoerg					case _UC_NONE:
33620253Sjoerg						break;
33720253Sjoerg					}
33820253Sjoerg				}
33920253Sjoerg			}
34020253Sjoerg		}
34120253Sjoerg		fclose(fp);
34220253Sjoerg	}
34320253Sjoerg	return &config;
34420253Sjoerg}
34520253Sjoerg
34620253Sjoerg
34720253Sjoergint
34820253Sjoergwrite_userconfig(char const * file)
34920253Sjoerg{
35020253Sjoerg	int             fd;
35120253Sjoerg
35220253Sjoerg	if (file == NULL)
35320253Sjoerg		file = _PATH_PW_CONF;
35420253Sjoerg
35520253Sjoerg	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
35620253Sjoerg		FILE           *fp;
35720253Sjoerg
35820253Sjoerg		if ((fp = fdopen(fd, "w")) == NULL)
35920253Sjoerg			close(fd);
36020253Sjoerg		else {
36120253Sjoerg			int             i, j, k;
36220253Sjoerg			char            buf[_UC_MAXLINE];
36320253Sjoerg
36420253Sjoerg			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
36520253Sjoerg				int             quote = 1;
36620253Sjoerg				char const     *val = buf;
36720253Sjoerg
36820253Sjoerg				*buf = '\0';
36920253Sjoerg				switch (i) {
37020253Sjoerg				case _UC_DEFAULTPWD:
37120253Sjoerg					val = boolean_str(config.default_password);
37220253Sjoerg					break;
37320253Sjoerg				case _UC_REUSEUID:
37420253Sjoerg					val = boolean_str(config.reuse_uids);
37520253Sjoerg					break;
37620253Sjoerg				case _UC_REUSEGID:
37720253Sjoerg					val = boolean_str(config.reuse_gids);
37820253Sjoerg					break;
37920253Sjoerg				case _UC_DOTDIR:
38020253Sjoerg					val = config.dotdir ? config.dotdir : boolean_str(0);
38120253Sjoerg					break;
38220253Sjoerg				case _UC_NEWMAIL:
38320253Sjoerg					val = config.newmail ? config.newmail : boolean_str(0);
38420253Sjoerg					break;
38520253Sjoerg				case _UC_LOGFILE:
38620253Sjoerg					val = config.logfile ? config.logfile : boolean_str(0);
38720253Sjoerg					break;
38820253Sjoerg				case _UC_HOMEROOT:
38920253Sjoerg					val = config.home;
39020253Sjoerg					break;
39120253Sjoerg				case _UC_SHELLPATH:
39220253Sjoerg					val = config.shelldir;
39320253Sjoerg					break;
39420253Sjoerg				case _UC_SHELLS:
39520253Sjoerg					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++)
39620253Sjoerg						k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", system_shells[j]);
39720253Sjoerg					quote = 0;
39820253Sjoerg					break;
39920253Sjoerg				case _UC_DEFAULTSHELL:
40020253Sjoerg					val = config.shell_default ? config.shell_default : bourne_shell;
40120253Sjoerg					break;
40220253Sjoerg				case _UC_DEFAULTGROUP:
40320253Sjoerg					val = config.default_group ? config.default_group : "";
40420253Sjoerg					break;
40520253Sjoerg				case _UC_EXTRAGROUPS:
40620253Sjoerg					for (j = k = 0; j < _UC_MAXGROUPS && default_groups[j] != NULL; j++)
40720253Sjoerg						k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", default_groups[j]);
40820253Sjoerg					quote = 0;
40920253Sjoerg					break;
41020253Sjoerg				case _UC_DEFAULTCLASS:
41120253Sjoerg					val = config.default_class ? config.default_class : "";
41220253Sjoerg					break;
41320253Sjoerg				case _UC_MINUID:
41420253Sjoerg					sprintf(buf, "%lu", (unsigned long) config.min_uid);
41520253Sjoerg					quote = 0;
41620253Sjoerg					break;
41720253Sjoerg				case _UC_MAXUID:
41820253Sjoerg					sprintf(buf, "%lu", (unsigned long) config.max_uid);
41920253Sjoerg					quote = 0;
42020253Sjoerg					break;
42120253Sjoerg				case _UC_MINGID:
42220253Sjoerg					sprintf(buf, "%lu", (unsigned long) config.min_gid);
42320253Sjoerg					quote = 0;
42420253Sjoerg					break;
42520253Sjoerg				case _UC_MAXGID:
42620253Sjoerg					sprintf(buf, "%lu", (unsigned long) config.max_gid);
42720253Sjoerg					quote = 0;
42820253Sjoerg					break;
42920253Sjoerg				case _UC_EXPIRE:
43020253Sjoerg					sprintf(buf, "%d", config.expire_days);
43120253Sjoerg					quote = 0;
43220253Sjoerg					break;
43320253Sjoerg				case _UC_PASSWORD:
43420253Sjoerg					sprintf(buf, "%d", config.password_days);
43520253Sjoerg					quote = 0;
43620253Sjoerg					break;
43720253Sjoerg				case _UC_NONE:
43820253Sjoerg					break;
43920253Sjoerg				}
44020253Sjoerg
44120253Sjoerg				if (comments[i])
44220253Sjoerg					fputs(comments[i], fp);
44320253Sjoerg
44420253Sjoerg				if (*kwds[i]) {
44520253Sjoerg					if (quote)
44620253Sjoerg						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
44720253Sjoerg					else
44820253Sjoerg						fprintf(fp, "%s = %s\n", kwds[i], val);
44920253Sjoerg#if debugging
45020253Sjoerg					printf("WROTE: %s = %s\n", kwds[i], val);
45120253Sjoerg#endif
45220253Sjoerg				}
45320253Sjoerg			}
45420253Sjoerg			return fclose(fp) != EOF;
45520253Sjoerg		}
45620253Sjoerg	}
45720253Sjoerg	return 0;
45820253Sjoerg}
459