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 == P_NO) 225 return (boolfalse[0]); 226 else if (val == P_RANDOM) 227 return ("random"); 228 else if (val == P_NONE) 229 return ("none"); 230 else 231 return (booltrue[0]); 232 } 233 234 char * 235 newstr(char const * p) 236 { 237 char *q; 238 239 if ((p = unquote(p)) == NULL) 240 return (NULL); 241 242 if ((q = strdup(p)) == NULL) 243 err(1, "strdup()"); 244 245 return (q); 246 } 247 248 struct userconf * 249 read_userconfig(char const * file) 250 { 251 FILE *fp; 252 char *buf, *p; 253 const char *errstr; 254 size_t linecap; 255 ssize_t linelen; 256 257 buf = NULL; 258 linecap = 0; 259 260 if ((fp = fopen(file, "r")) == NULL) 261 return (&config); 262 263 while ((linelen = getline(&buf, &linecap, fp)) > 0) { 264 if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') { 265 static char const toks[] = " \t\r\n,="; 266 char *q = strtok(NULL, toks); 267 int i = 0; 268 mode_t *modeset; 269 270 while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0) 271 ++i; 272 #if debugging 273 if (i == _UC_FIELDS) 274 printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : ""); 275 else 276 printf("Got kwd[%s]=%s\n", p, q); 277 #endif 278 switch (i) { 279 case _UC_DEFAULTPWD: 280 config.default_password = passwd_val(q, 1); 281 break; 282 case _UC_REUSEUID: 283 config.reuse_uids = boolean_val(q, 0); 284 break; 285 case _UC_REUSEGID: 286 config.reuse_gids = boolean_val(q, 0); 287 break; 288 case _UC_NISPASSWD: 289 config.nispasswd = (q == NULL || !boolean_val(q, 1)) 290 ? NULL : newstr(q); 291 break; 292 case _UC_DOTDIR: 293 config.dotdir = (q == NULL || !boolean_val(q, 1)) 294 ? NULL : newstr(q); 295 break; 296 case _UC_NEWMAIL: 297 config.newmail = (q == NULL || !boolean_val(q, 1)) 298 ? NULL : newstr(q); 299 break; 300 case _UC_LOGFILE: 301 config.logfile = (q == NULL || !boolean_val(q, 1)) 302 ? NULL : newstr(q); 303 break; 304 case _UC_HOMEROOT: 305 config.home = (q == NULL || !boolean_val(q, 1)) 306 ? "/home" : newstr(q); 307 break; 308 case _UC_HOMEMODE: 309 modeset = setmode(q); 310 config.homemode = (q == NULL || !boolean_val(q, 1)) 311 ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE); 312 free(modeset); 313 break; 314 case _UC_SHELLPATH: 315 config.shelldir = (q == NULL || !boolean_val(q, 1)) 316 ? "/bin" : newstr(q); 317 break; 318 case _UC_SHELLS: 319 for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks)) 320 system_shells[i] = newstr(q); 321 if (i > 0) 322 while (i < _UC_MAXSHELLS) 323 system_shells[i++] = NULL; 324 break; 325 case _UC_DEFAULTSHELL: 326 config.shell_default = (q == NULL || !boolean_val(q, 1)) 327 ? (char *) bourne_shell : newstr(q); 328 break; 329 case _UC_DEFAULTGROUP: 330 q = unquote(q); 331 config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL) 332 ? NULL : newstr(q); 333 break; 334 case _UC_EXTRAGROUPS: 335 while ((q = strtok(NULL, toks)) != NULL) { 336 if (config.groups == NULL) 337 config.groups = sl_init(); 338 sl_add(config.groups, newstr(q)); 339 } 340 break; 341 case _UC_DEFAULTCLASS: 342 config.default_class = (q == NULL || !boolean_val(q, 1)) 343 ? NULL : newstr(q); 344 break; 345 case _UC_MINUID: 346 if ((q = unquote(q)) != NULL) { 347 config.min_uid = strtounum(q, 0, 348 UID_MAX, &errstr); 349 if (errstr) 350 warnx("Invalid min_uid: '%s';" 351 " ignoring", q); 352 } 353 break; 354 case _UC_MAXUID: 355 if ((q = unquote(q)) != NULL) { 356 config.max_uid = strtounum(q, 0, 357 UID_MAX, &errstr); 358 if (errstr) 359 warnx("Invalid max_uid: '%s';" 360 " ignoring", q); 361 } 362 break; 363 case _UC_MINGID: 364 if ((q = unquote(q)) != NULL) { 365 config.min_gid = strtounum(q, 0, 366 GID_MAX, &errstr); 367 if (errstr) 368 warnx("Invalid min_gid: '%s';" 369 " ignoring", q); 370 } 371 break; 372 case _UC_MAXGID: 373 if ((q = unquote(q)) != NULL) { 374 config.max_gid = strtounum(q, 0, 375 GID_MAX, &errstr); 376 if (errstr) 377 warnx("Invalid max_gid: '%s';" 378 " ignoring", q); 379 } 380 break; 381 case _UC_EXPIRE: 382 if ((q = unquote(q)) != NULL) { 383 config.expire_days = strtonum(q, 0, 384 INT_MAX, &errstr); 385 if (errstr) 386 warnx("Invalid expire days:" 387 " '%s'; ignoring", q); 388 } 389 break; 390 case _UC_PASSWORD: 391 if ((q = unquote(q)) != NULL) { 392 config.password_days = strtonum(q, 0, 393 INT_MAX, &errstr); 394 if (errstr) 395 warnx("Invalid password days:" 396 " '%s'; ignoring", q); 397 } 398 break; 399 case _UC_FIELDS: 400 case _UC_NONE: 401 break; 402 } 403 } 404 } 405 free(buf); 406 fclose(fp); 407 408 return (&config); 409 } 410 411 412 int 413 write_userconfig(struct userconf *cnf, const char *file) 414 { 415 int fd; 416 int i, j; 417 struct sbuf *buf; 418 FILE *fp; 419 char cfgfile[MAXPATHLEN]; 420 421 if (file == NULL) { 422 snprintf(cfgfile, sizeof(cfgfile), "%s/" _PW_CONF, 423 conf.etcpath); 424 file = cfgfile; 425 } 426 427 if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1) 428 return (0); 429 430 if ((fp = fdopen(fd, "w")) == NULL) { 431 close(fd); 432 return (0); 433 } 434 435 buf = sbuf_new_auto(); 436 for (i = _UC_NONE; i < _UC_FIELDS; i++) { 437 int quote = 1; 438 439 sbuf_clear(buf); 440 switch (i) { 441 case _UC_DEFAULTPWD: 442 sbuf_cat(buf, boolean_str(cnf->default_password)); 443 break; 444 case _UC_REUSEUID: 445 sbuf_cat(buf, boolean_str(cnf->reuse_uids)); 446 break; 447 case _UC_REUSEGID: 448 sbuf_cat(buf, boolean_str(cnf->reuse_gids)); 449 break; 450 case _UC_NISPASSWD: 451 sbuf_cat(buf, cnf->nispasswd ? cnf->nispasswd : ""); 452 quote = 0; 453 break; 454 case _UC_DOTDIR: 455 sbuf_cat(buf, cnf->dotdir ? cnf->dotdir : 456 boolean_str(0)); 457 break; 458 case _UC_NEWMAIL: 459 sbuf_cat(buf, cnf->newmail ? cnf->newmail : 460 boolean_str(0)); 461 break; 462 case _UC_LOGFILE: 463 sbuf_cat(buf, cnf->logfile ? cnf->logfile : 464 boolean_str(0)); 465 break; 466 case _UC_HOMEROOT: 467 sbuf_cat(buf, cnf->home); 468 break; 469 case _UC_HOMEMODE: 470 sbuf_printf(buf, "%04o", cnf->homemode); 471 quote = 0; 472 break; 473 case _UC_SHELLPATH: 474 sbuf_cat(buf, cnf->shelldir); 475 break; 476 case _UC_SHELLS: 477 for (j = 0; j < _UC_MAXSHELLS && 478 system_shells[j] != NULL; j++) 479 sbuf_printf(buf, "%s\"%s\"", j ? 480 "," : "", system_shells[j]); 481 quote = 0; 482 break; 483 case _UC_DEFAULTSHELL: 484 sbuf_cat(buf, cnf->shell_default ? 485 cnf->shell_default : bourne_shell); 486 break; 487 case _UC_DEFAULTGROUP: 488 sbuf_cat(buf, cnf->default_group ? 489 cnf->default_group : ""); 490 break; 491 case _UC_EXTRAGROUPS: 492 for (j = 0; cnf->groups != NULL && 493 j < (int)cnf->groups->sl_cur; j++) 494 sbuf_printf(buf, "%s\"%s\"", j ? 495 "," : "", cnf->groups->sl_str[j]); 496 quote = 0; 497 break; 498 case _UC_DEFAULTCLASS: 499 sbuf_cat(buf, cnf->default_class ? 500 cnf->default_class : ""); 501 break; 502 case _UC_MINUID: 503 sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid); 504 quote = 0; 505 break; 506 case _UC_MAXUID: 507 sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid); 508 quote = 0; 509 break; 510 case _UC_MINGID: 511 sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid); 512 quote = 0; 513 break; 514 case _UC_MAXGID: 515 sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid); 516 quote = 0; 517 break; 518 case _UC_EXPIRE: 519 sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days); 520 quote = 0; 521 break; 522 case _UC_PASSWORD: 523 sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days); 524 quote = 0; 525 break; 526 case _UC_NONE: 527 break; 528 } 529 sbuf_finish(buf); 530 531 if (comments[i]) 532 fputs(comments[i], fp); 533 534 if (*kwds[i]) { 535 if (quote) 536 fprintf(fp, "%s = \"%s\"\n", kwds[i], 537 sbuf_data(buf)); 538 else 539 fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf)); 540 #if debugging 541 printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf)); 542 #endif 543 } 544 } 545 sbuf_delete(buf); 546 return (fclose(fp) != EOF); 547 } 548