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