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