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