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