1/* $OpenBSD: pwd_mkdb.c,v 1.36 2003/06/08 21:14:55 millert Exp $ */ 2 3/*- 4 * Copyright (c) 1991, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Portions Copyright (c) 1994, Jason Downs. All rights reserved. 7 * Portions Copyright (c) 1998, Todd C. Miller. All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include <sys/cdefs.h> 35#ifndef lint 36__unused static const char copyright[] = 37"@(#) Copyright (c) 1991, 1993, 1994\n\ 38 The Regents of the University of California. All rights reserved.\n"; 39#endif /* not lint */ 40 41#ifndef lint 42#if 0 43static const char sccsid[] = "from: @(#)pwd_mkdb.c 8.5 (Berkeley) 4/20/94"; 44#else 45__unused static const char rcsid[] = "$OpenBSD: pwd_mkdb.c,v 1.36 2003/06/08 21:14:55 millert Exp $"; 46#endif 47#endif /* not lint */ 48 49#include <sys/param.h> 50#include <sys/stat.h> 51 52#include <db.h> 53#include <err.h> 54#include <errno.h> 55#include <fcntl.h> 56#include <grp.h> 57#include <limits.h> 58#include <pwd.h> 59#include <signal.h> 60#include <stdio.h> 61#include <stdlib.h> 62#include <string.h> 63#include <unistd.h> 64#include <util.h> 65#include <sys/param.h> 66#include "pw_scan.h" 67 68#define INSECURE 1 69#define SECURE 2 70#define PERM_INSECURE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 71#define PERM_SECURE (S_IRUSR|S_IWUSR) 72 73#define FILE_SECURE 0x01 74#define FILE_INSECURE 0x02 75#define FILE_ORIG 0x04 76 77#define SHADOW_GROUP "wheel" 78 79HASHINFO openinfo = { 80 4096, /* bsize */ 81 32, /* ffactor */ 82 256, /* nelem */ 83 2048 * 1024, /* cachesize */ 84 NULL, /* hash() */ 85 0 /* lorder */ 86}; 87 88static char *pname; /* password file name */ 89static char *basedir; /* dir holding master.passwd */ 90static int clean; /* what to remove on cleanup */ 91static int hasyp; /* are we running YP? */ 92 93void cleanup(void); 94void error(char *); 95void errorx(char *); 96void cp(char *, char *, mode_t); 97void mv(char *, char *); 98int scan(FILE *, struct passwd *, int *); 99void usage(void); 100char *changedir(char *path, char *dir); 101void db_store(FILE *, FILE *, DB *, DB *,struct passwd *, int, char *, uid_t); 102 103int 104main(int argc, char **argv) 105{ 106 DB *dp, *edp; 107 DBT data, key; 108 FILE *fp, *oldfp = NULL; 109 struct stat st; 110 struct passwd pwd; 111 struct group *grp; 112 sigset_t set; 113 uid_t olduid; 114 gid_t shadow; 115 int ch, tfd, makeold, secureonly, flags, checkonly; 116 char *username, buf[MAX(MAXPATHLEN, LINE_MAX * 2)]; 117 118 flags = checkonly = makeold = secureonly = 0; 119 username = NULL; 120 while ((ch = getopt(argc, argv, "cd:psu:v")) != -1) 121 switch (ch) { 122 case 'c': /* verify only */ 123 checkonly = 1; 124 break; 125 case 'd': 126 basedir = optarg; 127 if (strlen(basedir) > MAXPATHLEN - 40) 128 errx(1, "basedir too long"); 129 break; 130 case 'p': /* create V7 "file.orig" */ 131 makeold = 1; 132 break; 133 case 's': /* only update spwd.db */ 134 secureonly = 1; 135 break; 136 case 'u': /* only update this record */ 137 username = optarg; 138 if (strlen(username) > _PW_NAME_LEN) 139 errx(1, "username too long"); 140 break; 141 case 'v': /* backward compatible */ 142 break; 143 case '?': 144 default: 145 usage(); 146 } 147 argc -= optind; 148 argv += optind; 149 150 if (argc != 1 || (makeold && secureonly) || 151 (username && (*username == '+' || *username == '-'))) 152 usage(); 153 154 if ((grp = getgrnam(SHADOW_GROUP)) == NULL) 155 errx(1, "cannot find `%s' in the group database, aborting", 156 SHADOW_GROUP); 157 shadow = grp->gr_gid; 158 159 /* 160 * This could be changed to allow the user to interrupt. 161 * Probably not worth the effort. 162 */ 163 sigemptyset(&set); 164 sigaddset(&set, SIGTSTP); 165 sigaddset(&set, SIGHUP); 166 sigaddset(&set, SIGINT); 167 sigaddset(&set, SIGQUIT); 168 sigaddset(&set, SIGTERM); 169 (void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL); 170 171 /* We don't care what the user wants. */ 172 (void)umask(0); 173 174 if (**argv != '/' && basedir == NULL) 175 errx(1, "%s must be specified as an absolute path", *argv); 176 177 if ((pname = strdup(changedir(*argv, basedir))) == NULL) 178 err(1, NULL); 179 /* Open the original password file */ 180 if (!(fp = fopen(pname, "r"))) 181 error(pname); 182 183 /* Check only if password database is valid */ 184 if (checkonly) { 185 u_int cnt; 186 187 for (cnt = 1; scan(fp, &pwd, &flags); ++cnt) 188 ; 189 exit(0); 190 } 191 192 if (fstat(fileno(fp), &st) == -1) 193 error(pname); 194 195 /* Tweak openinfo values for large passwd files. */ 196 if (st.st_size > (off_t)100*1024) 197 openinfo.cachesize = MIN(st.st_size * 20, (off_t)12*1024*1024); 198 if (st.st_size / 128 > openinfo.nelem) 199 openinfo.nelem = st.st_size / 128; 200 201 /* If only updating a single record, stash the old uid */ 202 if (username) { 203 dp = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL); 204 if (dp == NULL) 205 error(_PATH_MP_DB); 206 buf[0] = _PW_KEYBYNAME; 207 strlcpy(buf + 1, username, sizeof(buf) - 1); 208 key.data = (u_char *)buf; 209 key.size = strlen(buf + 1) + 1; 210 if ((dp->get)(dp, &key, &data, 0) == 0) { 211 char *p = (char *)data.data; 212 /* Skip to uid field */ 213 while (*p++ != '\0') 214 ; 215 while (*p++ != '\0') 216 ; 217 memcpy(&olduid, p, sizeof(olduid)); 218 } else 219 olduid = UID_MAX; 220 (dp->close)(dp); 221 } 222 223 /* Open the temporary encrypted password database. */ 224 (void)snprintf(buf, sizeof(buf), "%s.tmp", 225 changedir(_PATH_SMP_DB, basedir)); 226 if (username) { 227 cp(changedir(_PATH_SMP_DB, basedir), buf, PERM_SECURE); 228 edp = dbopen(buf, 229 O_RDWR, PERM_SECURE, DB_HASH, &openinfo); 230 } else { 231 edp = dbopen(buf, 232 O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo); 233 } 234 if (!edp) 235 error(buf); 236 if (fchown(edp->fd(edp), (uid_t)-1, shadow) != 0) 237 warn("%s: unable to set group to %s", _PATH_SMP_DB, 238 SHADOW_GROUP); 239 else if (fchmod(edp->fd(edp), PERM_SECURE|S_IRGRP) != 0) 240 warn("%s: unable to make group readable", _PATH_SMP_DB); 241 clean |= FILE_SECURE; 242 243 /* Open the temporary insecure password database. */ 244 if (!secureonly) { 245 (void)snprintf(buf, sizeof(buf), "%s.tmp", 246 changedir(_PATH_MP_DB, basedir)); 247 if (username) { 248 cp(changedir(_PATH_MP_DB, basedir), buf, PERM_INSECURE); 249 dp = dbopen(buf, O_RDWR, PERM_INSECURE, DB_HASH, 250 &openinfo); 251 } else { 252 dp = dbopen(buf, O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, 253 DB_HASH, &openinfo); 254 } 255 if (dp == NULL) 256 error(buf); 257 clean |= FILE_INSECURE; 258 } else 259 dp = NULL; 260 261 /* 262 * Open file for old password file. Minor trickiness -- don't want to 263 * chance the file already existing, since someone (stupidly) might 264 * still be using this for permission checking. So, open it first and 265 * fdopen the resulting fd. The resulting file should be readable by 266 * everyone. 267 */ 268 if (makeold) { 269 (void)snprintf(buf, sizeof(buf), "%s.orig", pname); 270 if ((tfd = open(buf, 271 O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0) 272 error(buf); 273 if ((oldfp = fdopen(tfd, "w")) == NULL) 274 error(buf); 275 clean |= FILE_ORIG; 276 } 277 278 /* 279 * The databases actually contain three copies of the original data. 280 * Each password file entry is converted into a rough approximation 281 * of a ``struct passwd'', with the strings placed inline. This 282 * object is then stored as the data for three separate keys. The 283 * first key * is the pw_name field prepended by the _PW_KEYBYNAME 284 * character. The second key is the pw_uid field prepended by the 285 * _PW_KEYBYUID character. The third key is the line number in the 286 * original file prepended by the _PW_KEYBYNUM character. (The special 287 * characters are prepended to ensure that the keys do not collide.) 288 * 289 * If we see something go by that looks like YP, we save a special 290 * pointer record, which if YP is enabled in the C lib, will speed 291 * things up. 292 */ 293 294 /* 295 * Write the .db files. 296 * We do this three times, one per key type (for getpw{nam,uid,ent}). 297 * The first time through we also check for YP, issue warnings 298 * and save the V7 format passwd file if necessary. 299 */ 300 db_store(fp, oldfp, edp, dp, &pwd, _PW_KEYBYNAME, username, olduid); 301 db_store(fp, oldfp, edp, dp, &pwd, _PW_KEYBYUID, username, olduid); 302 db_store(fp, oldfp, edp, dp, &pwd, _PW_KEYBYNUM, username, olduid); 303 304 /* Store YP token, if needed. */ 305 if (hasyp && !username) { 306 key.data = (u_char *)_PW_YPTOKEN; 307 key.size = strlen(_PW_YPTOKEN); 308 data.data = (u_char *)NULL; 309 data.size = 0; 310 311 if ((edp->put)(edp, &key, &data, R_NOOVERWRITE) == -1) 312 error("put"); 313 314 if (dp && (dp->put)(dp, &key, &data, R_NOOVERWRITE) == -1) 315 error("put"); 316 } 317 318 if ((edp->close)(edp)) 319 error("close edp"); 320 if (dp && (dp->close)(dp)) 321 error("close dp"); 322 if (makeold) { 323 if (fclose(oldfp) == EOF) 324 error("close old"); 325 } 326 327 /* Set master.passwd permissions, in case caller forgot. */ 328 (void)fchmod(fileno(fp), S_IRUSR|S_IWUSR); 329 if (fclose(fp) != 0) 330 error("fclose"); 331 332 /* Install as the real password files. */ 333 if (!secureonly) { 334 (void)snprintf(buf, sizeof(buf), "%s.tmp", 335 changedir(_PATH_MP_DB, basedir)); 336 mv(buf, changedir(_PATH_MP_DB, basedir)); 337 } 338 (void)snprintf(buf, sizeof(buf), "%s.tmp", 339 changedir(_PATH_SMP_DB, basedir)); 340 mv(buf, changedir(_PATH_SMP_DB, basedir)); 341 if (makeold) { 342 (void)snprintf(buf, sizeof(buf), "%s.orig", pname); 343 mv(buf, changedir(_PATH_PASSWD, basedir)); 344 } 345 346 /* 347 * Move the master password LAST -- chpass(1), passwd(1) and vipw(8) 348 * all use flock(2) on it to block other incarnations of themselves. 349 * The rename means that everything is unlocked, as the original file 350 * can no longer be accessed. 351 */ 352 mv(pname, changedir(_PATH_MASTERPASSWD, basedir)); 353 exit(0); 354} 355 356int 357scan(FILE *fp, struct passwd *pw, int *flags) 358{ 359 static int lcnt; 360 static char line[LINE_MAX]; 361 char *p; 362 363 if (fgets(line, sizeof(line), fp) == NULL) 364 return (0); 365 ++lcnt; 366 /* 367 * ``... if I swallow anything evil, put your fingers down my 368 * throat...'' 369 * -- The Who 370 */ 371 p = line; 372 if (*p != '\0' && *(p += strlen(line) - 1) != '\n') { 373 warnx("line too long"); 374 goto fmt; 375 } 376 *p = '\0'; 377 *flags = 0; 378 if (!pw_scan(line, pw, flags)) { 379 warnx("at line #%d", lcnt); 380fmt: errno = EFTYPE; /* XXX */ 381 error(pname); 382 } 383 384 return (1); 385} 386 387void 388cp(char *from, char *to, mode_t mode) 389{ 390 static char buf[MAXBSIZE]; 391 int from_fd, rcount, to_fd, wcount; 392 393 if ((from_fd = open(from, O_RDONLY, 0)) < 0) 394 error(from); 395 if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) 396 error(to); 397 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { 398 wcount = write(to_fd, buf, rcount); 399 if (rcount != wcount || wcount == -1) { 400 int sverrno = errno; 401 402 (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); 403 errno = sverrno; 404 error(buf); 405 } 406 } 407 if (rcount < 0) { 408 int sverrno = errno; 409 410 (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); 411 errno = sverrno; 412 error(buf); 413 } 414} 415 416void 417mv(from, to) 418 char *from, *to; 419{ 420 char buf[MAXPATHLEN * 2]; 421 422 if (rename(from, to)) { 423 int sverrno = errno; 424 425 (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); 426 errno = sverrno; 427 error(buf); 428 } 429} 430 431void 432error(name) 433 char *name; 434{ 435 436 warn("%s", name); 437 cleanup(); 438 exit(1); 439} 440 441void 442errorx(name) 443 char *name; 444{ 445 446 warnx("%s", name); 447 cleanup(); 448 exit(1); 449} 450 451void 452cleanup() 453{ 454 char buf[MAXPATHLEN]; 455 456 if (clean & FILE_ORIG) { 457 (void)snprintf(buf, sizeof(buf), "%s.orig", pname); 458 (void)unlink(buf); 459 } 460 if (clean & FILE_SECURE) { 461 (void)snprintf(buf, sizeof(buf), "%s.tmp", 462 changedir(_PATH_SMP_DB, basedir)); 463 (void)unlink(buf); 464 } 465 if (clean & FILE_INSECURE) { 466 (void)snprintf(buf, sizeof(buf), "%s.tmp", 467 changedir(_PATH_MP_DB, basedir)); 468 (void)unlink(buf); 469 } 470} 471 472void 473usage(void) 474{ 475 476 (void)fprintf(stderr, 477 "usage: pwd_mkdb [-c] [-p | -s] [-d basedir] [-u username] file\n"); 478 exit(1); 479} 480 481char * 482changedir(char *path, char *dir) 483{ 484 static char fixed[MAXPATHLEN]; 485 char *p; 486 487 if (!dir) 488 return (path); 489 490 if ((p = strrchr(path, '/')) != NULL) 491 path = p + 1; 492 snprintf(fixed, sizeof(fixed), "%s/%s", dir, path); 493 return (fixed); 494} 495 496void 497db_store(FILE *fp, FILE *oldfp, DB *edp, DB *dp, struct passwd *pw, 498 int keytype, char *username, uid_t olduid) 499{ 500 int flags = 0; 501 int dbmode, found = 0; 502 u_int cnt; 503 char *p, *t, buf[LINE_MAX * 2], tbuf[1024]; 504 DBT data, key; 505 size_t len; 506 static int firsttime = 1; 507 508 /* If given a username just add that record to the existing db. */ 509 dbmode = username ? 0 : R_NOOVERWRITE; 510 511 rewind(fp); 512 data.data = (u_char *)buf; 513 key.data = (u_char *)tbuf; 514 for (cnt = 1; scan(fp, pw, &flags); ++cnt) { 515 516#ifdef __APPLE__ 517 if (pw->pw_name == NULL) 518 continue; 519#endif 520 521 if (firsttime) { 522 /* Look like YP? */ 523 if ((pw->pw_name[0] == '+') || (pw->pw_name[0] == '-')) 524 hasyp++; 525 526 /* Warn about potentially unsafe uid/gid overrides. */ 527 if (pw->pw_name[0] == '+') { 528 if (!(flags & _PASSWORD_NOUID) && !pw->pw_uid) 529 warnx("line %d: superuser override in " 530 "YP inclusion", cnt); 531 if (!(flags & _PASSWORD_NOGID) && !pw->pw_gid) 532 warnx("line %d: wheel override in " 533 "YP inclusion", cnt); 534 } 535 536 /* Create V7 format password file entry. */ 537 if (oldfp != NULL) 538 if (fprintf(oldfp, "%s:*:%u:%u:%s:%s:%s\n", 539 pw->pw_name, pw->pw_uid, pw->pw_gid, 540 pw->pw_gecos, pw->pw_dir, pw->pw_shell) 541 == EOF) 542 error("write old"); 543 } 544 545 /* Are we updating a specific record? */ 546 if (username) { 547 if (strcmp(username, pw->pw_name) != 0) 548 continue; 549 found = 1; 550 /* If the uid changed, remove the old record by uid. */ 551 if (olduid != UID_MAX && olduid != pw->pw_uid) { 552 tbuf[0] = _PW_KEYBYUID; 553 memcpy(tbuf + 1, &olduid, sizeof(olduid)); 554 key.size = sizeof(olduid) + 1; 555 (edp->del)(edp, &key, 0); 556 if (dp) 557 (dp->del)(dp, &key, 0); 558 } 559 /* XXX - should check to see if line number changed. */ 560 } 561 562 /* Build the key. */ 563 tbuf[0] = keytype; 564 switch (keytype) { 565 case _PW_KEYBYNUM: 566 memmove(tbuf + 1, &cnt, sizeof(cnt)); 567 key.size = sizeof(cnt) + 1; 568 break; 569 570 case _PW_KEYBYNAME: 571 len = strlen(pw->pw_name); 572 memmove(tbuf + 1, pw->pw_name, len); 573 key.size = len + 1; 574 break; 575 576 case _PW_KEYBYUID: 577 memmove(tbuf + 1, &pw->pw_uid, sizeof(pw->pw_uid)); 578 key.size = sizeof(pw->pw_uid) + 1; 579 break; 580 } 581 582#define COMPACT(e) t = e; while ((*p++ = *t++)); 583 /* Create the secure record. */ 584 p = buf; 585 COMPACT(pw->pw_name); 586 COMPACT(pw->pw_passwd); 587 memmove(p, &pw->pw_uid, sizeof(uid_t)); 588 p += sizeof(uid_t); 589 memmove(p, &pw->pw_gid, sizeof(gid_t)); 590 p += sizeof(gid_t); 591 memmove(p, &pw->pw_change, sizeof(time_t)); 592 p += sizeof(time_t); 593 COMPACT(pw->pw_class); 594 COMPACT(pw->pw_gecos); 595 COMPACT(pw->pw_dir); 596 COMPACT(pw->pw_shell); 597 memmove(p, &pw->pw_expire, sizeof(time_t)); 598 p += sizeof(time_t); 599 memmove(p, &flags, sizeof(int)); 600 p += sizeof(int); 601 data.size = p - buf; 602 603 /* Write the secure record. */ 604 if ((edp->put)(edp, &key, &data, dbmode) == -1) 605 error("put"); 606 607 if (dp == NULL) 608 continue; 609 610 /* Star out password to make insecure record. */ 611 p = buf + strlen(pw->pw_name) + 1; /* skip pw_name */ 612 len = strlen(pw->pw_passwd); 613 memset(p, 0, len); /* zero pw_passwd */ 614 t = p + len + 1; /* skip pw_passwd */ 615 if (len != 0) 616 *p++ = '*'; 617 *p++ = '\0'; 618 memmove(p, t, data.size - (t - buf)); 619 data.size -= len - 1; 620 621 /* Write the insecure record. */ 622 if ((dp->put)(dp, &key, &data, dbmode) == -1) 623 error("put"); 624 } 625 if (firsttime) { 626 firsttime = 0; 627 if (username && !found && olduid != UID_MAX) 628 errorx("can't find user in master.passwd"); 629 } 630} 631