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