quark-digestauth-20200916-5d0221d.diff - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) 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 <soy.jmi2k@gmail.com>
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, struct buffer *buf)
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 *lower, size_t *upper)
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 inline
130 + * and continue the loop early, before reaching the quoted-string
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 *a1,
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(A2) */
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, struct response *res,
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_AUTHORIZATION],
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->crypt,
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(response))) {
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, struct response *res,
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 *req,
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 buffer *);
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 response *,
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->nonce, s);
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->nonce, s);
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->nonce, s);
578 if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
579 /* couldn't generate the header, we failed for good */
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 connection will
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[1] ||
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[1]) {
656 + usage();
657 + }
658 + errno = 0;
659 + if (!(grp = getgrnam(tok[0]))) {
660 + die("getgrnam '%s': %s", tok[0] ? tok[0] : "null",
661 + errno ? strerror(errno) : "Entry not found");
662 + }
663 + if (!(srv.realm = reallocarray(srv.realm, ++srv.realm_len,
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, 0x4787c62a, 0xa8304613, 0xfd469501,
699 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
700 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
701 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
702 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
703 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
704 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
705 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
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