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