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