Introduction
Introduction Statistics Contact Development Disclaimer Help
quark-digestauth-20201101-dff98c0.diff - sites - public wiki contents of suckle…
git clone git://git.suckless.org/sites
Log
Files
Refs
---
quark-digestauth-20201101-dff98c0.diff (25809B)
---
1 From 2d855b934bf0ba2bdcaf7c818a4a9b1a836b2c59 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: Sun, 1 Nov 2020 00:57:31 +0000
5 Subject: [PATCH] Add Digest auth support
6
7 This follows RFC 7616, but only MD5 algorithm and auth qop are supported.
8 ---
9 Makefile | 3 +-
10 config.def.h | 2 +-
11 http.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++--
12 http.h | 28 ++++-
13 main.c | 79 ++++++++++++--
14 md5.c | 148 ++++++++++++++++++++++++++
15 md5.h | 18 ++++
16 quark.1 | 26 +++++
17 util.h | 14 +++
18 9 files changed, 586 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 da0e458..52fc3db 100644
24 --- a/Makefile
25 +++ b/Makefile
26 @@ -4,13 +4,14 @@
27
28 include config.mk
29
30 -COMPONENTS = data http queue sock util
31 +COMPONENTS = data http md5 queue 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 queue.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 dc32290..1f99722 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 @@ -549,21 +555,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 @@ -809,14 +991,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 @@ -854,17 +1085,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 @@ -883,4 +1119,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 e26cc77..65dacf4 100644
530 --- a/main.c
531 +++ b/main.c
532 @@ -66,11 +66,17 @@ serve_connection(struct connection *c, const struct …
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 @@ -78,7 +84,7 @@ serve_connection(struct connection *c, const struct se…
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 @@ -88,16 +94,16 @@ serve_connection(struct connection *c, const struct …
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 @@ -151,6 +157,21 @@ response:
582 }
583 err:
584 logmsg(c);
585 +
586 + /* don't cleanup if we keep the connection alive */
587 + if (c->res.keep_alive) {
588 + /*
589 + * if the length is unspecified, a keep-alive connectio…
590 + * wait timeout: kill the connection to avoid it
591 + */
592 + if (c->res.field[RES_CONTENT_LENGTH][0] == '\0') {
593 + c->res.status = S_INTERNAL_SERVER_ERROR;
594 + } else {
595 + c->state = C_START;
596 + return;
597 + }
598 + }
599 +
600 close_connection(c);
601 }
602
603 @@ -296,6 +317,7 @@ thread_method(void *data)
604 * we are "stuck" at
605 */
606 switch(c->state) {
607 + case C_START:
608 case C_RECV_HEADER:
609 if (queue_mod_fd(qfd, c->fd,
610 QUEUE_EVENT_IN,
611 @@ -463,7 +485,8 @@ static void
612 usage(void)
613 {
614 const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l]…
615 - "[-i file] [-v vhost] ... [-m map] ...";
616 + "[-i file] [-v vhost] ... [-m map] ... "
617 + "[-r realm] ... [-a account] ...";
618
619 die("usage: %s -p port [-h host] %s\n"
620 " %s -U file [-p port] %s", argv0,
621 @@ -479,6 +502,7 @@ main(int argc, char *argv[])
622 struct server srv = {
623 .docindex = "index.html",
624 };
625 + struct realm *realm;
626 size_t i;
627 int *insock = NULL, status = 0;
628 const char *err;
629 @@ -492,6 +516,29 @@ main(int argc, char *argv[])
630 char *group = "nogroup";
631
632 ARGBEGIN {
633 + case 'a':
634 + if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok…
635 + !tok[2]) {
636 + usage();
637 + }
638 + realm = NULL;
639 + for (i = 0; i < srv.realm_len; i++) {
640 + if (!strcmp(srv.realm[i].name, tok[0])) {
641 + realm = &(srv.realm[i]);
642 + break;
643 + }
644 + }
645 + if (!realm) {
646 + die("Realm '%s' not found", tok[0]);
647 + }
648 + if (!(realm->account = reallocarray(realm->account,
649 + ++realm->account_len,
650 + sizeof(struct account)…
651 + die("reallocarray:");
652 + }
653 + realm->account[realm->account_len - 1].username = tok[1…
654 + realm->account[realm->account_len - 1].crypt = tok[2…
655 + break;
656 case 'd':
657 servedir = EARGF(usage());
658 break;
659 @@ -539,6 +586,24 @@ main(int argc, char *argv[])
660 case 'p':
661 srv.port = EARGF(usage());
662 break;
663 + case 'r':
664 + if (spacetok(EARGF(usage()), tok, 2) || !tok[0] || !tok…
665 + usage();
666 + }
667 + errno = 0;
668 + if (!(grp = getgrnam(tok[0]))) {
669 + die("getgrnam '%s': %s", tok[0] ? tok[0] : "nul…
670 + errno ? strerror(errno) : "Entry not found"…
671 + }
672 + if (!(srv.realm = reallocarray(srv.realm, ++srv.realm_l…
673 + sizeof(struct realm)))) {
674 + die("reallocarray:");
675 + }
676 + srv.realm[srv.realm_len - 1].gid = grp->gr_gid;
677 + srv.realm[srv.realm_len - 1].name = tok[1];
678 + srv.realm[srv.realm_len - 1].account = NULL;
679 + srv.realm[srv.realm_len - 1].account_len = 0;
680 + break;
681 case 'U':
682 udsname = EARGF(usage());
683 break;
684 diff --git a/md5.c b/md5.c
685 new file mode 100644
686 index 0000000..f56a501
687 --- /dev/null
688 +++ b/md5.c
689 @@ -0,0 +1,148 @@
690 +/* public domain md5 implementation based on rfc1321 and libtomcrypt */
691 +#include <stdint.h>
692 +#include <string.h>
693 +
694 +#include "md5.h"
695 +
696 +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)…
697 +#define F(x,y,z) (z ^ (x & (y ^ z)))
698 +#define G(x,y,z) (y ^ (z & (y ^ x)))
699 +#define H(x,y,z) (x ^ y ^ z)
700 +#define I(x,y,z) (y ^ (x | ~z))
701 +#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b
702 +#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b
703 +#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b
704 +#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b
705 +
706 +static const uint32_t tab[64] = {
707 + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4…
708 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xf…
709 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x0…
710 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xf…
711 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4…
712 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe…
713 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8…
714 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xb…
715 +};
716 +
717 +static void
718 +processblock(struct md5 *s, const uint8_t *buf)
719 +{
720 + uint32_t i, W[16], a, b, c, d;
721 +
722 + for (i = 0; i < 16; i++) {
723 + W[i] = buf[4*i];
724 + W[i] |= (uint32_t)buf[4*i+1]<<8;
725 + W[i] |= (uint32_t)buf[4*i+2]<<16;
726 + W[i] |= (uint32_t)buf[4*i+3]<<24;
727 + }
728 +
729 + a = s->h[0];
730 + b = s->h[1];
731 + c = s->h[2];
732 + d = s->h[3];
733 +
734 + i = 0;
735 + while (i < 16) {
736 + FF(a,b,c,d, W[i], 7, tab[i]); i++;
737 + FF(d,a,b,c, W[i], 12, tab[i]); i++;
738 + FF(c,d,a,b, W[i], 17, tab[i]); i++;
739 + FF(b,c,d,a, W[i], 22, tab[i]); i++;
740 + }
741 + while (i < 32) {
742 + GG(a,b,c,d, W[(5*i+1)%16], 5, tab[i]); i++;
743 + GG(d,a,b,c, W[(5*i+1)%16], 9, tab[i]); i++;
744 + GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++;
745 + GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++;
746 + }
747 + while (i < 48) {
748 + HH(a,b,c,d, W[(3*i+5)%16], 4, tab[i]); i++;
749 + HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++;
750 + HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++;
751 + HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++;
752 + }
753 + while (i < 64) {
754 + II(a,b,c,d, W[7*i%16], 6, tab[i]); i++;
755 + II(d,a,b,c, W[7*i%16], 10, tab[i]); i++;
756 + II(c,d,a,b, W[7*i%16], 15, tab[i]); i++;
757 + II(b,c,d,a, W[7*i%16], 21, tab[i]); i++;
758 + }
759 +
760 + s->h[0] += a;
761 + s->h[1] += b;
762 + s->h[2] += c;
763 + s->h[3] += d;
764 +}
765 +
766 +static void
767 +pad(struct md5 *s)
768 +{
769 + unsigned r = s->len % 64;
770 +
771 + s->buf[r++] = 0x80;
772 + if (r > 56) {
773 + memset(s->buf + r, 0, 64 - r);
774 + r = 0;
775 + processblock(s, s->buf);
776 + }
777 + memset(s->buf + r, 0, 56 - r);
778 + s->len *= 8;
779 + s->buf[56] = s->len;
780 + s->buf[57] = s->len >> 8;
781 + s->buf[58] = s->len >> 16;
782 + s->buf[59] = s->len >> 24;
783 + s->buf[60] = s->len >> 32;
784 + s->buf[61] = s->len >> 40;
785 + s->buf[62] = s->len >> 48;
786 + s->buf[63] = s->len >> 56;
787 + processblock(s, s->buf);
788 +}
789 +
790 +void
791 +md5_init(void *ctx)
792 +{
793 + struct md5 *s = ctx;
794 + s->len = 0;
795 + s->h[0] = 0x67452301;
796 + s->h[1] = 0xefcdab89;
797 + s->h[2] = 0x98badcfe;
798 + s->h[3] = 0x10325476;
799 +}
800 +
801 +void
802 +md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH])
803 +{
804 + struct md5 *s = ctx;
805 + int i;
806 +
807 + pad(s);
808 + for (i = 0; i < 4; i++) {
809 + md[4*i] = s->h[i];
810 + md[4*i+1] = s->h[i] >> 8;
811 + md[4*i+2] = s->h[i] >> 16;
812 + md[4*i+3] = s->h[i] >> 24;
813 + }
814 +}
815 +
816 +void
817 +md5_update(void *ctx, const void *m, unsigned long len)
818 +{
819 + struct md5 *s = ctx;
820 + const uint8_t *p = m;
821 + unsigned r = s->len % 64;
822 +
823 + s->len += len;
824 + if (r) {
825 + if (len < 64 - r) {
826 + memcpy(s->buf + r, p, len);
827 + return;
828 + }
829 + memcpy(s->buf + r, p, 64 - r);
830 + len -= 64 - r;
831 + p += 64 - r;
832 + processblock(s, s->buf);
833 + }
834 + for (; len >= 64; len -= 64, p += 64)
835 + processblock(s, p);
836 + memcpy(s->buf, p, len);
837 +}
838 diff --git a/md5.h b/md5.h
839 new file mode 100644
840 index 0000000..0b5005e
841 --- /dev/null
842 +++ b/md5.h
843 @@ -0,0 +1,18 @@
844 +/* public domain md5 implementation based on rfc1321 and libtomcrypt */
845 +
846 +struct md5 {
847 + uint64_t len; /* processed message length */
848 + uint32_t h[4]; /* hash state */
849 + uint8_t buf[64]; /* message block buffer */
850 +};
851 +
852 +enum { MD5_DIGEST_LENGTH = 16 };
853 +
854 +/* reset state */
855 +void md5_init(void *ctx);
856 +/* process message */
857 +void md5_update(void *ctx, const void *m, unsigned long len);
858 +/* get message digest */
859 +/* state is ruined after sum, keep a copy if multiple sum is needed */
860 +/* part of the message might be left in s, zero it if secrecy is needed…
861 +void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]);
862 diff --git a/quark.1 b/quark.1
863 index d752cc7..2e79661 100644
864 --- a/quark.1
865 +++ b/quark.1
866 @@ -17,6 +17,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 .Nm
873 .Fl U Ar file
874 .Op Fl p Ar port
875 @@ -29,6 +31,8 @@
876 .Op Fl i Ar file
877 .Oo Fl v Ar vhost Oc ...
878 .Oo Fl m Ar map Oc ...
879 +.Oo Fl r Ar realm Oc ...
880 +.Oo Fl a Ar account Oc ...
881 .Sh DESCRIPTION
882 .Nm
883 is a simple HTTP GET/HEAD-only web server for static content.
884 @@ -38,11 +42,26 @@ explicit redirects (see
885 .Fl m ) ,
886 directory listings (see
887 .Fl l ) ,
888 +Digest authentication (RFC 7616, see
889 +.Fl r
890 +and
891 +.Fl a ) ,
892 conditional "If-Modified-Since"-requests (RFC 7232), range requests
893 (RFC 7233) and well-known URIs (RFC 8615), while refusing to serve
894 hidden files and directories.
895 .Sh OPTIONS
896 .Bl -tag -width Ds
897 +.It Fl a Ar account
898 +Add the account specified by
899 +.Ar account ,
900 +which has the form
901 +.Qq Pa realm username crypt ,
902 +where each element is separated with spaces (0x20) that can be
903 +escaped with '\\'. The
904 +.Pa crypt
905 +parameter can be generated as follows:
906 +.Pp
907 +echo -n 'username:realm:password' | md5sum | awk '{ print $1 }'
908 .It Fl d Ar dir
909 Serve
910 .Ar dir
911 @@ -90,6 +109,13 @@ In socket mode, use
912 .Ar port
913 for constructing proper virtual host
914 redirects on non-standard ports.
915 +.It Fl r Ar realm
916 +Add mapping from group to realm as specified by
917 +.Ar realm ,
918 +which has the form
919 +.Qq Pa group name ,
920 +where each element is separated with spaces (0x20) that can be
921 +escaped with '\\'.
922 .It Fl U Ar file
923 Create the UNIX-domain socket
924 .Ar file ,
925 diff --git a/util.h b/util.h
926 index 983abd2..0307a34 100644
927 --- a/util.h
928 +++ b/util.h
929 @@ -23,6 +23,18 @@ struct map {
930 char *to;
931 };
932
933 +struct account {
934 + char *username;
935 + char *crypt;
936 +};
937 +
938 +struct realm {
939 + gid_t gid;
940 + char *name;
941 + struct account *account;
942 + size_t account_len;
943 +};
944 +
945 struct server {
946 char *host;
947 char *port;
948 @@ -32,6 +44,8 @@ struct server {
949 size_t vhost_len;
950 struct map *map;
951 size_t map_len;
952 + struct realm *realm;
953 + size_t realm_len;
954 };
955
956 /* general purpose buffer */
957 --
958 2.29.2
959
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.