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