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$ 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 config.default_group = (q == NULL || !boolean_val(q, 1) || getgrnam(q) == NULL) 310 ? NULL : newstr(q); 311 break; 312 case _UC_EXTRAGROUPS: 313 for (i = 0; q != NULL; q = strtok(NULL, toks)) { 314 if (extendarray(&config.groups, &config.numgroups, i + 2) != -1) 315 config.groups[i++] = newstr(q); 316 } 317 if (i > 0) 318 while (i < config.numgroups) 319 config.groups[i++] = NULL; 320 break; 321 case _UC_DEFAULTCLASS: 322 config.default_class = (q == NULL || !boolean_val(q, 1)) 323 ? NULL : newstr(q); 324 break; 325 case _UC_MINUID: 326 if ((q = unquote(q)) != NULL && isdigit(*q)) 327 config.min_uid = (uid_t) atol(q); 328 break; 329 case _UC_MAXUID: 330 if ((q = unquote(q)) != NULL && isdigit(*q)) 331 config.max_uid = (uid_t) atol(q); 332 break; 333 case _UC_MINGID: 334 if ((q = unquote(q)) != NULL && isdigit(*q)) 335 config.min_gid = (gid_t) atol(q); 336 break; 337 case _UC_MAXGID: 338 if ((q = unquote(q)) != NULL && isdigit(*q)) 339 config.max_gid = (gid_t) atol(q); 340 break; 341 case _UC_EXPIRE: 342 if ((q = unquote(q)) != NULL && isdigit(*q)) 343 config.expire_days = atoi(q); 344 break; 345 case _UC_PASSWORD: 346 if ((q = unquote(q)) != NULL && isdigit(*q)) 347 config.password_days = atoi(q); 348 break; 349 case _UC_FIELDS: 350 case _UC_NONE: 351 break; 352 } 353 } 354 } 355 free(buf); 356 fclose(fp); 357 } 358 return &config; 359 } 360 361 362 int 363 write_userconfig(char const * file) 364 { 365 int fd; 366 367 if (file == NULL) 368 file = _PATH_PW_CONF; 369 370 if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) { 371 FILE *fp; 372 373 if ((fp = fdopen(fd, "w")) == NULL) 374 close(fd); 375 else { 376 int i, j, k; 377 int len = LNBUFSZ; 378 char *buf = malloc(len); 379 380 for (i = _UC_NONE; i < _UC_FIELDS; i++) { 381 int quote = 1; 382 char const *val = buf; 383 384 *buf = '\0'; 385 switch (i) { 386 case _UC_DEFAULTPWD: 387 val = boolean_str(config.default_password); 388 break; 389 case _UC_REUSEUID: 390 val = boolean_str(config.reuse_uids); 391 break; 392 case _UC_REUSEGID: 393 val = boolean_str(config.reuse_gids); 394 break; 395 case _UC_NISPASSWD: 396 val = config.nispasswd ? config.nispasswd : ""; 397 quote = 0; 398 break; 399 case _UC_DOTDIR: 400 val = config.dotdir ? config.dotdir : boolean_str(0); 401 break; 402 case _UC_NEWMAIL: 403 val = config.newmail ? config.newmail : boolean_str(0); 404 break; 405 case _UC_LOGFILE: 406 val = config.logfile ? config.logfile : boolean_str(0); 407 break; 408 case _UC_HOMEROOT: 409 val = config.home; 410 break; 411 case _UC_SHELLPATH: 412 val = config.shelldir; 413 break; 414 case _UC_SHELLS: 415 for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) { 416 char lbuf[64]; 417 int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]); 418 if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) { 419 strcpy(buf + k, lbuf); 420 k += l; 421 } 422 } 423 quote = 0; 424 break; 425 case _UC_DEFAULTSHELL: 426 val = config.shell_default ? config.shell_default : bourne_shell; 427 break; 428 case _UC_DEFAULTGROUP: 429 val = config.default_group ? config.default_group : ""; 430 break; 431 case _UC_EXTRAGROUPS: 432 extendarray(&config.groups, &config.numgroups, 200); 433 for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) { 434 char lbuf[64]; 435 int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]); 436 if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) { 437 strcpy(buf + k, lbuf); 438 k += l; 439 } 440 } 441 quote = 0; 442 break; 443 case _UC_DEFAULTCLASS: 444 val = config.default_class ? config.default_class : ""; 445 break; 446 case _UC_MINUID: 447 sprintf(buf, "%lu", (unsigned long) config.min_uid); 448 quote = 0; 449 break; 450 case _UC_MAXUID: 451 sprintf(buf, "%lu", (unsigned long) config.max_uid); 452 quote = 0; 453 break; 454 case _UC_MINGID: 455 sprintf(buf, "%lu", (unsigned long) config.min_gid); 456 quote = 0; 457 break; 458 case _UC_MAXGID: 459 sprintf(buf, "%lu", (unsigned long) config.max_gid); 460 quote = 0; 461 break; 462 case _UC_EXPIRE: 463 sprintf(buf, "%d", config.expire_days); 464 quote = 0; 465 break; 466 case _UC_PASSWORD: 467 sprintf(buf, "%d", config.password_days); 468 quote = 0; 469 break; 470 case _UC_NONE: 471 break; 472 } 473 474 if (comments[i]) 475 fputs(comments[i], fp); 476 477 if (*kwds[i]) { 478 if (quote) 479 fprintf(fp, "%s = \"%s\"\n", kwds[i], val); 480 else 481 fprintf(fp, "%s = %s\n", kwds[i], val); 482 #if debugging 483 printf("WROTE: %s = %s\n", kwds[i], val); 484 #endif 485 } 486 } 487 free(buf); 488 return fclose(fp) != EOF; 489 } 490 } 491 return 0; 492 } 493