quark-digestauth-20200916-5d0221d.diff - sites - public wiki contents of suckle… | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
quark-digestauth-20200916-5d0221d.diff (25577B) | |
--- | |
1 From e0efcece3647fad31ca2750aaf59dd39dd192496 Mon Sep 17 00:00:00 2001 | |
2 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20S=C3=A1nchez=20Garc=C3=ADa?= | |
3 <[email protected]> | |
4 Date: Thu, 29 Oct 2020 10:05:27 +0000 | |
5 Subject: [PATCH] Add Digest auth support | |
6 | |
7 This follows RFC 7616, but only MD5 algorithm and auth qop is supported. | |
8 --- | |
9 Makefile | 3 +- | |
10 config.def.h | 2 +- | |
11 http.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++-- | |
12 http.h | 28 ++++- | |
13 main.c | 77 ++++++++++++-- | |
14 md5.c | 148 ++++++++++++++++++++++++++ | |
15 md5.h | 18 ++++ | |
16 quark.1 | 26 +++++ | |
17 util.h | 14 +++ | |
18 9 files changed, 584 insertions(+), 23 deletions(-) | |
19 create mode 100644 md5.c | |
20 create mode 100644 md5.h | |
21 | |
22 diff --git a/Makefile b/Makefile | |
23 index 548e6aa..6c9e442 100644 | |
24 --- a/Makefile | |
25 +++ b/Makefile | |
26 @@ -4,13 +4,14 @@ | |
27 | |
28 include config.mk | |
29 | |
30 -COMPONENTS = data http sock util | |
31 +COMPONENTS = data http md5 sock util | |
32 | |
33 all: quark | |
34 | |
35 data.o: data.c data.h http.h util.h config.mk | |
36 http.o: http.c config.h http.h util.h config.mk | |
37 main.o: main.c arg.h data.h http.h sock.h util.h config.mk | |
38 +md5.o: md5.c md5.h config.mk | |
39 sock.o: sock.c sock.h util.h config.mk | |
40 util.o: util.c util.h config.mk | |
41 | |
42 diff --git a/config.def.h b/config.def.h | |
43 index 56f62aa..a322e7a 100644 | |
44 --- a/config.def.h | |
45 +++ b/config.def.h | |
46 @@ -2,7 +2,7 @@ | |
47 #define CONFIG_H | |
48 | |
49 #define BUFFER_SIZE 4096 | |
50 -#define FIELD_MAX 200 | |
51 +#define FIELD_MAX 500 | |
52 | |
53 /* mime-types */ | |
54 static const struct { | |
55 diff --git a/http.c b/http.c | |
56 index f1e15a4..1862dc4 100644 | |
57 --- a/http.c | |
58 +++ b/http.c | |
59 @@ -17,13 +17,16 @@ | |
60 #include <unistd.h> | |
61 | |
62 #include "config.h" | |
63 +#include "data.h" | |
64 #include "http.h" | |
65 +#include "md5.h" | |
66 #include "util.h" | |
67 | |
68 const char *req_field_str[] = { | |
69 [REQ_HOST] = "Host", | |
70 [REQ_RANGE] = "Range", | |
71 [REQ_IF_MODIFIED_SINCE] = "If-Modified-Since", | |
72 + [REQ_AUTHORIZATION] = "Authorization", | |
73 }; | |
74 | |
75 const char *req_method_str[] = { | |
76 @@ -37,6 +40,7 @@ const char *status_str[] = { | |
77 [S_MOVED_PERMANENTLY] = "Moved Permanently", | |
78 [S_NOT_MODIFIED] = "Not Modified", | |
79 [S_BAD_REQUEST] = "Bad Request", | |
80 + [S_UNAUTHORIZED] = "Unauthorized", | |
81 [S_FORBIDDEN] = "Forbidden", | |
82 [S_NOT_FOUND] = "Not Found", | |
83 [S_METHOD_NOT_ALLOWED] = "Method Not Allowed", | |
84 @@ -55,6 +59,7 @@ const char *res_field_str[] = { | |
85 [RES_CONTENT_LENGTH] = "Content-Length", | |
86 [RES_CONTENT_RANGE] = "Content-Range", | |
87 [RES_CONTENT_TYPE] = "Content-Type", | |
88 + [RES_AUTHENTICATE] = "WWW-Authenticate", | |
89 }; | |
90 | |
91 enum status | |
92 @@ -75,8 +80,9 @@ http_prepare_header_buf(const struct response *res, st… | |
93 if (buffer_appendf(buf, | |
94 "HTTP/1.1 %d %s\r\n" | |
95 "Date: %s\r\n" | |
96 - "Connection: close\r\n", | |
97 - res->status, status_str[res->status], tstmp)… | |
98 + "Connection: %s\r\n", | |
99 + res->status, status_str[res->status], tstmp, | |
100 + res->keep_alive ? "keep-alive" : "close")) { | |
101 goto err; | |
102 } | |
103 | |
104 @@ -527,21 +533,197 @@ parse_range(const char *str, size_t size, size_t … | |
105 return 0; | |
106 } | |
107 | |
108 +static enum status | |
109 +parse_auth(const char *str, struct auth *auth) | |
110 +{ | |
111 + const char *p; | |
112 + char *q; | |
113 + | |
114 + /* done if no range-string is given */ | |
115 + if (str == NULL || *str == '\0') { | |
116 + return 0; | |
117 + } | |
118 + | |
119 + /* skip method authentication statement */ | |
120 + if (strncmp(str, "Digest", sizeof("Digest") - 1)) { | |
121 + return S_BAD_REQUEST; | |
122 + } | |
123 + | |
124 + p = str + (sizeof("Digest") - 1); | |
125 + | |
126 + /* | |
127 + * Almost all the fields are quoted-string with no restrictions. | |
128 + * | |
129 + * However, some of them require special parsing, which is done… | |
130 + * and continue the loop early, before reaching the quoted-stri… | |
131 + * parser. | |
132 + */ | |
133 + while (*p) { | |
134 + /* skip leading whitespace */ | |
135 + for (++p; *p == ' ' || *p == '\t'; p++) | |
136 + ; | |
137 + | |
138 + if (!strncmp("qop=", p, | |
139 + sizeof("qop=") - 1)) { | |
140 + p += sizeof("qop=") - 1; | |
141 + q = auth->qop; | |
142 + /* "qop" is handled differently */ | |
143 + while (*p && *p != ',') { | |
144 + if (*p == '\\') { | |
145 + ++p; | |
146 + } | |
147 + *q++ = *p++; | |
148 + } | |
149 + *q = '\0'; | |
150 + | |
151 + continue; | |
152 + } else if (!strncmp("algorithm=", p, | |
153 + sizeof("algorithm=") - 1)) { | |
154 + p += sizeof("algorithm=") - 1; | |
155 + q = auth->algorithm; | |
156 + /* "algorithm" is handled differently */ | |
157 + while (*p && *p != ',') { | |
158 + if (*p == '\\') { | |
159 + ++p; | |
160 + } | |
161 + *q++ = *p++; | |
162 + } | |
163 + *q = '\0'; | |
164 + | |
165 + continue; | |
166 + } else if (!strncmp("nc=", p, | |
167 + sizeof("nc=") - 1)) { | |
168 + p += sizeof("nc=") - 1; | |
169 + q = auth->nc; | |
170 + /* "nc" is handled differently */ | |
171 + while (*p && *p != ',') { | |
172 + if (*p < '0' || *p > '9') { | |
173 + return S_BAD_REQUEST; | |
174 + } | |
175 + *q++ = *p++; | |
176 + } | |
177 + *q = '\0'; | |
178 + | |
179 + continue; | |
180 + /* these all are quoted-string */ | |
181 + } else if (!strncmp("response=\"", p, | |
182 + sizeof("response=\"") - 1)) { | |
183 + p += sizeof("response=\"") - 1; | |
184 + q = auth->response; | |
185 + } else if (!strncmp("username=\"", p, | |
186 + sizeof("username=\"") - 1)) { | |
187 + p += sizeof("username=\"") - 1; | |
188 + q = auth->username; | |
189 + } else if (!strncmp("realm=\"", p, | |
190 + sizeof("realm=\"") - 1)) { | |
191 + p += sizeof("realm=\"") - 1; | |
192 + q = auth->realm; | |
193 + } else if (!strncmp("uri=\"", p, | |
194 + sizeof("uri=\"") - 1)) { | |
195 + p += sizeof("uri=\"") - 1; | |
196 + q = auth->uri; | |
197 + } else if (!strncmp("cnonce=\"", p, | |
198 + sizeof("cnonce=\"") - 1)) { | |
199 + p += sizeof("cnonce=\"") - 1; | |
200 + q = auth->cnonce; | |
201 + } else if (!strncmp("nonce=\"", p, | |
202 + sizeof("nonce=\"") - 1)) { | |
203 + p += sizeof("nonce=\"") - 1; | |
204 + q = auth->nonce; | |
205 + } else { | |
206 + return S_BAD_REQUEST; | |
207 + } | |
208 + | |
209 + /* parse quoted-string */ | |
210 + while (*p != '"') { | |
211 + if (*p == '\\') { | |
212 + ++p; | |
213 + } | |
214 + if (!*p) { | |
215 + return S_BAD_REQUEST; | |
216 + } | |
217 + *q++ = *p++; | |
218 + } | |
219 + *q = '\0'; | |
220 + ++p; | |
221 + } | |
222 + | |
223 + /* skip trailing whitespace */ | |
224 + for (++p; *p == ' ' || *p == '\t'; p++) | |
225 + ; | |
226 + | |
227 + if (*p) { | |
228 + return S_BAD_REQUEST; | |
229 + } | |
230 + | |
231 + return 0; | |
232 +} | |
233 + | |
234 +static enum status | |
235 +prepare_digest(char response[MD5_DIGEST_LENGTH * 2 + 1], | |
236 + enum req_method method, const char *uri, const uint8_t *… | |
237 + const char *nonce, const char *nc, const char *cnonce, | |
238 + const char *qop) | |
239 +{ | |
240 + uint8_t a2[MD5_DIGEST_LENGTH], kdr[MD5_DIGEST_LENGTH]; | |
241 + char scratch[FIELD_MAX]; | |
242 + struct md5 md5; | |
243 + unsigned int i; | |
244 + char *p; | |
245 + | |
246 + /* calculate H(A2) */ | |
247 + if (esnprintf(scratch, sizeof(scratch), "%s:%s", | |
248 + req_method_str[method], uri)) { | |
249 + return S_INTERNAL_SERVER_ERROR; | |
250 + } | |
251 + | |
252 + md5_init(&md5); | |
253 + md5_update(&md5, scratch, strlen(scratch)); | |
254 + md5_sum(&md5, a2); | |
255 + | |
256 + /* calculate response */ | |
257 + if (esnprintf(scratch, sizeof(scratch), "%s:%s:%s:%s:%s:%-*x", | |
258 + a1, nonce, nc, cnonce, qop, | |
259 + MD5_DIGEST_LENGTH * 2 + 1, 0)) { | |
260 + return S_INTERNAL_SERVER_ERROR; | |
261 + } | |
262 + | |
263 + /* replace trailing string of '-' inside scratch with actual H(… | |
264 + p = &scratch[strlen(scratch) - (MD5_DIGEST_LENGTH * 2 + 1)]; | |
265 + for (i = 0; i < sizeof(a2); i++) { | |
266 + sprintf(&p[i << 1], "%02x", a2[i]); | |
267 + } | |
268 + | |
269 + md5_init(&md5); | |
270 + md5_update(&md5, scratch, strlen(scratch)); | |
271 + md5_sum(&md5, kdr); | |
272 + | |
273 + for (i = 0; i < sizeof(kdr); i++) { | |
274 + sprintf(&response[i << 1], "%02x", kdr[i]); | |
275 + } | |
276 + | |
277 + return 0; | |
278 +} | |
279 + | |
280 #undef RELPATH | |
281 #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) | |
282 | |
283 void | |
284 -http_prepare_response(const struct request *req, struct response *res, | |
285 - const struct server *srv) | |
286 +http_prepare_response(struct request *req, struct response *res, | |
287 + char nonce[FIELD_MAX], const struct server *srv) | |
288 { | |
289 enum status s; | |
290 struct in6_addr addr; | |
291 struct stat st; | |
292 struct tm tm = { 0 }; | |
293 + struct auth auth = { 0 }; | |
294 struct vhost *vhost; | |
295 + struct realm *realm; | |
296 + struct account *account; | |
297 size_t len, i; | |
298 int hasport, ipv6host; | |
299 static char realuri[PATH_MAX], tmpuri[PATH_MAX]; | |
300 + char response[MD5_DIGEST_LENGTH * 2 + 1]; | |
301 char *p, *mime; | |
302 const char *targethost; | |
303 | |
304 @@ -787,14 +969,63 @@ http_prepare_response(const struct request *req, s… | |
305 } | |
306 } | |
307 | |
308 - /* fill response struct */ | |
309 - res->type = RESTYPE_FILE; | |
310 - | |
311 /* check if file is readable */ | |
312 res->status = (access(res->path, R_OK)) ? S_FORBIDDEN : | |
313 (req->field[REQ_RANGE][0] != '\0') ? | |
314 S_PARTIAL_CONTENT : S_OK; | |
315 | |
316 + /* check if the client is authorized */ | |
317 + realm = NULL; | |
318 + if (srv->realm) { | |
319 + for (i = 0; i < srv->realm_len; i++) { | |
320 + if (srv->realm[i].gid == st.st_gid) { | |
321 + realm = &(srv->realm[i]); | |
322 + break; | |
323 + } | |
324 + } | |
325 + req->realm = realm; | |
326 + /* if the file belongs to a realm */ | |
327 + if (i < srv->realm_len) { | |
328 + if (req->field[REQ_AUTHORIZATION][0] == '\0') { | |
329 + s = S_UNAUTHORIZED; | |
330 + goto err; | |
331 + } | |
332 + if ((s = parse_auth(req->field[REQ_AUTHORIZATIO… | |
333 + &auth))) { | |
334 + goto err; | |
335 + } | |
336 + /* look for the requested user */ | |
337 + for (i = 0; i < realm->account_len; i++) { | |
338 + if (!strcmp(auth.username, | |
339 + realm->account[i].username)… | |
340 + account = &(realm->account[i]); | |
341 + break; | |
342 + } | |
343 + } | |
344 + if (i == realm->account_len) { | |
345 + s = S_UNAUTHORIZED; | |
346 + goto err; | |
347 + } | |
348 + if ((s = prepare_digest(response, req->method, | |
349 + auth.uri, | |
350 + (uint8_t *)account->cry… | |
351 + nonce, auth.nc, | |
352 + auth.cnonce, auth.qop))… | |
353 + goto err; | |
354 + } | |
355 + if (strcmp(auth.nonce, nonce)) { | |
356 + req->stale = 1; | |
357 + } | |
358 + if (strncmp(response, auth.response, sizeof(res… | |
359 + s = S_UNAUTHORIZED; | |
360 + goto err; | |
361 + } | |
362 + } | |
363 + } | |
364 + | |
365 + /* fill response struct */ | |
366 + res->type = RESTYPE_FILE; | |
367 + | |
368 if (esnprintf(res->field[RES_ACCEPT_RANGES], | |
369 sizeof(res->field[RES_ACCEPT_RANGES]), | |
370 "%s", "bytes")) { | |
371 @@ -832,17 +1063,22 @@ http_prepare_response(const struct request *req, … | |
372 | |
373 return; | |
374 err: | |
375 - http_prepare_error_response(req, res, s); | |
376 + http_prepare_error_response(req, res, nonce, s); | |
377 } | |
378 | |
379 void | |
380 http_prepare_error_response(const struct request *req, | |
381 - struct response *res, enum status s) | |
382 + struct response *res, char nonce[FIELD_MAX], | |
383 + enum status s) | |
384 { | |
385 + struct timespec ts; | |
386 + struct buffer buf; | |
387 + size_t progress; | |
388 + | |
389 /* used later */ | |
390 (void)req; | |
391 | |
392 - /* empty all response fields */ | |
393 + /* empty all fields */ | |
394 memset(res, 0, sizeof(*res)); | |
395 | |
396 res->type = RESTYPE_ERROR; | |
397 @@ -861,4 +1097,39 @@ http_prepare_error_response(const struct request *… | |
398 res->status = S_INTERNAL_SERVER_ERROR; | |
399 } | |
400 } | |
401 + | |
402 + if (res->status == S_UNAUTHORIZED) { | |
403 + clock_gettime(CLOCK_MONOTONIC, &ts); | |
404 + if (esnprintf(nonce, FIELD_MAX, | |
405 + "%lus, %luns, %s", | |
406 + ts.tv_sec, ts.tv_nsec, | |
407 + req->realm->name)) { | |
408 + res->status = S_INTERNAL_SERVER_ERROR; | |
409 + } | |
410 + if (esnprintf(res->field[RES_AUTHENTICATE], | |
411 + sizeof(res->field[RES_AUTHENTICATE]), | |
412 + "Digest " | |
413 + "realm=\"%s\", " | |
414 + "qop=\"auth\", " | |
415 + "algorithm=MD5, " | |
416 + "stale=%s, " | |
417 + "nonce=\"%s\"", | |
418 + req->realm->name, | |
419 + req->stale ? "true" : "false", | |
420 + nonce)) { | |
421 + res->status = S_INTERNAL_SERVER_ERROR; | |
422 + } else { | |
423 + res->keep_alive = 1; | |
424 + } | |
425 + } | |
426 + | |
427 + progress = 0; | |
428 + if (data_prepare_error_buf(res, &buf, &progress) | |
429 + || esnprintf(res->field[RES_CONTENT_LENGTH], | |
430 + sizeof(res->field[RES_CONTENT_LENGTH]), | |
431 + "%zu", buf.len)) { | |
432 + res->field[RES_CONTENT_LENGTH][0] = '\0'; | |
433 + res->keep_alive = 0; | |
434 + s = S_INTERNAL_SERVER_ERROR; | |
435 + } | |
436 } | |
437 diff --git a/http.h b/http.h | |
438 index bfaa807..215bb8f 100644 | |
439 --- a/http.h | |
440 +++ b/http.h | |
441 @@ -12,6 +12,7 @@ enum req_field { | |
442 REQ_HOST, | |
443 REQ_RANGE, | |
444 REQ_IF_MODIFIED_SINCE, | |
445 + REQ_AUTHORIZATION, | |
446 NUM_REQ_FIELDS, | |
447 }; | |
448 | |
449 @@ -28,6 +29,8 @@ extern const char *req_method_str[]; | |
450 struct request { | |
451 enum req_method method; | |
452 char uri[PATH_MAX]; | |
453 + struct realm *realm; | |
454 + int stale; | |
455 char field[NUM_REQ_FIELDS][FIELD_MAX]; | |
456 }; | |
457 | |
458 @@ -37,6 +40,7 @@ enum status { | |
459 S_MOVED_PERMANENTLY = 301, | |
460 S_NOT_MODIFIED = 304, | |
461 S_BAD_REQUEST = 400, | |
462 + S_UNAUTHORIZED = 401, | |
463 S_FORBIDDEN = 403, | |
464 S_NOT_FOUND = 404, | |
465 S_METHOD_NOT_ALLOWED = 405, | |
466 @@ -57,6 +61,7 @@ enum res_field { | |
467 RES_CONTENT_LENGTH, | |
468 RES_CONTENT_RANGE, | |
469 RES_CONTENT_TYPE, | |
470 + RES_AUTHENTICATE, | |
471 NUM_RES_FIELDS, | |
472 }; | |
473 | |
474 @@ -72,6 +77,7 @@ enum res_type { | |
475 struct response { | |
476 enum res_type type; | |
477 enum status status; | |
478 + int keep_alive; | |
479 char field[NUM_RES_FIELDS][FIELD_MAX]; | |
480 char uri[PATH_MAX]; | |
481 char path[PATH_MAX]; | |
482 @@ -83,6 +89,7 @@ struct response { | |
483 | |
484 enum conn_state { | |
485 C_VACANT, | |
486 + C_START, | |
487 C_RECV_HEADER, | |
488 C_SEND_HEADER, | |
489 C_SEND_BODY, | |
490 @@ -91,6 +98,7 @@ enum conn_state { | |
491 | |
492 struct connection { | |
493 enum conn_state state; | |
494 + char nonce[FIELD_MAX]; | |
495 int fd; | |
496 struct sockaddr_storage ia; | |
497 struct request req; | |
498 @@ -99,13 +107,25 @@ struct connection { | |
499 size_t progress; | |
500 }; | |
501 | |
502 +struct auth { | |
503 + char response[FIELD_MAX]; | |
504 + char username[FIELD_MAX]; | |
505 + char realm[FIELD_MAX]; | |
506 + char uri[FIELD_MAX]; | |
507 + char qop[FIELD_MAX]; | |
508 + char cnonce[FIELD_MAX]; | |
509 + char nonce[FIELD_MAX]; | |
510 + char algorithm[FIELD_MAX]; | |
511 + char nc[FIELD_MAX]; | |
512 +}; | |
513 + | |
514 enum status http_prepare_header_buf(const struct response *, struct buf… | |
515 enum status http_send_buf(int, struct buffer *); | |
516 enum status http_recv_header(int, struct buffer *, int *); | |
517 enum status http_parse_header(const char *, struct request *); | |
518 -void http_prepare_response(const struct request *, struct response *, | |
519 - const struct server *); | |
520 -void http_prepare_error_response(const struct request *, | |
521 - struct response *, enum status); | |
522 +void http_prepare_response(struct request *, struct response *, | |
523 + char nonce[FIELD_MAX], const struct server *… | |
524 +void http_prepare_error_response(const struct request *, struct respons… | |
525 + char nonce[FIELD_MAX], enum status); | |
526 | |
527 #endif /* HTTP_H */ | |
528 diff --git a/main.c b/main.c | |
529 index d64774b..b45ad15 100644 | |
530 --- a/main.c | |
531 +++ b/main.c | |
532 @@ -60,11 +60,17 @@ serve(struct connection *c, const struct server *srv) | |
533 | |
534 switch (c->state) { | |
535 case C_VACANT: | |
536 + /* we were passed a "fresh" connection, reset all state… | |
537 + | |
538 + c->state = C_START; | |
539 + /* fallthrough */ | |
540 + case C_START: | |
541 /* | |
542 - * we were passed a "fresh" connection which should now | |
543 - * try to receive the header, reset buf beforehand | |
544 + * we start handling a request, so we first must try to | |
545 + * receive the header, reset buf beforehand | |
546 */ | |
547 memset(&c->buf, 0, sizeof(c->buf)); | |
548 + c->progress = 0; | |
549 | |
550 c->state = C_RECV_HEADER; | |
551 /* fallthrough */ | |
552 @@ -72,7 +78,7 @@ serve(struct connection *c, const struct server *srv) | |
553 /* receive header */ | |
554 done = 0; | |
555 if ((s = http_recv_header(c->fd, &c->buf, &done))) { | |
556 - http_prepare_error_response(&c->req, &c->res, s… | |
557 + http_prepare_error_response(&c->req, &c->res, c… | |
558 goto response; | |
559 } | |
560 if (!done) { | |
561 @@ -82,16 +88,16 @@ serve(struct connection *c, const struct server *srv) | |
562 | |
563 /* parse header */ | |
564 if ((s = http_parse_header(c->buf.data, &c->req))) { | |
565 - http_prepare_error_response(&c->req, &c->res, s… | |
566 + http_prepare_error_response(&c->req, &c->res, c… | |
567 goto response; | |
568 } | |
569 | |
570 /* prepare response struct */ | |
571 - http_prepare_response(&c->req, &c->res, srv); | |
572 + http_prepare_response(&c->req, &c->res, c->nonce, srv); | |
573 response: | |
574 /* generate response header */ | |
575 if ((s = http_prepare_header_buf(&c->res, &c->buf))) { | |
576 - http_prepare_error_response(&c->req, &c->res, s… | |
577 + http_prepare_error_response(&c->req, &c->res, c… | |
578 if ((s = http_prepare_header_buf(&c->res, &c->b… | |
579 /* couldn't generate the header, we fai… | |
580 c->res.status = s; | |
581 @@ -146,6 +152,20 @@ response: | |
582 err: | |
583 logmsg(c); | |
584 | |
585 + /* don't cleanup if we keep the connection alive */ | |
586 + if (c->res.keep_alive) { | |
587 + /* | |
588 + * if the length is unspecified, a keep-alive connectio… | |
589 + * wait timeout: kill the connection to avoid it | |
590 + */ | |
591 + if (c->res.field[RES_CONTENT_LENGTH][0] == '\0') { | |
592 + c->res.status = S_INTERNAL_SERVER_ERROR; | |
593 + } else { | |
594 + c->state = C_START; | |
595 + return; | |
596 + } | |
597 + } | |
598 + | |
599 /* clean up and finish */ | |
600 shutdown(c->fd, SHUT_RD); | |
601 shutdown(c->fd, SHUT_WR); | |
602 @@ -257,7 +277,8 @@ static void | |
603 usage(void) | |
604 { | |
605 const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l]… | |
606 - "[-i file] [-v vhost] ... [-m map] ..."; | |
607 + "[-i file] [-v vhost] ... [-m map] ... " | |
608 + "[-r realm] ... [-a account] ..."; | |
609 | |
610 die("usage: %s -p port [-h host] %s\n" | |
611 " %s -U file [-p port] %s", argv0, | |
612 @@ -273,6 +294,7 @@ main(int argc, char *argv[]) | |
613 struct server srv = { | |
614 .docindex = "index.html", | |
615 }; | |
616 + struct realm *realm; | |
617 size_t i; | |
618 int insock, status = 0; | |
619 const char *err; | |
620 @@ -285,6 +307,29 @@ main(int argc, char *argv[]) | |
621 char *group = "nogroup"; | |
622 | |
623 ARGBEGIN { | |
624 + case 'a': | |
625 + if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok… | |
626 + !tok[2]) { | |
627 + usage(); | |
628 + } | |
629 + realm = NULL; | |
630 + for (i = 0; i < srv.realm_len; i++) { | |
631 + if (!strcmp(srv.realm[i].name, tok[0])) { | |
632 + realm = &(srv.realm[i]); | |
633 + break; | |
634 + } | |
635 + } | |
636 + if (!realm) { | |
637 + die("Realm '%s' not found", tok[0]); | |
638 + } | |
639 + if (!(realm->account = reallocarray(realm->account, | |
640 + ++realm->account_len, | |
641 + sizeof(struct account)… | |
642 + die("reallocarray:"); | |
643 + } | |
644 + realm->account[realm->account_len - 1].username = tok[1… | |
645 + realm->account[realm->account_len - 1].crypt = tok[2… | |
646 + break; | |
647 case 'd': | |
648 servedir = EARGF(usage()); | |
649 break; | |
650 @@ -324,6 +369,24 @@ main(int argc, char *argv[]) | |
651 case 'p': | |
652 srv.port = EARGF(usage()); | |
653 break; | |
654 + case 'r': | |
655 + if (spacetok(EARGF(usage()), tok, 2) || !tok[0] || !tok… | |
656 + usage(); | |
657 + } | |
658 + errno = 0; | |
659 + if (!(grp = getgrnam(tok[0]))) { | |
660 + die("getgrnam '%s': %s", tok[0] ? tok[0] : "nul… | |
661 + errno ? strerror(errno) : "Entry not found"… | |
662 + } | |
663 + if (!(srv.realm = reallocarray(srv.realm, ++srv.realm_l… | |
664 + sizeof(struct realm)))) { | |
665 + die("reallocarray:"); | |
666 + } | |
667 + srv.realm[srv.realm_len - 1].gid = grp->gr_gid; | |
668 + srv.realm[srv.realm_len - 1].name = tok[1]; | |
669 + srv.realm[srv.realm_len - 1].account = NULL; | |
670 + srv.realm[srv.realm_len - 1].account_len = 0; | |
671 + break; | |
672 case 'U': | |
673 udsname = EARGF(usage()); | |
674 break; | |
675 diff --git a/md5.c b/md5.c | |
676 new file mode 100644 | |
677 index 0000000..f56a501 | |
678 --- /dev/null | |
679 +++ b/md5.c | |
680 @@ -0,0 +1,148 @@ | |
681 +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ | |
682 +#include <stdint.h> | |
683 +#include <string.h> | |
684 + | |
685 +#include "md5.h" | |
686 + | |
687 +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)… | |
688 +#define F(x,y,z) (z ^ (x & (y ^ z))) | |
689 +#define G(x,y,z) (y ^ (z & (y ^ x))) | |
690 +#define H(x,y,z) (x ^ y ^ z) | |
691 +#define I(x,y,z) (y ^ (x | ~z)) | |
692 +#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b | |
693 +#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b | |
694 +#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b | |
695 +#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b | |
696 + | |
697 +static const uint32_t tab[64] = { | |
698 + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4… | |
699 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xf… | |
700 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x0… | |
701 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xf… | |
702 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4… | |
703 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe… | |
704 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8… | |
705 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xb… | |
706 +}; | |
707 + | |
708 +static void | |
709 +processblock(struct md5 *s, const uint8_t *buf) | |
710 +{ | |
711 + uint32_t i, W[16], a, b, c, d; | |
712 + | |
713 + for (i = 0; i < 16; i++) { | |
714 + W[i] = buf[4*i]; | |
715 + W[i] |= (uint32_t)buf[4*i+1]<<8; | |
716 + W[i] |= (uint32_t)buf[4*i+2]<<16; | |
717 + W[i] |= (uint32_t)buf[4*i+3]<<24; | |
718 + } | |
719 + | |
720 + a = s->h[0]; | |
721 + b = s->h[1]; | |
722 + c = s->h[2]; | |
723 + d = s->h[3]; | |
724 + | |
725 + i = 0; | |
726 + while (i < 16) { | |
727 + FF(a,b,c,d, W[i], 7, tab[i]); i++; | |
728 + FF(d,a,b,c, W[i], 12, tab[i]); i++; | |
729 + FF(c,d,a,b, W[i], 17, tab[i]); i++; | |
730 + FF(b,c,d,a, W[i], 22, tab[i]); i++; | |
731 + } | |
732 + while (i < 32) { | |
733 + GG(a,b,c,d, W[(5*i+1)%16], 5, tab[i]); i++; | |
734 + GG(d,a,b,c, W[(5*i+1)%16], 9, tab[i]); i++; | |
735 + GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++; | |
736 + GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++; | |
737 + } | |
738 + while (i < 48) { | |
739 + HH(a,b,c,d, W[(3*i+5)%16], 4, tab[i]); i++; | |
740 + HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++; | |
741 + HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++; | |
742 + HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++; | |
743 + } | |
744 + while (i < 64) { | |
745 + II(a,b,c,d, W[7*i%16], 6, tab[i]); i++; | |
746 + II(d,a,b,c, W[7*i%16], 10, tab[i]); i++; | |
747 + II(c,d,a,b, W[7*i%16], 15, tab[i]); i++; | |
748 + II(b,c,d,a, W[7*i%16], 21, tab[i]); i++; | |
749 + } | |
750 + | |
751 + s->h[0] += a; | |
752 + s->h[1] += b; | |
753 + s->h[2] += c; | |
754 + s->h[3] += d; | |
755 +} | |
756 + | |
757 +static void | |
758 +pad(struct md5 *s) | |
759 +{ | |
760 + unsigned r = s->len % 64; | |
761 + | |
762 + s->buf[r++] = 0x80; | |
763 + if (r > 56) { | |
764 + memset(s->buf + r, 0, 64 - r); | |
765 + r = 0; | |
766 + processblock(s, s->buf); | |
767 + } | |
768 + memset(s->buf + r, 0, 56 - r); | |
769 + s->len *= 8; | |
770 + s->buf[56] = s->len; | |
771 + s->buf[57] = s->len >> 8; | |
772 + s->buf[58] = s->len >> 16; | |
773 + s->buf[59] = s->len >> 24; | |
774 + s->buf[60] = s->len >> 32; | |
775 + s->buf[61] = s->len >> 40; | |
776 + s->buf[62] = s->len >> 48; | |
777 + s->buf[63] = s->len >> 56; | |
778 + processblock(s, s->buf); | |
779 +} | |
780 + | |
781 +void | |
782 +md5_init(void *ctx) | |
783 +{ | |
784 + struct md5 *s = ctx; | |
785 + s->len = 0; | |
786 + s->h[0] = 0x67452301; | |
787 + s->h[1] = 0xefcdab89; | |
788 + s->h[2] = 0x98badcfe; | |
789 + s->h[3] = 0x10325476; | |
790 +} | |
791 + | |
792 +void | |
793 +md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]) | |
794 +{ | |
795 + struct md5 *s = ctx; | |
796 + int i; | |
797 + | |
798 + pad(s); | |
799 + for (i = 0; i < 4; i++) { | |
800 + md[4*i] = s->h[i]; | |
801 + md[4*i+1] = s->h[i] >> 8; | |
802 + md[4*i+2] = s->h[i] >> 16; | |
803 + md[4*i+3] = s->h[i] >> 24; | |
804 + } | |
805 +} | |
806 + | |
807 +void | |
808 +md5_update(void *ctx, const void *m, unsigned long len) | |
809 +{ | |
810 + struct md5 *s = ctx; | |
811 + const uint8_t *p = m; | |
812 + unsigned r = s->len % 64; | |
813 + | |
814 + s->len += len; | |
815 + if (r) { | |
816 + if (len < 64 - r) { | |
817 + memcpy(s->buf + r, p, len); | |
818 + return; | |
819 + } | |
820 + memcpy(s->buf + r, p, 64 - r); | |
821 + len -= 64 - r; | |
822 + p += 64 - r; | |
823 + processblock(s, s->buf); | |
824 + } | |
825 + for (; len >= 64; len -= 64, p += 64) | |
826 + processblock(s, p); | |
827 + memcpy(s->buf, p, len); | |
828 +} | |
829 diff --git a/md5.h b/md5.h | |
830 new file mode 100644 | |
831 index 0000000..0b5005e | |
832 --- /dev/null | |
833 +++ b/md5.h | |
834 @@ -0,0 +1,18 @@ | |
835 +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ | |
836 + | |
837 +struct md5 { | |
838 + uint64_t len; /* processed message length */ | |
839 + uint32_t h[4]; /* hash state */ | |
840 + uint8_t buf[64]; /* message block buffer */ | |
841 +}; | |
842 + | |
843 +enum { MD5_DIGEST_LENGTH = 16 }; | |
844 + | |
845 +/* reset state */ | |
846 +void md5_init(void *ctx); | |
847 +/* process message */ | |
848 +void md5_update(void *ctx, const void *m, unsigned long len); | |
849 +/* get message digest */ | |
850 +/* state is ruined after sum, keep a copy if multiple sum is needed */ | |
851 +/* part of the message might be left in s, zero it if secrecy is needed… | |
852 +void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]); | |
853 diff --git a/quark.1 b/quark.1 | |
854 index 6e0e5f8..3394639 100644 | |
855 --- a/quark.1 | |
856 +++ b/quark.1 | |
857 @@ -16,6 +16,8 @@ | |
858 .Op Fl i Ar file | |
859 .Oo Fl v Ar vhost Oc ... | |
860 .Oo Fl m Ar map Oc ... | |
861 +.Oo Fl r Ar realm Oc ... | |
862 +.Oo Fl a Ar account Oc ... | |
863 .Nm | |
864 .Fl U Ar file | |
865 .Op Fl p Ar port | |
866 @@ -27,6 +29,8 @@ | |
867 .Op Fl i Ar file | |
868 .Oo Fl v Ar vhost Oc ... | |
869 .Oo Fl m Ar map Oc ... | |
870 +.Oo Fl r Ar realm Oc ... | |
871 +.Oo Fl a Ar account Oc ... | |
872 .Sh DESCRIPTION | |
873 .Nm | |
874 is a simple HTTP GET/HEAD-only web server for static content. | |
875 @@ -36,11 +40,26 @@ explicit redirects (see | |
876 .Fl m ) , | |
877 directory listings (see | |
878 .Fl l ) , | |
879 +Digest authentication (RFC 7616, see | |
880 +.Fl r | |
881 +and | |
882 +.Fl a ) , | |
883 conditional "If-Modified-Since"-requests (RFC 7232), range requests | |
884 (RFC 7233) and well-known URIs (RFC 8615), while refusing to serve | |
885 hidden files and directories. | |
886 .Sh OPTIONS | |
887 .Bl -tag -width Ds | |
888 +.It Fl a Ar account | |
889 +Add the account specified by | |
890 +.Ar account , | |
891 +which has the form | |
892 +.Qq Pa realm username crypt , | |
893 +where each element is separated with spaces (0x20) that can be | |
894 +escaped with '\\'. The | |
895 +.Pa crypt | |
896 +parameter can be generated as follows: | |
897 +.Pp | |
898 +echo -n 'username:realm:password' | md5sum | awk '{ print $1 }' | |
899 .It Fl d Ar dir | |
900 Serve | |
901 .Ar dir | |
902 @@ -92,6 +111,13 @@ In socket mode, use | |
903 .Ar port | |
904 for constructing proper virtual host | |
905 redirects on non-standard ports. | |
906 +.It Fl r Ar realm | |
907 +Add mapping from group to realm as specified by | |
908 +.Ar realm , | |
909 +which has the form | |
910 +.Qq Pa group name , | |
911 +where each element is separated with spaces (0x20) that can be | |
912 +escaped with '\\'. | |
913 .It Fl U Ar file | |
914 Create the UNIX-domain socket | |
915 .Ar file , | |
916 diff --git a/util.h b/util.h | |
917 index 983abd2..0307a34 100644 | |
918 --- a/util.h | |
919 +++ b/util.h | |
920 @@ -23,6 +23,18 @@ struct map { | |
921 char *to; | |
922 }; | |
923 | |
924 +struct account { | |
925 + char *username; | |
926 + char *crypt; | |
927 +}; | |
928 + | |
929 +struct realm { | |
930 + gid_t gid; | |
931 + char *name; | |
932 + struct account *account; | |
933 + size_t account_len; | |
934 +}; | |
935 + | |
936 struct server { | |
937 char *host; | |
938 char *port; | |
939 @@ -32,6 +44,8 @@ struct server { | |
940 size_t vhost_len; | |
941 struct map *map; | |
942 size_t map_len; | |
943 + struct realm *realm; | |
944 + size_t realm_len; | |
945 }; | |
946 | |
947 /* general purpose buffer */ | |
948 -- | |
949 2.29.0 | |
950 |