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