1/* $OpenBSD: chpass.c,v 1.50 2023/03/08 04:43:10 guenther Exp $ */ 2/* $NetBSD: chpass.c,v 1.8 1996/05/15 21:50:43 jtc Exp $ */ 3 4/*- 5 * Copyright (c) 1988, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33#include <sys/resource.h> 34#include <sys/stat.h> 35#include <sys/time.h> 36#include <sys/uio.h> 37 38#include <err.h> 39#include <errno.h> 40#include <fcntl.h> 41#include <paths.h> 42#include <pwd.h> 43#include <signal.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <unistd.h> 48#include <util.h> 49 50#include "chpass.h" 51 52extern char *__progname; 53 54enum { NEWSH, LOADENTRY, EDITENTRY } op; 55uid_t uid; 56 57void baduser(void); 58void kbintr(int); 59void usage(void); 60 61int 62main(int argc, char *argv[]) 63{ 64 struct passwd *pw = NULL, *opw = NULL, lpw; 65 int i, ch, pfd, tfd, dfd; 66 char *tz, *arg = NULL; 67 sigset_t fullset; 68 69 /* We need to use the system timezone for date conversions. */ 70 if ((tz = getenv("TZ")) != NULL) { 71 unsetenv("TZ"); 72 tzset(); 73 setenv("TZ", tz, 1); 74 } 75 76 op = EDITENTRY; 77 while ((ch = getopt(argc, argv, "a:s:")) != -1) 78 switch(ch) { 79 case 'a': 80 op = LOADENTRY; 81 arg = optarg; 82 break; 83 case 's': 84 op = NEWSH; 85 arg = optarg; 86 break; 87 default: 88 usage(); 89 } 90 argc -= optind; 91 argv += optind; 92 93 uid = getuid(); 94 95 if (op == EDITENTRY || op == NEWSH) 96 switch(argc) { 97 case 0: 98 pw = getpwuid_shadow(uid); 99 if (!pw) 100 errx(1, "unknown user: uid %u", uid); 101 break; 102 case 1: 103 pw = getpwnam_shadow(*argv); 104 if (!pw) 105 errx(1, "unknown user: %s", *argv); 106 if (uid && uid != pw->pw_uid) 107 baduser(); 108 break; 109 default: 110 usage(); 111 } 112 113 if (op == LOADENTRY) { 114 if (argc != 0) 115 errx(1, "option -a does not accept user argument"); 116 if (uid) 117 baduser(); 118 pw = &lpw; 119 if (!pw_scan(arg, pw, NULL)) 120 exit(1); 121 opw = getpwnam_shadow(pw->pw_name); 122 } 123 if (opw == NULL && (opw = pw_dup(pw)) == NULL) 124 err(1, NULL); 125 126 /* Edit the user passwd information if requested. */ 127 if (op == EDITENTRY) { 128 char tempname[] = _PATH_VARTMP "pw.XXXXXXXXXX"; 129 int edit_status; 130 131 if ((pw = pw_dup(pw)) == NULL) 132 pw_error(NULL, 1, 1); 133 dfd = mkostemp(tempname, O_CLOEXEC); 134 if (dfd == -1) 135 pw_error(tempname, 1, 1); 136 display(tempname, dfd, pw); 137 138 if (unveil(_PATH_BSHELL, "x") == -1) 139 err(1, "unveil %s", _PATH_BSHELL); 140 if (unveil(_PATH_SHELLS, "r") == -1) 141 err(1, "unveil %s", _PATH_SHELLS); 142 if (unveil(tempname, "rc") == -1) 143 err(1, "unveil %s", tempname); 144 if (pledge("stdio rpath wpath cpath id proc exec unveil", 145 NULL) == -1) 146 err(1, "pledge"); 147 148 edit_status = edit(tempname, pw); 149 close(dfd); 150 unlink(tempname); 151 152 switch (edit_status) { 153 case EDIT_OK: 154 break; 155 case EDIT_NOCHANGE: 156 pw_error(NULL, 0, 0); 157 break; 158 case EDIT_ERROR: 159 default: 160 pw_error(tempname, 1, 1); 161 break; 162 } 163 } 164 165 if (op == NEWSH) { 166 if (unveil(_PATH_SHELLS, "r") == -1) 167 err(1, "unveil %s", _PATH_SHELLS); 168 if (pledge("stdio rpath wpath cpath id proc exec unveil", 169 NULL) == -1) 170 err(1, "pledge"); 171 172 /* protect p_shell -- it thinks NULL is /bin/sh */ 173 if (!arg[0]) 174 usage(); 175 if (p_shell(arg, pw, NULL)) 176 pw_error(NULL, 0, 1); 177 } 178 179 /* Drop user's real uid and block all signals to avoid a DoS. */ 180 setuid(0); 181 sigfillset(&fullset); 182 sigdelset(&fullset, SIGINT); 183 sigprocmask(SIG_BLOCK, &fullset, NULL); 184 185 if (unveil(_PATH_MASTERPASSWD_LOCK, "rwc") == -1) 186 err(1, "unveil %s", _PATH_MASTERPASSWD_LOCK); 187 if (unveil(_PATH_MASTERPASSWD, "r") == -1) 188 err(1, "unveil %s", _PATH_MASTERPASSWD); 189 if (unveil(_PATH_PWD_MKDB, "x") == -1) 190 err(1, "unveil %s", _PATH_PWD_MKDB); 191 if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1) 192 err(1, "pledge"); 193 194 /* Get the passwd lock file and open the passwd file for reading. */ 195 pw_init(); 196 for (i = 1; (tfd = pw_lock(0)) == -1; i++) { 197 if (i == 4) 198 (void)fputs("Attempting to lock password file, " 199 "please wait or press ^C to abort", stderr); 200 (void)signal(SIGINT, kbintr); 201 if (i % 16 == 0) 202 fputc('.', stderr); 203 usleep(250000); 204 (void)signal(SIGINT, SIG_IGN); 205 } 206 if (i >= 4) 207 fputc('\n', stderr); 208 pfd = open(_PATH_MASTERPASSWD, O_RDONLY|O_CLOEXEC); 209 if (pfd == -1) 210 pw_error(_PATH_MASTERPASSWD, 1, 1); 211 212 /* Copy the passwd file to the lock file, updating pw. */ 213 pw_copy(pfd, tfd, pw, opw); 214 215 /* If username changed we need to rebuild the entire db. */ 216 arg = !strcmp(opw->pw_name, pw->pw_name) ? pw->pw_name : NULL; 217 218 /* Now finish the passwd file update. */ 219 if (pw_mkdb(arg, 0) == -1) 220 pw_error(NULL, 0, 1); 221 exit(0); 222} 223 224void 225baduser(void) 226{ 227 228 errx(1, "%s", strerror(EACCES)); 229} 230 231void 232kbintr(int signo) 233{ 234 dprintf(STDERR_FILENO, "\n%s: %s unchanged\n", 235 __progname, _PATH_MASTERPASSWD); 236 _exit(1); 237} 238 239void 240usage(void) 241{ 242 243 (void)fprintf(stderr, "usage: %s [-s newshell] [user]\n", __progname); 244 (void)fprintf(stderr, " %s -a list\n", __progname); 245 exit(1); 246} 247