Annotation of src/usr.bin/htpasswd/htpasswd.c, Revision 1.6
1.6 ! florian 1: /* $OpenBSD: htpasswd.c,v 1.5 2014/03/17 22:37:53 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.3 florian 39: fprintf(stderr, "usage: %s [file] login\n", __progname);
1.1 florian 40: exit(1);
41: }
42:
1.6 ! florian 43: #define MAXNAG 5
! 44: int nagcount;
! 45:
1.1 florian 46: int
47: main(int argc, char** argv)
48: {
49: FILE *in, *out;
50: size_t linesize;
51: ssize_t linelen;
1.2 florian 52: mode_t old_umask;
1.1 florian 53: int fd, loginlen;
54: char hash[_PASSWORD_LEN], *file, *line, *login, pass[1024], pass2[1024];
55: char salt[_PASSWORD_LEN], tmpl[sizeof("/tmp/htpasswd-XXXXXXXXXX")];
56:
57: file = NULL;
58: login = NULL;
59: in = NULL;
60: out = NULL;
61: line = NULL;
62: linesize = 0;
63:
64: switch (argc) {
65: case 2:
66: if ((loginlen = asprintf(&login, "%s:", argv[1])) == -1)
67: err(1, "asprintf");
68: break;
69: case 3:
70: file = argv[1];
71: if ((loginlen = asprintf(&login, "%s:", argv[2])) == -1)
72: err(1, "asprintf");
73: break;
74: default:
75: usage();
76: /* NOT REACHED */
77: break;
78: }
79:
80: if (!readpassphrase("Password: ", pass, sizeof(pass), RPP_ECHO_OFF))
81: err(1, "unable to read password");
82: if (!readpassphrase("Retype Password: ", pass2, sizeof(pass2),
83: RPP_ECHO_OFF)) {
84: explicit_bzero(pass, sizeof(pass));
85: err(1, "unable to read password");
86: }
87: if (strcmp(pass, pass2) != 0) {
88: explicit_bzero(pass, sizeof(pass));
89: explicit_bzero(pass2, sizeof(pass2));
90: errx(1, "passwords don't match");
91: }
92:
93: explicit_bzero(pass2, sizeof(pass2));
94: if (strlcpy(salt, bcrypt_gensalt(8), sizeof(salt)) >= sizeof(salt))
95: err(1, "salt too long");
96: if (strlcpy(hash, bcrypt(pass, salt), sizeof(hash)) >= sizeof(hash))
97: err(1, "hash too long");
98: explicit_bzero(pass, sizeof(pass));
99:
100: if (file == NULL)
101: printf("%s%s\n", login, hash);
102: else {
103: if ((in = fopen(file, "r+")) == NULL) {
104: if (errno == ENOENT) {
1.2 florian 105: old_umask = umask(S_IXUSR|
106: S_IWGRP|S_IRGRP|S_IXGRP|
107: S_IWOTH|S_IROTH|S_IXOTH);
1.1 florian 108: if ((out = fopen(file, "w")) == NULL)
109: err(1, "cannot open password file for"
110: " reading or writing");
1.2 florian 111: umask(old_umask);
1.1 florian 112: } else
1.4 benno 113: err(1, "cannot open password file for"
114: " reading or writing");
1.5 florian 115: } else
116: if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1)
117: errx(1, "cannot lock password file");
118:
1.1 florian 119: /* file already exits, copy content and filter login out */
120: if (out == NULL) {
121: strlcpy(tmpl, "/tmp/htpasswd-XXXXXXXXXX", sizeof(tmpl));
122: if ((fd = mkstemp(tmpl)) == -1)
123: err(1, "mkstemp");
124:
125: if ((out = fdopen(fd, "w+")) == NULL)
126: err(1, "cannot open tempfile");
127:
128: while ((linelen = getline(&line, &linesize, in))
129: != -1) {
130: if (strncmp(line, login, loginlen) != 0) {
131: if (fprintf(out, "%s", line) == -1)
132: err(1, "cannot write to temp "
133: "file");
134: nag(line);
135: }
136: }
137: }
138: if (fprintf(out, "%s%s\n", login, hash) == -1)
139: err(1, "cannot write new password hash");
140:
141: /* file already exists, overwrite it */
142: if (in != NULL) {
143: if (fseek(in, 0, SEEK_SET) == -1)
144: err(1, "cannot seek in password file");
1.4 benno 145: if (fseek(out, 0, SEEK_SET) == -1)
146: err(1, "cannot seek in temp file");
1.1 florian 147: if (ftruncate(fileno(in), 0) == -1)
148: err(1, "cannot truncate password file");
149: while ((linelen = getline(&line, &linesize, out))
150: != -1)
151: if (fprintf(in, "%s", line) == -1)
152: err(1, "cannot write to password file");
153: if (fclose(in) == EOF)
154: err(1, "cannot close password file");
155: }
156: if (fclose(out) == EOF) {
157: if (in != NULL)
158: err(1, "cannot close temp file");
159: else
160: err(1, "cannot close password file");
161: }
162: if (in != NULL && unlink(tmpl) == -1)
163: err(1, "cannot delete temp file (%s)", tmpl);
164: }
1.6 ! florian 165: if (nagcount >= MAXNAG)
! 166: fprintf(stderr, "%d more logins not using bcryt.\n",
! 167: nagcount - MAXNAG);
1.1 florian 168: exit(0);
169: }
170:
171: void
172: nag(char* line)
173: {
174: char *tok;
175: if (strtok(line, ":") != NULL)
176: if ((tok = strtok(NULL, ":")) != NULL)
177: if (strncmp(tok, "$2a$", 4) != 0 &&
1.6 ! florian 178: strncmp(tok, "$2b$", 4) != 0) {
! 179: nagcount++;
! 180: if (nagcount <= MAXNAG)
! 181: fprintf(stderr, "%s doesn't use bcrypt."
! 182: " Update the password.\n", line);
! 183: }
1.1 florian 184: }