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