pw_conf.c revision 20253
1/*-
2 * Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
3 * 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 as
10 *    the first lines of this file unmodified.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 *    must display the following acknowledgement:
16 *	This product includes software developed by David L. Nugent.
17 * 4. The name of the author may not be used to endorse or promote products
18 *    derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 *	$Id$
33 */
34
35#include <string.h>
36#include <ctype.h>
37#include <fcntl.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_DOTDIR,
49	_UC_NEWMAIL,
50	_UC_LOGFILE,
51	_UC_HOMEROOT,
52	_UC_SHELLPATH,
53	_UC_SHELLS,
54	_UC_DEFAULTSHELL,
55	_UC_DEFAULTGROUP,
56	_UC_EXTRAGROUPS,
57	_UC_DEFAULTCLASS,
58	_UC_MINUID,
59	_UC_MAXUID,
60	_UC_MINGID,
61	_UC_MAXGID,
62	_UC_EXPIRE,
63	_UC_PASSWORD,
64	_UC_FIELDS
65};
66
67static char     bourne_shell[] = "sh";
68
69static char    *system_shells[_UC_MAXSHELLS] =
70{
71	bourne_shell,
72	"csh"
73};
74
75static char    *default_groups[_UC_MAXGROUPS] =
76{
77	NULL
78};
79
80static char const *booltrue[] =
81{
82	"yes", "true", "1", "on", NULL
83};
84static char const *boolfalse[] =
85{
86	"no", "false", "0", "off", NULL
87};
88
89static struct userconf config =
90{
91	0,			/* Default password for new users? (nologin) */
92	0,			/* Reuse uids? */
93	0,			/* Reuse gids? */
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	"/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	default_groups,		/* 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# Obtain default dotfiles from this directory\n",
117	"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
118	"\n# Log add/change/remove information in this file\n",
119	"\n# Root directory in which $HOME directory is created\n",
120	"\n# Colon separated list of directories containing valid shells\n",
121	"\n# Space separated list of available shells (without paths)\n",
122	"\n# Default shell (without path)\n",
123	"\n# Default group (leave blank for new group per user)\n",
124	"\n# Extra groups for new users\n",
125	"\n# Default login class for new users\n",
126	"\n# Range of valid default user ids\n",
127	NULL,
128	"\n# Range of valid default group ids\n",
129	NULL,
130	"\n# Days after which account expires (0=disabled)\n",
131	"\n# Days after which password expires (0=disabled)\n"
132};
133
134static char const *kwds[] =
135{
136	"",
137	"defaultpasswd",
138	"reuseuids",
139	"reusegids",
140	"skeleton",
141	"newmail",
142	"logfile",
143	"home",
144	"shellpath",
145	"shells",
146	"defaultshell",
147	"defaultgroup",
148	"extragroups",
149	"defaultclass",
150	"minuid",
151	"maxuid",
152	"mingid",
153	"maxgid",
154	"expire_days",
155	"password_days",
156	NULL
157};
158
159static char    *
160unquote(char const * str)
161{
162	if (str && (*str == '"' || *str == '\'')) {
163		char           *p = strchr(str + 1, *str);
164
165		if (p != NULL)
166			*p = '\0';
167		return (char *) (*++str ? str : NULL);
168	}
169	return (char *) str;
170}
171
172int
173boolean_val(char const * str, int dflt)
174{
175	if ((str = unquote(str)) != NULL) {
176		int             i;
177
178		for (i = 0; booltrue[i]; i++)
179			if (strcmp(str, booltrue[i]) == 0)
180				return 1;
181		for (i = 0; boolfalse[i]; i++)
182			if (strcmp(str, boolfalse[i]) == 0)
183				return 0;
184
185		/*
186		 * Special cases for defaultpassword
187		 */
188		if (strcmp(str, "random") == 0)
189			return -1;
190		if (strcmp(str, "none") == 0)
191			return -2;
192	}
193	return dflt;
194}
195
196char const     *
197boolean_str(int val)
198{
199	if (val == -1)
200		return "random";
201	else if (val == -2)
202		return "none";
203	else
204		return val ? booltrue[0] : boolfalse[0];
205}
206
207char           *
208newstr(char const * p)
209{
210	char           *q = NULL;
211
212	if ((p = unquote(p)) != NULL) {
213		int             l = strlen(p) + 1;
214
215		if ((q = malloc(l)) != NULL)
216			memcpy(q, p, l);
217	}
218	return q;
219}
220
221
222struct userconf *
223read_userconfig(char const * file)
224{
225	FILE           *fp;
226
227	if (file == NULL)
228		file = _PATH_PW_CONF;
229	if ((fp = fopen(file, "r")) != NULL) {
230		char            buf[_UC_MAXLINE];
231
232		while (fgets(buf, sizeof buf, fp) != NULL) {
233			char           *p = strchr(buf, '\n');
234
235			if (p == NULL) {	/* Line too long */
236				int             ch;
237
238				while ((ch = fgetc(fp)) != '\n' && ch != EOF);
239			} else {
240				*p = '\0';
241				if (*buf && *buf != '\n' && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
242					static char const toks[] = " \t\r\n,=";
243					char           *q = strtok(NULL, toks);
244					int             i = 0;
245
246					while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
247						++i;
248#if debugging
249					if (i == _UC_FIELDS)
250						printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
251					else
252						printf("Got kwd[%s]=%s\n", p, q);
253#endif
254					switch (i) {
255					case _UC_DEFAULTPWD:
256						config.default_password = boolean_val(q, 1);
257						break;
258					case _UC_REUSEUID:
259						config.reuse_uids = boolean_val(q, 0);
260						break;
261					case _UC_REUSEGID:
262						config.reuse_gids = boolean_val(q, 0);
263						break;
264					case _UC_DOTDIR:
265						config.dotdir = (q == NULL || !boolean_val(q, 1))
266							? NULL : newstr(q);
267						break;
268					case _UC_NEWMAIL:
269						config.newmail = (q == NULL || !boolean_val(q, 1))
270							? NULL : newstr(q);
271						break;
272					case _UC_LOGFILE:
273						config.logfile = (q == NULL || !boolean_val(q, 1))
274							? NULL : newstr(q);
275						break;
276					case _UC_HOMEROOT:
277						config.home = (q == NULL || !boolean_val(q, 1))
278							? "/home" : newstr(q);
279						break;
280					case _UC_SHELLPATH:
281						config.shelldir = (q == NULL || !boolean_val(q, 1))
282							? "/bin" : newstr(q);
283						break;
284					case _UC_SHELLS:
285						for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
286							system_shells[i] = newstr(q);
287						if (i > 0)
288							while (i < _UC_MAXSHELLS)
289								system_shells[i++] = NULL;
290						break;
291					case _UC_DEFAULTSHELL:
292						config.shell_default = (q == NULL || !boolean_val(q, 1))
293							? (char *) bourne_shell : newstr(q);
294						break;
295					case _UC_DEFAULTGROUP:
296						config.default_group = (q == NULL || !boolean_val(q, 1) || getgrnam(q) == NULL)
297							? NULL : newstr(q);
298						break;
299					case _UC_EXTRAGROUPS:
300						for (i = 0; i < _UC_MAXGROUPS && q != NULL; i++, q = strtok(NULL, toks))
301							default_groups[i] = newstr(q);
302						if (i > 0)
303							while (i < _UC_MAXGROUPS)
304								default_groups[i++] = NULL;
305						break;
306					case _UC_DEFAULTCLASS:
307						config.default_class = (q == NULL || !boolean_val(q, 1))
308							? NULL : newstr(q);
309						break;
310					case _UC_MINUID:
311						if ((q = unquote(q)) != NULL && isdigit(*q))
312							config.min_uid = (uid_t) atol(q);
313						break;
314					case _UC_MAXUID:
315						if ((q = unquote(q)) != NULL && isdigit(*q))
316							config.max_uid = (uid_t) atol(q);
317						break;
318					case _UC_MINGID:
319						if ((q = unquote(q)) != NULL && isdigit(*q))
320							config.min_gid = (gid_t) atol(q);
321						break;
322					case _UC_MAXGID:
323						if ((q = unquote(q)) != NULL && isdigit(*q))
324							config.max_gid = (gid_t) atol(q);
325						break;
326					case _UC_EXPIRE:
327						if ((q = unquote(q)) != NULL && isdigit(*q))
328							config.expire_days = atoi(q);
329						break;
330					case _UC_PASSWORD:
331						if ((q = unquote(q)) != NULL && isdigit(*q))
332							config.password_days = atoi(q);
333						break;
334					case _UC_FIELDS:
335					case _UC_NONE:
336						break;
337					}
338				}
339			}
340		}
341		fclose(fp);
342	}
343	return &config;
344}
345
346
347int
348write_userconfig(char const * file)
349{
350	int             fd;
351
352	if (file == NULL)
353		file = _PATH_PW_CONF;
354
355	if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
356		FILE           *fp;
357
358		if ((fp = fdopen(fd, "w")) == NULL)
359			close(fd);
360		else {
361			int             i, j, k;
362			char            buf[_UC_MAXLINE];
363
364			for (i = _UC_NONE; i < _UC_FIELDS; i++) {
365				int             quote = 1;
366				char const     *val = buf;
367
368				*buf = '\0';
369				switch (i) {
370				case _UC_DEFAULTPWD:
371					val = boolean_str(config.default_password);
372					break;
373				case _UC_REUSEUID:
374					val = boolean_str(config.reuse_uids);
375					break;
376				case _UC_REUSEGID:
377					val = boolean_str(config.reuse_gids);
378					break;
379				case _UC_DOTDIR:
380					val = config.dotdir ? config.dotdir : boolean_str(0);
381					break;
382				case _UC_NEWMAIL:
383					val = config.newmail ? config.newmail : boolean_str(0);
384					break;
385				case _UC_LOGFILE:
386					val = config.logfile ? config.logfile : boolean_str(0);
387					break;
388				case _UC_HOMEROOT:
389					val = config.home;
390					break;
391				case _UC_SHELLPATH:
392					val = config.shelldir;
393					break;
394				case _UC_SHELLS:
395					for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++)
396						k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", system_shells[j]);
397					quote = 0;
398					break;
399				case _UC_DEFAULTSHELL:
400					val = config.shell_default ? config.shell_default : bourne_shell;
401					break;
402				case _UC_DEFAULTGROUP:
403					val = config.default_group ? config.default_group : "";
404					break;
405				case _UC_EXTRAGROUPS:
406					for (j = k = 0; j < _UC_MAXGROUPS && default_groups[j] != NULL; j++)
407						k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", default_groups[j]);
408					quote = 0;
409					break;
410				case _UC_DEFAULTCLASS:
411					val = config.default_class ? config.default_class : "";
412					break;
413				case _UC_MINUID:
414					sprintf(buf, "%lu", (unsigned long) config.min_uid);
415					quote = 0;
416					break;
417				case _UC_MAXUID:
418					sprintf(buf, "%lu", (unsigned long) config.max_uid);
419					quote = 0;
420					break;
421				case _UC_MINGID:
422					sprintf(buf, "%lu", (unsigned long) config.min_gid);
423					quote = 0;
424					break;
425				case _UC_MAXGID:
426					sprintf(buf, "%lu", (unsigned long) config.max_gid);
427					quote = 0;
428					break;
429				case _UC_EXPIRE:
430					sprintf(buf, "%d", config.expire_days);
431					quote = 0;
432					break;
433				case _UC_PASSWORD:
434					sprintf(buf, "%d", config.password_days);
435					quote = 0;
436					break;
437				case _UC_NONE:
438					break;
439				}
440
441				if (comments[i])
442					fputs(comments[i], fp);
443
444				if (*kwds[i]) {
445					if (quote)
446						fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
447					else
448						fprintf(fp, "%s = %s\n", kwds[i], val);
449#if debugging
450					printf("WROTE: %s = %s\n", kwds[i], val);
451#endif
452				}
453			}
454			return fclose(fp) != EOF;
455		}
456	}
457	return 0;
458}
459