1/* $OpenBSD: htpasswd.c,v 1.18 2021/07/12 15:09:19 beck Exp $ */ 2/* 3 * Copyright (c) 2014 Florian Obser <florian@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/stat.h> 19 20#include <err.h> 21#include <errno.h> 22#include <fcntl.h> 23#include <limits.h> 24#include <pwd.h> 25#include <readpassphrase.h> 26#include <stdio.h> 27#include <stdlib.h> 28#include <string.h> 29#include <unistd.h> 30 31__dead void usage(void); 32void nag(char*); 33 34extern char *__progname; 35 36__dead void 37usage(void) 38{ 39 fprintf(stderr, "usage:\t%s [file] login\n", __progname); 40 fprintf(stderr, "\t%s -I [file]\n", __progname); 41 exit(1); 42} 43 44#define MAXNAG 5 45int nagcount; 46 47int 48main(int argc, char** argv) 49{ 50 char tmpl[sizeof("/tmp/htpasswd-XXXXXXXXXX")]; 51 char hash[_PASSWORD_LEN], pass[1024], pass2[1024]; 52 char *line = NULL, *login = NULL, *tok; 53 int c, fd, loginlen, batch = 0; 54 FILE *in = NULL, *out = NULL; 55 const char *file = NULL; 56 size_t linesize = 0; 57 ssize_t linelen; 58 mode_t old_umask; 59 60 while ((c = getopt(argc, argv, "I")) != -1) { 61 switch (c) { 62 case 'I': 63 batch = 1; 64 break; 65 default: 66 usage(); 67 /* NOT REACHED */ 68 break; 69 } 70 } 71 72 argc -= optind; 73 argv += optind; 74 75 if ((batch && argc == 1) || (!batch && argc == 2)) { 76 if (unveil(argv[0], "rwc") == -1) 77 err(1, "unveil %s", argv[0]); 78 if (unveil("/tmp", "rwc") == -1) 79 err(1, "unveil /tmp"); 80 } 81 if (pledge("stdio rpath wpath cpath flock tmppath tty", NULL) == -1) 82 err(1, "pledge"); 83 84 if (batch) { 85 if (argc == 1) 86 file = argv[0]; 87 else if (argc > 1) 88 usage(); 89 else if (pledge("stdio", NULL) == -1) 90 err(1, "pledge"); 91 92 if ((linelen = getline(&line, &linesize, stdin)) == -1) 93 err(1, "cannot read login:password from stdin"); 94 line[linelen-1] = '\0'; 95 96 if ((tok = strstr(line, ":")) == NULL) 97 errx(1, "cannot find ':' in input"); 98 *tok++ = '\0'; 99 100 if ((loginlen = asprintf(&login, "%s:", line)) == -1) 101 err(1, "asprintf"); 102 103 if (strlcpy(pass, tok, sizeof(pass)) >= sizeof(pass)) 104 errx(1, "password too long"); 105 } else { 106 107 switch (argc) { 108 case 1: 109 if (pledge("stdio tty", NULL) == -1) 110 err(1, "pledge"); 111 if ((loginlen = asprintf(&login, "%s:", argv[0])) == -1) 112 err(1, "asprintf"); 113 break; 114 case 2: 115 file = argv[0]; 116 if ((loginlen = asprintf(&login, "%s:", argv[1])) == -1) 117 err(1, "asprintf"); 118 break; 119 default: 120 usage(); 121 /* NOT REACHED */ 122 break; 123 } 124 125 if (!readpassphrase("Password: ", pass, sizeof(pass), 126 RPP_ECHO_OFF)) 127 err(1, "unable to read password"); 128 if (!readpassphrase("Retype Password: ", pass2, sizeof(pass2), 129 RPP_ECHO_OFF)) { 130 explicit_bzero(pass, sizeof(pass)); 131 err(1, "unable to read password"); 132 } 133 if (strcmp(pass, pass2) != 0) { 134 explicit_bzero(pass, sizeof(pass)); 135 explicit_bzero(pass2, sizeof(pass2)); 136 errx(1, "passwords don't match"); 137 } 138 139 explicit_bzero(pass2, sizeof(pass2)); 140 } 141 142 if (crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)) != 0) 143 err(1, "can't generate hash"); 144 explicit_bzero(pass, sizeof(pass)); 145 146 if (file == NULL) 147 printf("%s%s\n", login, hash); 148 else { 149 if ((in = fopen(file, "r+")) == NULL) { 150 if (errno == ENOENT) { 151 old_umask = umask(S_IXUSR| 152 S_IWGRP|S_IRGRP|S_IXGRP| 153 S_IWOTH|S_IROTH|S_IXOTH); 154 if ((out = fopen(file, "w")) == NULL) 155 err(1, "cannot open password file for" 156 " reading or writing"); 157 umask(old_umask); 158 } else 159 err(1, "cannot open password file for" 160 " reading or writing"); 161 } else 162 if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1) 163 errx(1, "cannot lock password file"); 164 165 /* file already exits, copy content and filter login out */ 166 if (out == NULL) { 167 strlcpy(tmpl, "/tmp/htpasswd-XXXXXXXXXX", sizeof(tmpl)); 168 if ((fd = mkstemp(tmpl)) == -1) 169 err(1, "mkstemp"); 170 171 if ((out = fdopen(fd, "w+")) == NULL) 172 err(1, "cannot open tempfile"); 173 174 while ((linelen = getline(&line, &linesize, in)) 175 != -1) { 176 if (strncmp(line, login, loginlen) != 0) { 177 if (fprintf(out, "%s", line) == -1) 178 errx(1, "cannot write to temp " 179 "file"); 180 nag(line); 181 } 182 } 183 } 184 if (fprintf(out, "%s%s\n", login, hash) == -1) 185 errx(1, "cannot write new password hash"); 186 187 /* file already exists, overwrite it */ 188 if (in != NULL) { 189 if (fseek(in, 0, SEEK_SET) == -1) 190 err(1, "cannot seek in password file"); 191 if (fseek(out, 0, SEEK_SET) == -1) 192 err(1, "cannot seek in temp file"); 193 if (ftruncate(fileno(in), 0) == -1) 194 err(1, "cannot truncate password file"); 195 while ((linelen = getline(&line, &linesize, out)) 196 != -1) 197 if (fprintf(in, "%s", line) == -1) 198 errx(1, "cannot write to password " 199 "file"); 200 if (fclose(in) == EOF) 201 err(1, "cannot close password file"); 202 } 203 if (fclose(out) == EOF) { 204 if (in != NULL) 205 err(1, "cannot close temp file"); 206 else 207 err(1, "cannot close password file"); 208 } 209 if (in != NULL && unlink(tmpl) == -1) 210 err(1, "cannot delete temp file (%s)", tmpl); 211 } 212 if (nagcount >= MAXNAG) 213 warnx("%d more logins not using bcryt.", nagcount - MAXNAG); 214 exit(0); 215} 216 217void 218nag(char* line) 219{ 220 const char *tok; 221 if (strtok(line, ":") != NULL) 222 if ((tok = strtok(NULL, ":")) != NULL) 223 if (strncmp(tok, "$2a$", 4) != 0 && 224 strncmp(tok, "$2b$", 4) != 0) { 225 nagcount++; 226 if (nagcount <= MAXNAG) 227 warnx("%s doesn't use bcrypt." 228 " Update the password.", line); 229 } 230} 231