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