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