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