1 /*
2 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3 * All rights reserved
4 *
5 * "THE BEER-WARE LICENSE" (Revision 42):
6 * Sergey Lyubka wrote this file. As long as you retain this notice you
7 * can do whatever you want with this stuff. If we meet some day, and you think
8 * this stuff is worth it, you can buy me a beer in return.
9 */
10
11 #include "defs.h"
12
13 #if !defined(NO_AUTH)
14 /*
15 * Stringify binary data. Output buffer must be twice as big as input,
16 * because each byte takes 2 bytes in string representation
17 */
18 static void
bin2str(char * to,const unsigned char * p,size_t len)19 bin2str(char *to, const unsigned char *p, size_t len)
20 {
21 const char *hex = "0123456789abcdef";
22
23 for (;len--; p++) {
24 *to++ = hex[p[0] >> 4];
25 *to++ = hex[p[0] & 0x0f];
26 }
27 }
28
29 /*
30 * Return stringified MD5 hash for list of vectors.
31 * buf must point to at least 32-bytes long buffer
32 */
33 static void
md5(char * buf,...)34 md5(char *buf, ...)
35 {
36 unsigned char hash[16];
37 const struct vec *v;
38 va_list ap;
39 MD5_CTX ctx;
40 int i;
41
42 MD5Init(&ctx);
43
44 va_start(ap, buf);
45 for (i = 0; (v = va_arg(ap, const struct vec *)) != NULL; i++) {
46 assert(v->len >= 0);
47 if (v->len == 0)
48 continue;
49 if (i > 0)
50 MD5Update(&ctx, (unsigned char *) ":", 1);
51 MD5Update(&ctx,(unsigned char *)v->ptr,(unsigned int)v->len);
52 }
53 va_end(ap);
54
55 MD5Final(hash, &ctx);
56 bin2str(buf, hash, sizeof(hash));
57 }
58
59 /*
60 * Compare to vectors. Return 1 if they are equal
61 */
62 static int
vcmp(const struct vec * v1,const struct vec * v2)63 vcmp(const struct vec *v1, const struct vec *v2)
64 {
65 return (v1->len == v2->len && !memcmp(v1->ptr, v2->ptr, v1->len));
66 }
67
68 struct digest {
69 struct vec user;
70 struct vec uri;
71 struct vec nonce;
72 struct vec cnonce;
73 struct vec resp;
74 struct vec qop;
75 struct vec nc;
76 };
77
78 static const struct auth_keyword {
79 size_t offset;
80 struct vec vec;
81 } known_auth_keywords[] = {
82 {offsetof(struct digest, user), {"username=", 9}},
83 {offsetof(struct digest, cnonce), {"cnonce=", 7}},
84 {offsetof(struct digest, resp), {"response=", 9}},
85 {offsetof(struct digest, uri), {"uri=", 4}},
86 {offsetof(struct digest, qop), {"qop=", 4}},
87 {offsetof(struct digest, nc), {"nc=", 3}},
88 {offsetof(struct digest, nonce), {"nonce=", 6}},
89 {0, {NULL, 0}}
90 };
91
92 static void
parse_authorization_header(const struct vec * h,struct digest * dig)93 parse_authorization_header(const struct vec *h, struct digest *dig)
94 {
95 const unsigned char *p, *e, *s;
96 struct vec *v, vec;
97 const struct auth_keyword *kw;
98
99 (void) memset(dig, 0, sizeof(*dig));
100 p = (unsigned char *) h->ptr + 7;
101 e = (unsigned char *) h->ptr + h->len;
102
103 while (p < e) {
104
105 /* Skip spaces */
106 while (p < e && (*p == ' ' || *p == ','))
107 p++;
108
109 /* Skip to "=" */
110 for (s = p; s < e && *s != '='; )
111 s++;
112 s++;
113
114 /* Is it known keyword ? */
115 for (kw = known_auth_keywords; kw->vec.len > 0; kw++)
116 if (kw->vec.len <= s - p &&
117 !memcmp(p, kw->vec.ptr, kw->vec.len))
118 break;
119
120 if (kw->vec.len == 0)
121 v = &vec; /* Dummy placeholder */
122 else
123 v = (struct vec *) ((char *) dig + kw->offset);
124
125 if (*s == '"') {
126 p = ++s;
127 while (p < e && *p != '"')
128 p++;
129 } else {
130 p = s;
131 while (p < e && *p != ' ' && *p != ',')
132 p++;
133 }
134
135 v->ptr = (char *) s;
136 v->len = p - s;
137
138 if (*p == '"')
139 p++;
140
141 DBG(("auth field [%.*s]", v->len, v->ptr));
142 }
143 }
144
145 /*
146 * Check the user's password, return 1 if OK
147 */
148 static int
check_password(int method,const struct vec * ha1,const struct digest * digest)149 check_password(int method, const struct vec *ha1, const struct digest *digest)
150 {
151 char a2[32], resp[32];
152 struct vec vec_a2;
153
154 /* XXX Due to a bug in MSIE, we do not compare the URI */
155 /* Also, we do not check for authentication timeout */
156 if (/*strcmp(dig->uri, c->ouri) != 0 || */
157 digest->resp.len != 32 /*||
158 now - strtoul(dig->nonce, NULL, 10) > 3600 */)
159 return (0);
160
161 md5(a2, &_shttpd_known_http_methods[method], &digest->uri, NULL);
162 vec_a2.ptr = a2;
163 vec_a2.len = sizeof(a2);
164 md5(resp, ha1, &digest->nonce, &digest->nc,
165 &digest->cnonce, &digest->qop, &vec_a2, NULL);
166 DBG(("%s: uri [%.*s] expected_resp [%.*s] resp [%.*s]",
167 "check_password", digest->uri.len, digest->uri.ptr,
168 32, resp, digest->resp.len, digest->resp.ptr));
169
170 return (!memcmp(resp, digest->resp.ptr, 32));
171 }
172
173 static FILE *
open_auth_file(struct shttpd_ctx * ctx,const char * path)174 open_auth_file(struct shttpd_ctx *ctx, const char *path)
175 {
176 char name[FILENAME_MAX];
177 const char *p, *e;
178 FILE *fp = NULL;
179 int fd;
180
181 if (ctx->options[OPT_AUTH_GPASSWD] != NULL) {
182 /* Use global passwords file */
183 _shttpd_snprintf(name, sizeof(name), "%s",
184 ctx->options[OPT_AUTH_GPASSWD]);
185 } else {
186 /*
187 * Try to find .htpasswd in requested directory.
188 * Given the path, create the path to .htpasswd file
189 * in the same directory. Find the right-most
190 * directory separator character first. That would be the
191 * directory name. If directory separator character is not
192 * found, 'e' will point to 'p'.
193 */
194 for (p = path, e = p + strlen(p) - 1; e > p; e--)
195 if (IS_DIRSEP_CHAR(*e))
196 break;
197
198 /*
199 * Make up the path by concatenating directory name and
200 * .htpasswd file name.
201 */
202 (void) _shttpd_snprintf(name, sizeof(name), "%.*s/%s",
203 (int) (e - p), p, HTPASSWD);
204 }
205
206 if ((fd = _shttpd_open(name, O_RDONLY, 0)) == -1) {
207 DBG(("open_auth_file: open(%s)", name));
208 } else if ((fp = fdopen(fd, "r")) == NULL) {
209 DBG(("open_auth_file: fdopen(%s)", name));
210 (void) close(fd);
211 }
212
213 return (fp);
214 }
215
216 /*
217 * Parse the line from htpasswd file. Line should be in form of
218 * "user:domain:ha1". Fill in the vector values. Return 1 if successful.
219 */
220 static int
parse_htpasswd_line(const char * s,struct vec * user,struct vec * domain,struct vec * ha1)221 parse_htpasswd_line(const char *s, struct vec *user,
222 struct vec *domain, struct vec *ha1)
223 {
224 user->len = domain->len = ha1->len = 0;
225
226 for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++);
227 if (*s++ != ':')
228 return (0);
229
230 for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++);
231 if (*s++ != ':')
232 return (0);
233
234 for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s);
235 s++, ha1->len++);
236
237 DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr,
238 domain->len, domain->ptr, ha1->len, ha1->ptr));
239
240 return (user->len > 0 && domain->len > 0 && ha1->len > 0);
241 }
242
243 /*
244 * Authorize against the opened passwords file. Return 1 if authorized.
245 */
246 static int
authorize(struct conn * c,FILE * fp)247 authorize(struct conn *c, FILE *fp)
248 {
249 struct vec *auth_vec = &c->ch.auth.v_vec;
250 struct vec *user_vec = &c->ch.user.v_vec;
251 struct vec user, domain, ha1;
252 struct digest digest;
253 int ok = 0;
254 char line[256];
255
256 if (auth_vec->len > 20 &&
257 !_shttpd_strncasecmp(auth_vec->ptr, "Digest ", 7)) {
258
259 parse_authorization_header(auth_vec, &digest);
260 *user_vec = digest.user;
261
262 while (fgets(line, sizeof(line), fp) != NULL) {
263
264 if (!parse_htpasswd_line(line, &user, &domain, &ha1))
265 continue;
266
267 DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr,
268 domain.len, domain.ptr, ha1.len, ha1.ptr));
269
270 if (vcmp(user_vec, &user) &&
271 !memcmp(c->ctx->options[OPT_AUTH_REALM],
272 domain.ptr, domain.len)) {
273 ok = check_password(c->method, &ha1, &digest);
274 break;
275 }
276 }
277 }
278
279 return (ok);
280 }
281
282 int
_shttpd_check_authorization(struct conn * c,const char * path)283 _shttpd_check_authorization(struct conn *c, const char *path)
284 {
285 FILE *fp = NULL;
286 int len, n, authorized = 1;
287 const char *p, *s = c->ctx->options[OPT_PROTECT];
288 char protected_path[FILENAME_MAX];
289
290 FOR_EACH_WORD_IN_LIST(s, len) {
291
292 if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s)
293 continue;
294
295 if (!memcmp(c->uri, s, p - s)) {
296
297 n = s + len - p;
298 if (n > (int) sizeof(protected_path) - 1)
299 n = sizeof(protected_path) - 1;
300
301 _shttpd_strlcpy(protected_path, p + 1, n);
302
303 if ((fp = fopen(protected_path, "r")) == NULL)
304 _shttpd_elog(E_LOG, c,
305 "check_auth: cannot open %s: %s",
306 protected_path, strerror(errno));
307 break;
308 }
309 }
310
311 if (fp == NULL)
312 fp = open_auth_file(c->ctx, path);
313
314 if (fp != NULL) {
315 authorized = authorize(c, fp);
316 (void) fclose(fp);
317 }
318
319 return (authorized);
320 }
321
322 int
_shttpd_is_authorized_for_put(struct conn * c)323 _shttpd_is_authorized_for_put(struct conn *c)
324 {
325 FILE *fp;
326 int ret = 0;
327
328 if ((fp = fopen(c->ctx->options[OPT_AUTH_PUT], "r")) != NULL) {
329 ret = authorize(c, fp);
330 (void) fclose(fp);
331 }
332
333 return (ret);
334 }
335
336 void
_shttpd_send_authorization_request(struct conn * c)337 _shttpd_send_authorization_request(struct conn *c)
338 {
339 char buf[512];
340
341 (void) _shttpd_snprintf(buf, sizeof(buf), "Unauthorized\r\n"
342 "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", "
343 "nonce=\"%lu\"", c->ctx->options[OPT_AUTH_REALM],
344 (unsigned long) _shttpd_current_time);
345
346 _shttpd_send_server_error(c, 401, buf);
347 }
348
349 /*
350 * Edit the passwords file.
351 */
352 int
_shttpd_edit_passwords(const char * fname,const char * domain,const char * user,const char * pass)353 _shttpd_edit_passwords(const char *fname, const char *domain,
354 const char *user, const char *pass)
355 {
356 int ret = EXIT_SUCCESS, found = 0;
357 struct vec u, d, p;
358 char line[512], tmp[FILENAME_MAX], ha1[32];
359 FILE *fp = NULL, *fp2 = NULL;
360
361 (void) _shttpd_snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
362
363 /* Create the file if does not exist */
364 if ((fp = fopen(fname, "a+")))
365 (void) fclose(fp);
366
367 /* Open the given file and temporary file */
368 if ((fp = fopen(fname, "r")) == NULL)
369 _shttpd_elog(E_FATAL, NULL,
370 "Cannot open %s: %s", fname, strerror(errno));
371 else if ((fp2 = fopen(tmp, "w+")) == NULL)
372 _shttpd_elog(E_FATAL, NULL,
373 "Cannot open %s: %s", tmp, strerror(errno));
374
375 p.ptr = pass;
376 p.len = strlen(pass);
377
378 /* Copy the stuff to temporary file */
379 while (fgets(line, sizeof(line), fp) != NULL) {
380 u.ptr = line;
381 if ((d.ptr = strchr(line, ':')) == NULL)
382 continue;
383 u.len = d.ptr - u.ptr;
384 d.ptr++;
385 if (strchr(d.ptr, ':') == NULL)
386 continue;
387 d.len = strchr(d.ptr, ':') - d.ptr;
388
389 if ((int) strlen(user) == u.len &&
390 !memcmp(user, u.ptr, u.len) &&
391 (int) strlen(domain) == d.len &&
392 !memcmp(domain, d.ptr, d.len)) {
393 found++;
394 md5(ha1, &u, &d, &p, NULL);
395 (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
396 } else {
397 (void) fprintf(fp2, "%s", line);
398 }
399 }
400
401 /* If new user, just add it */
402 if (found == 0) {
403 u.ptr = user; u.len = strlen(user);
404 d.ptr = domain; d.len = strlen(domain);
405 md5(ha1, &u, &d, &p, NULL);
406 (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
407 }
408
409 /* Close files */
410 (void) fclose(fp);
411 (void) fclose(fp2);
412
413 /* Put the temp file in place of real file */
414 (void) _shttpd_remove(fname);
415 (void) _shttpd_rename(tmp, fname);
416
417 return (ret);
418 }
419 #endif /* NO_AUTH */
420