1 /* $OpenBSD: htpasswd.c,v 1.15 2015/11/05 20:07:15 florian 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); 32 void nag(char*); 33 34 extern char *__progname; 35 36 __dead void 37 usage(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 45 int nagcount; 46 47 int 48 main(int argc, char** argv) 49 { 50 char salt[_PASSWORD_LEN], 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 if (pledge("stdio rpath wpath cpath flock tmppath tty", NULL) == -1) 61 err(1, "pledge"); 62 63 while ((c = getopt(argc, argv, "I")) != -1) { 64 switch (c) { 65 case 'I': 66 batch = 1; 67 break; 68 default: 69 usage(); 70 /* NOT REACHED */ 71 break; 72 } 73 } 74 75 argc -= optind; 76 argv += optind; 77 78 if (batch) { 79 if (argc == 1) 80 file = argv[0]; 81 else if (argc > 1) 82 usage(); 83 else if (pledge("stdio", NULL) == -1) 84 err(1, "pledge"); 85 86 if ((linelen = getline(&line, &linesize, stdin)) == -1) 87 err(1, "cannot read login:password from stdin"); 88 line[linelen-1] = '\0'; 89 90 if ((tok = strstr(line, ":")) == NULL) 91 errx(1, "cannot find ':' in input"); 92 *tok++ = '\0'; 93 94 if ((loginlen = asprintf(&login, "%s:", line)) == -1) 95 err(1, "asprintf"); 96 97 if (strlcpy(pass, tok, sizeof(pass)) >= sizeof(pass)) 98 errx(1, "password too long"); 99 } else { 100 101 switch (argc) { 102 case 1: 103 if (pledge("stdio tty", NULL) == -1) 104 err(1, "pledge"); 105 if ((loginlen = asprintf(&login, "%s:", argv[0])) == -1) 106 err(1, "asprintf"); 107 break; 108 case 2: 109 file = argv[0]; 110 if ((loginlen = asprintf(&login, "%s:", argv[1])) == -1) 111 err(1, "asprintf"); 112 break; 113 default: 114 usage(); 115 /* NOT REACHED */ 116 break; 117 } 118 119 if (!readpassphrase("Password: ", pass, sizeof(pass), 120 RPP_ECHO_OFF)) 121 err(1, "unable to read password"); 122 if (!readpassphrase("Retype Password: ", pass2, sizeof(pass2), 123 RPP_ECHO_OFF)) { 124 explicit_bzero(pass, sizeof(pass)); 125 err(1, "unable to read password"); 126 } 127 if (strcmp(pass, pass2) != 0) { 128 explicit_bzero(pass, sizeof(pass)); 129 explicit_bzero(pass2, sizeof(pass2)); 130 errx(1, "passwords don't match"); 131 } 132 133 explicit_bzero(pass2, sizeof(pass2)); 134 } 135 136 if (strlcpy(salt, bcrypt_gensalt(8), sizeof(salt)) >= sizeof(salt)) 137 errx(1, "salt too long"); 138 if (strlcpy(hash, bcrypt(pass, salt), sizeof(hash)) >= sizeof(hash)) 139 errx(1, "hash too long"); 140 explicit_bzero(pass, sizeof(pass)); 141 142 if (file == NULL) 143 printf("%s%s\n", login, hash); 144 else { 145 if ((in = fopen(file, "r+")) == NULL) { 146 if (errno == ENOENT) { 147 old_umask = umask(S_IXUSR| 148 S_IWGRP|S_IRGRP|S_IXGRP| 149 S_IWOTH|S_IROTH|S_IXOTH); 150 if ((out = fopen(file, "w")) == NULL) 151 err(1, "cannot open password file for" 152 " reading or writing"); 153 umask(old_umask); 154 } else 155 err(1, "cannot open password file for" 156 " reading or writing"); 157 } else 158 if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1) 159 errx(1, "cannot lock password file"); 160 161 /* file already exits, copy content and filter login out */ 162 if (out == NULL) { 163 strlcpy(tmpl, "/tmp/htpasswd-XXXXXXXXXX", sizeof(tmpl)); 164 if ((fd = mkstemp(tmpl)) == -1) 165 err(1, "mkstemp"); 166 167 if ((out = fdopen(fd, "w+")) == NULL) 168 err(1, "cannot open tempfile"); 169 170 while ((linelen = getline(&line, &linesize, in)) 171 != -1) { 172 if (strncmp(line, login, loginlen) != 0) { 173 if (fprintf(out, "%s", line) == -1) 174 errx(1, "cannot write to temp " 175 "file"); 176 nag(line); 177 } 178 } 179 } 180 if (fprintf(out, "%s%s\n", login, hash) == -1) 181 errx(1, "cannot write new password hash"); 182 183 /* file already exists, overwrite it */ 184 if (in != NULL) { 185 if (fseek(in, 0, SEEK_SET) == -1) 186 err(1, "cannot seek in password file"); 187 if (fseek(out, 0, SEEK_SET) == -1) 188 err(1, "cannot seek in temp file"); 189 if (ftruncate(fileno(in), 0) == -1) 190 err(1, "cannot truncate password file"); 191 while ((linelen = getline(&line, &linesize, out)) 192 != -1) 193 if (fprintf(in, "%s", line) == -1) 194 errx(1, "cannot write to password " 195 "file"); 196 if (fclose(in) == EOF) 197 err(1, "cannot close password file"); 198 } 199 if (fclose(out) == EOF) { 200 if (in != NULL) 201 err(1, "cannot close temp file"); 202 else 203 err(1, "cannot close password file"); 204 } 205 if (in != NULL && unlink(tmpl) == -1) 206 err(1, "cannot delete temp file (%s)", tmpl); 207 } 208 if (nagcount >= MAXNAG) 209 warnx("%d more logins not using bcryt.", nagcount - MAXNAG); 210 exit(0); 211 } 212 213 void 214 nag(char* line) 215 { 216 const char *tok; 217 if (strtok(line, ":") != NULL) 218 if ((tok = strtok(NULL, ":")) != NULL) 219 if (strncmp(tok, "$2a$", 4) != 0 && 220 strncmp(tok, "$2b$", 4) != 0) { 221 nagcount++; 222 if (nagcount <= MAXNAG) 223 warnx("%s doesn't use bcrypt." 224 " Update the password.", line); 225 } 226 } 227