Annotation of src/usr.bin/htpasswd/htpasswd.c, Revision 1.18
1.18 ! beck 1: /* $OpenBSD: htpasswd.c,v 1.17 2018/10/31 07:39:10 mestre 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.10 florian 60: while ((c = getopt(argc, argv, "I")) != -1) {
1.7 florian 61: switch (c) {
1.10 florian 62: case 'I':
1.11 deraadt 63: batch = 1;
1.7 florian 64: break;
65: default:
66: usage();
67: /* NOT REACHED */
68: break;
69: }
70: }
71:
72: argc -= optind;
73: argv += optind;
1.17 mestre 74:
75: if ((batch && argc == 1) || (!batch && argc == 2)) {
76: if (unveil(argv[0], "rwc") == -1)
1.18 ! beck 77: err(1, "unveil %s", argv[0]);
1.17 mestre 78: if (unveil("/tmp", "rwc") == -1)
1.18 ! beck 79: err(1, "unveil /tmp");
1.17 mestre 80: }
81: if (pledge("stdio rpath wpath cpath flock tmppath tty", NULL) == -1)
82: err(1, "pledge");
1.7 florian 83:
84: if (batch) {
85: if (argc == 1)
86: file = argv[0];
87: else if (argc > 1)
88: usage();
1.15 florian 89: else if (pledge("stdio", NULL) == -1)
90: err(1, "pledge");
91:
1.7 florian 92: if ((linelen = getline(&line, &linesize, stdin)) == -1)
1.9 florian 93: err(1, "cannot read login:password from stdin");
1.7 florian 94: line[linelen-1] = '\0';
95:
96: if ((tok = strstr(line, ":")) == NULL)
1.9 florian 97: errx(1, "cannot find ':' in input");
1.7 florian 98: *tok++ = '\0';
99:
100: if ((loginlen = asprintf(&login, "%s:", line)) == -1)
1.1 florian 101: err(1, "asprintf");
102:
1.7 florian 103: if (strlcpy(pass, tok, sizeof(pass)) >= sizeof(pass))
1.9 florian 104: errx(1, "password too long");
1.7 florian 105: } else {
106:
107: switch (argc) {
108: case 1:
1.15 florian 109: if (pledge("stdio tty", NULL) == -1)
110: err(1, "pledge");
1.7 florian 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:
1.1 florian 139: explicit_bzero(pass2, sizeof(pass2));
140: }
141:
1.16 awolk 142: if (crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)) != 0)
143: err(1, "can't generate hash");
1.1 florian 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) {
1.2 florian 151: old_umask = umask(S_IXUSR|
152: S_IWGRP|S_IRGRP|S_IXGRP|
153: S_IWOTH|S_IROTH|S_IXOTH);
1.1 florian 154: if ((out = fopen(file, "w")) == NULL)
155: err(1, "cannot open password file for"
156: " reading or writing");
1.2 florian 157: umask(old_umask);
1.1 florian 158: } else
1.4 benno 159: err(1, "cannot open password file for"
160: " reading or writing");
1.5 florian 161: } else
162: if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1)
163: errx(1, "cannot lock password file");
164:
1.1 florian 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)
1.9 florian 178: errx(1, "cannot write to temp "
1.1 florian 179: "file");
180: nag(line);
181: }
182: }
183: }
184: if (fprintf(out, "%s%s\n", login, hash) == -1)
1.9 florian 185: errx(1, "cannot write new password hash");
1.1 florian 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");
1.4 benno 191: if (fseek(out, 0, SEEK_SET) == -1)
192: err(1, "cannot seek in temp file");
1.1 florian 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)
1.9 florian 198: errx(1, "cannot write to password "
199: "file");
1.1 florian 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: }
1.6 florian 212: if (nagcount >= MAXNAG)
1.9 florian 213: warnx("%d more logins not using bcryt.", nagcount - MAXNAG);
1.1 florian 214: exit(0);
215: }
216:
217: void
218: nag(char* line)
219: {
1.9 florian 220: const char *tok;
1.1 florian 221: if (strtok(line, ":") != NULL)
222: if ((tok = strtok(NULL, ":")) != NULL)
223: if (strncmp(tok, "$2a$", 4) != 0 &&
1.6 florian 224: strncmp(tok, "$2b$", 4) != 0) {
225: nagcount++;
226: if (nagcount <= MAXNAG)
1.9 florian 227: warnx("%s doesn't use bcrypt."
228: " Update the password.", line);
1.6 florian 229: }
1.1 florian 230: }