Annotation of src/usr.bin/htpasswd/htpasswd.c, Revision 1.16
1.16 ! awolk 1: /* $OpenBSD: htpasswd.c,v 1.15 2015/11/05 20:07:15 florian Exp $ */
1.1 florian 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>
1.5 florian 22: #include <fcntl.h>
1.1 florian 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: {
1.7 florian 39: fprintf(stderr, "usage:\t%s [file] login\n", __progname);
1.10 florian 40: fprintf(stderr, "\t%s -I [file]\n", __progname);
1.1 florian 41: exit(1);
42: }
43:
1.6 florian 44: #define MAXNAG 5
45: int nagcount;
46:
1.1 florian 47: int
48: main(int argc, char** argv)
49: {
1.16 ! awolk 50: char tmpl[sizeof("/tmp/htpasswd-XXXXXXXXXX")];
1.12 deraadt 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;
1.1 florian 57: ssize_t linelen;
1.2 florian 58: mode_t old_umask;
1.1 florian 59:
1.14 millert 60: if (pledge("stdio rpath wpath cpath flock tmppath tty", NULL) == -1)
1.13 deraadt 61: err(1, "pledge");
1.1 florian 62:
1.10 florian 63: while ((c = getopt(argc, argv, "I")) != -1) {
1.7 florian 64: switch (c) {
1.10 florian 65: case 'I':
1.11 deraadt 66: batch = 1;
1.7 florian 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();
1.15 florian 83: else if (pledge("stdio", NULL) == -1)
84: err(1, "pledge");
85:
1.7 florian 86: if ((linelen = getline(&line, &linesize, stdin)) == -1)
1.9 florian 87: err(1, "cannot read login:password from stdin");
1.7 florian 88: line[linelen-1] = '\0';
89:
90: if ((tok = strstr(line, ":")) == NULL)
1.9 florian 91: errx(1, "cannot find ':' in input");
1.7 florian 92: *tok++ = '\0';
93:
94: if ((loginlen = asprintf(&login, "%s:", line)) == -1)
1.1 florian 95: err(1, "asprintf");
96:
1.7 florian 97: if (strlcpy(pass, tok, sizeof(pass)) >= sizeof(pass))
1.9 florian 98: errx(1, "password too long");
1.7 florian 99: } else {
100:
101: switch (argc) {
102: case 1:
1.15 florian 103: if (pledge("stdio tty", NULL) == -1)
104: err(1, "pledge");
1.7 florian 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:
1.1 florian 133: explicit_bzero(pass2, sizeof(pass2));
134: }
135:
1.16 ! awolk 136: if (crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)) != 0)
! 137: err(1, "can't generate hash");
1.1 florian 138: explicit_bzero(pass, sizeof(pass));
139:
140: if (file == NULL)
141: printf("%s%s\n", login, hash);
142: else {
143: if ((in = fopen(file, "r+")) == NULL) {
144: if (errno == ENOENT) {
1.2 florian 145: old_umask = umask(S_IXUSR|
146: S_IWGRP|S_IRGRP|S_IXGRP|
147: S_IWOTH|S_IROTH|S_IXOTH);
1.1 florian 148: if ((out = fopen(file, "w")) == NULL)
149: err(1, "cannot open password file for"
150: " reading or writing");
1.2 florian 151: umask(old_umask);
1.1 florian 152: } else
1.4 benno 153: err(1, "cannot open password file for"
154: " reading or writing");
1.5 florian 155: } else
156: if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1)
157: errx(1, "cannot lock password file");
158:
1.1 florian 159: /* file already exits, copy content and filter login out */
160: if (out == NULL) {
161: strlcpy(tmpl, "/tmp/htpasswd-XXXXXXXXXX", sizeof(tmpl));
162: if ((fd = mkstemp(tmpl)) == -1)
163: err(1, "mkstemp");
164:
165: if ((out = fdopen(fd, "w+")) == NULL)
166: err(1, "cannot open tempfile");
167:
168: while ((linelen = getline(&line, &linesize, in))
169: != -1) {
170: if (strncmp(line, login, loginlen) != 0) {
171: if (fprintf(out, "%s", line) == -1)
1.9 florian 172: errx(1, "cannot write to temp "
1.1 florian 173: "file");
174: nag(line);
175: }
176: }
177: }
178: if (fprintf(out, "%s%s\n", login, hash) == -1)
1.9 florian 179: errx(1, "cannot write new password hash");
1.1 florian 180:
181: /* file already exists, overwrite it */
182: if (in != NULL) {
183: if (fseek(in, 0, SEEK_SET) == -1)
184: err(1, "cannot seek in password file");
1.4 benno 185: if (fseek(out, 0, SEEK_SET) == -1)
186: err(1, "cannot seek in temp file");
1.1 florian 187: if (ftruncate(fileno(in), 0) == -1)
188: err(1, "cannot truncate password file");
189: while ((linelen = getline(&line, &linesize, out))
190: != -1)
191: if (fprintf(in, "%s", line) == -1)
1.9 florian 192: errx(1, "cannot write to password "
193: "file");
1.1 florian 194: if (fclose(in) == EOF)
195: err(1, "cannot close password file");
196: }
197: if (fclose(out) == EOF) {
198: if (in != NULL)
199: err(1, "cannot close temp file");
200: else
201: err(1, "cannot close password file");
202: }
203: if (in != NULL && unlink(tmpl) == -1)
204: err(1, "cannot delete temp file (%s)", tmpl);
205: }
1.6 florian 206: if (nagcount >= MAXNAG)
1.9 florian 207: warnx("%d more logins not using bcryt.", nagcount - MAXNAG);
1.1 florian 208: exit(0);
209: }
210:
211: void
212: nag(char* line)
213: {
1.9 florian 214: const char *tok;
1.1 florian 215: if (strtok(line, ":") != NULL)
216: if ((tok = strtok(NULL, ":")) != NULL)
217: if (strncmp(tok, "$2a$", 4) != 0 &&
1.6 florian 218: strncmp(tok, "$2b$", 4) != 0) {
219: nagcount++;
220: if (nagcount <= MAXNAG)
1.9 florian 221: warnx("%s doesn't use bcrypt."
222: " Update the password.", line);
1.6 florian 223: }
1.1 florian 224: }