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 _DEF_DIRMODE, /* 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 char *buf, *p; 231 size_t linecap; 232 ssize_t linelen; 233 234 buf = NULL; 235 linecap = 0; 236 237 extendarray(&config.groups, &config.numgroups, 200); 238 memset(config.groups, 0, config.numgroups * sizeof(char *)); 239 if (file == NULL) 240 file = _PATH_PW_CONF; 241 242 if ((fp = fopen(file, "r")) != NULL) { 243 while ((linelen = getline(&buf, &linecap, fp)) > 0) { 244 if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') { 245 static char const toks[] = " \t\r\n,="; 246 char *q = strtok(NULL, toks); 247 int i = 0; 248 mode_t *modeset; 249 250 while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0) 251 ++i; 252 #if debugging 253 if (i == _UC_FIELDS) 254 printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : ""); 255 else 256 printf("Got kwd[%s]=%s\n", p, q); 257 #endif 258 switch (i) { 259 case _UC_DEFAULTPWD: 260 config.default_password = boolean_val(q, 1); 261 break; 262 case _UC_REUSEUID: 263 config.reuse_uids = boolean_val(q, 0); 264 break; 265 case _UC_REUSEGID: 266 config.reuse_gids = boolean_val(q, 0); 267 break; 268 case _UC_NISPASSWD: 269 config.nispasswd = (q == NULL || !boolean_val(q, 1)) 270 ? NULL : newstr(q); 271 break; 272 case _UC_DOTDIR: 273 config.dotdir = (q == NULL || !boolean_val(q, 1)) 274 ? NULL : newstr(q); 275 break; 276 case _UC_NEWMAIL: 277 config.newmail = (q == NULL || !boolean_val(q, 1)) 278 ? NULL : newstr(q); 279 break; 280 case _UC_LOGFILE: 281 config.logfile = (q == NULL || !boolean_val(q, 1)) 282 ? NULL : newstr(q); 283 break; 284 case _UC_HOMEROOT: 285 config.home = (q == NULL || !boolean_val(q, 1)) 286 ? "/home" : newstr(q); 287 break; 288 case _UC_HOMEMODE: 289 modeset = setmode(q); 290 config.homemode = (q == NULL || !boolean_val(q, 1)) 291 ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE); 292 free(modeset); 293 break; 294 case _UC_SHELLPATH: 295 config.shelldir = (q == NULL || !boolean_val(q, 1)) 296 ? "/bin" : newstr(q); 297 break; 298 case _UC_SHELLS: 299 for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks)) 300 system_shells[i] = newstr(q); 301 if (i > 0) 302 while (i < _UC_MAXSHELLS) 303 system_shells[i++] = NULL; 304 break; 305 case _UC_DEFAULTSHELL: 306 config.shell_default = (q == NULL || !boolean_val(q, 1)) 307 ? (char *) bourne_shell : newstr(q); 308 break; 309 case _UC_DEFAULTGROUP: 310 q = unquote(q); 311 config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL) 312 ? NULL : newstr(q); 313 break; 314 case _UC_EXTRAGROUPS: 315 for (i = 0; q != NULL; q = strtok(NULL, toks)) { 316 if (extendarray(&config.groups, &config.numgroups, i + 2) != -1) 317 config.groups[i++] = newstr(q); 318 } 319 if (i > 0) 320 while (i < config.numgroups) 321 config.groups[i++] = NULL; 322 break; 323 case _UC_DEFAULTCLASS: 324 config.default_class = (q == NULL || !boolean_val(q, 1)) 325 ? NULL : newstr(q); 326 break; 327 case _UC_MINUID: 328 if ((q = unquote(q)) != NULL && isdigit(*q)) 329 config.min_uid = (uid_t) atol(q); 330 break; 331 case _UC_MAXUID: 332 if ((q = unquote(q)) != NULL && isdigit(*q)) 333 config.max_uid = (uid_t) atol(q); 334 break; 335 case _UC_MINGID: 336 if ((q = unquote(q)) != NULL && isdigit(*q)) 337 config.min_gid = (gid_t) atol(q); 338 break; 339 case _UC_MAXGID: 340 if ((q = unquote(q)) != NULL && isdigit(*q)) 341 config.max_gid = (gid_t) atol(q); 342 break; 343 case _UC_EXPIRE: 344 if ((q = unquote(q)) != NULL && isdigit(*q)) 345 config.expire_days = atoi(q); 346 break; 347 case _UC_PASSWORD: 348 if ((q = unquote(q)) != NULL && isdigit(*q)) 349 config.password_days = atoi(q); 350 break; 351 case _UC_FIELDS: 352 case _UC_NONE: 353 break; 354 } 355 } 356 } 357 if (linecap > 0) 358 free(buf); 359 fclose(fp); 360 } 361 return &config; 362 } 363 364 365 int 366 write_userconfig(char const * file) 367 { 368 int fd; 369 370 if (file == NULL) 371 file = _PATH_PW_CONF; 372 373 if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) { 374 FILE *fp; 375 376 if ((fp = fdopen(fd, "w")) == NULL) 377 close(fd); 378 else { 379 int i, j, k; 380 int len = LNBUFSZ; 381 char *buf = malloc(len); 382 383 for (i = _UC_NONE; i < _UC_FIELDS; i++) { 384 int quote = 1; 385 char const *val = buf; 386 387 *buf = '\0'; 388 switch (i) { 389 case _UC_DEFAULTPWD: 390 val = boolean_str(config.default_password); 391 break; 392 case _UC_REUSEUID: 393 val = boolean_str(config.reuse_uids); 394 break; 395 case _UC_REUSEGID: 396 val = boolean_str(config.reuse_gids); 397 break; 398 case _UC_NISPASSWD: 399 val = config.nispasswd ? config.nispasswd : ""; 400 quote = 0; 401 break; 402 case _UC_DOTDIR: 403 val = config.dotdir ? config.dotdir : boolean_str(0); 404 break; 405 case _UC_NEWMAIL: 406 val = config.newmail ? config.newmail : boolean_str(0); 407 break; 408 case _UC_LOGFILE: 409 val = config.logfile ? config.logfile : boolean_str(0); 410 break; 411 case _UC_HOMEROOT: 412 val = config.home; 413 break; 414 case _UC_HOMEMODE: 415 sprintf(buf, "%04o", config.homemode); 416 quote = 0; 417 break; 418 case _UC_SHELLPATH: 419 val = config.shelldir; 420 break; 421 case _UC_SHELLS: 422 for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) { 423 char lbuf[64]; 424 int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]); 425 if (l < 0) 426 l = 0; 427 if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) { 428 strcpy(buf + k, lbuf); 429 k += l; 430 } 431 } 432 quote = 0; 433 break; 434 case _UC_DEFAULTSHELL: 435 val = config.shell_default ? config.shell_default : bourne_shell; 436 break; 437 case _UC_DEFAULTGROUP: 438 val = config.default_group ? config.default_group : ""; 439 break; 440 case _UC_EXTRAGROUPS: 441 extendarray(&config.groups, &config.numgroups, 200); 442 for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) { 443 char lbuf[64]; 444 int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]); 445 if (l < 0) 446 l = 0; 447 if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) { 448 strcpy(buf + k, lbuf); 449 k += l; 450 } 451 } 452 quote = 0; 453 break; 454 case _UC_DEFAULTCLASS: 455 val = config.default_class ? config.default_class : ""; 456 break; 457 case _UC_MINUID: 458 sprintf(buf, "%lu", (unsigned long) config.min_uid); 459 quote = 0; 460 break; 461 case _UC_MAXUID: 462 sprintf(buf, "%lu", (unsigned long) config.max_uid); 463 quote = 0; 464 break; 465 case _UC_MINGID: 466 sprintf(buf, "%lu", (unsigned long) config.min_gid); 467 quote = 0; 468 break; 469 case _UC_MAXGID: 470 sprintf(buf, "%lu", (unsigned long) config.max_gid); 471 quote = 0; 472 break; 473 case _UC_EXPIRE: 474 sprintf(buf, "%d", config.expire_days); 475 quote = 0; 476 break; 477 case _UC_PASSWORD: 478 sprintf(buf, "%d", config.password_days); 479 quote = 0; 480 break; 481 case _UC_NONE: 482 break; 483 } 484 485 if (comments[i]) 486 fputs(comments[i], fp); 487 488 if (*kwds[i]) { 489 if (quote) 490 fprintf(fp, "%s = \"%s\"\n", kwds[i], val); 491 else 492 fprintf(fp, "%s = %s\n", kwds[i], val); 493 #if debugging 494 printf("WROTE: %s = %s\n", kwds[i], val); 495 #endif 496 } 497 } 498 free(buf); 499 return fclose(fp) != EOF; 500 } 501 } 502 return 0; 503 } 504