sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

commit bd9ab73ac75cc2c47964a88e46019840e71d6614
parent 738e94cbb317298cdb66b40b226346cf429fda60
Author: José Miguel Sánchez García <soy.jmi2k@gmail.com>
Date:   Fri, 30 Oct 2020 14:42:58 +0000

[quark][patch][digestauth] add digestauth patch

Diffstat:
Atools.suckless.org/quark/patches/digestauth/index.md | 21+++++++++++++++++++++
Atools.suckless.org/quark/patches/digestauth/quark-digestauth-20200916-5d0221d.diff | 947+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 968 insertions(+), 0 deletions(-)

diff --git a/tools.suckless.org/quark/patches/digestauth/index.md b/tools.suckless.org/quark/patches/digestauth/index.md @@ -0,0 +1,21 @@ +Digest auth +=========== + +Description +----------- +This patch adds support for Digest auth to quark. It follows RFC 7616, but +with some limitations: + +* SHA-256 is unsupported, only MD5 can be used. If we lived in an ideal world, + SHA-256 Digest auth would be supported by browsers since mid-2010s. Turns + out that we aren't that lucky, so MD5 it is. +* Only auth qop mode is supported. If you want to protect the integrity of + your connection, better use a TLS tunnel. + +Download +-------- +* [quark-digestauth-20200916-5d0221d.diff](quark-digestauth-20200916-5d0221d.diff) + +Author +------ +* José Miguel Sánchez García <soy.jmi2k AT gmail DOT com> diff --git a/tools.suckless.org/quark/patches/digestauth/quark-digestauth-20200916-5d0221d.diff b/tools.suckless.org/quark/patches/digestauth/quark-digestauth-20200916-5d0221d.diff @@ -0,0 +1,947 @@ +From d006445858e709222093baaddb71d582654dc0e4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Miguel=20S=C3=A1nchez=20Garc=C3=ADa?= + <soy.jmi2k@gmail.com> +Date: Thu, 29 Oct 2020 10:05:27 +0000 +Subject: [PATCH] Add Digest auth support + +This follows RFC 7616, but only MD5 algorithm and auth qop is supported. +--- + Makefile | 3 +- + config.def.h | 2 +- + http.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++-- + http.h | 27 ++++- + main.c | 77 ++++++++++++-- + md5.c | 148 ++++++++++++++++++++++++++ + md5.h | 18 ++++ + quark.1 | 26 +++++ + util.h | 14 +++ + 9 files changed, 581 insertions(+), 23 deletions(-) + create mode 100644 md5.c + create mode 100644 md5.h + +diff --git a/Makefile b/Makefile +index 548e6aa..6c9e442 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,13 +4,14 @@ + + include config.mk + +-COMPONENTS = data http sock util ++COMPONENTS = data http md5 sock util + + all: quark + + data.o: data.c data.h http.h util.h config.mk + http.o: http.c config.h http.h util.h config.mk + main.o: main.c arg.h data.h http.h sock.h util.h config.mk ++md5.o: md5.c md5.h config.mk + sock.o: sock.c sock.h util.h config.mk + util.o: util.c util.h config.mk + +diff --git a/config.def.h b/config.def.h +index 56f62aa..a322e7a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,7 +2,7 @@ + #define CONFIG_H + + #define BUFFER_SIZE 4096 +-#define FIELD_MAX 200 ++#define FIELD_MAX 500 + + /* mime-types */ + static const struct { +diff --git a/http.c b/http.c +index f1e15a4..4ceef04 100644 +--- a/http.c ++++ b/http.c +@@ -17,13 +17,16 @@ + #include <unistd.h> + + #include "config.h" ++#include "data.h" + #include "http.h" ++#include "md5.h" + #include "util.h" + + const char *req_field_str[] = { + [REQ_HOST] = "Host", + [REQ_RANGE] = "Range", + [REQ_IF_MODIFIED_SINCE] = "If-Modified-Since", ++ [REQ_AUTHORIZATION] = "Authorization", + }; + + const char *req_method_str[] = { +@@ -37,6 +40,7 @@ const char *status_str[] = { + [S_MOVED_PERMANENTLY] = "Moved Permanently", + [S_NOT_MODIFIED] = "Not Modified", + [S_BAD_REQUEST] = "Bad Request", ++ [S_UNAUTHORIZED] = "Unauthorized", + [S_FORBIDDEN] = "Forbidden", + [S_NOT_FOUND] = "Not Found", + [S_METHOD_NOT_ALLOWED] = "Method Not Allowed", +@@ -55,6 +59,7 @@ const char *res_field_str[] = { + [RES_CONTENT_LENGTH] = "Content-Length", + [RES_CONTENT_RANGE] = "Content-Range", + [RES_CONTENT_TYPE] = "Content-Type", ++ [RES_AUTHENTICATE] = "WWW-Authenticate", + }; + + enum status +@@ -75,8 +80,9 @@ http_prepare_header_buf(const struct response *res, struct buffer *buf) + if (buffer_appendf(buf, + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" +- "Connection: close\r\n", +- res->status, status_str[res->status], tstmp)) { ++ "Connection: %s\r\n", ++ res->status, status_str[res->status], tstmp, ++ res->keep_alive ? "keep-alive" : "close")) { + goto err; + } + +@@ -527,21 +533,197 @@ parse_range(const char *str, size_t size, size_t *lower, size_t *upper) + return 0; + } + ++static enum status ++parse_auth(const char *str, struct auth *auth) ++{ ++ const char *p; ++ char *q; ++ ++ /* done if no range-string is given */ ++ if (str == NULL || *str == '\0') { ++ return 0; ++ } ++ ++ /* skip method authentication statement */ ++ if (strncmp(str, "Digest", sizeof("Digest") - 1)) { ++ return S_BAD_REQUEST; ++ } ++ ++ p = str + (sizeof("Digest") - 1); ++ ++ /* ++ * Almost all the fields are quoted-string with no restrictions. ++ * ++ * However, some of them require special parsing, which is done inline ++ * and continue the loop early, before reaching the quoted-string ++ * parser. ++ */ ++ while (*p) { ++ /* skip leading whitespace */ ++ for (++p; *p == ' ' || *p == '\t'; p++) ++ ; ++ ++ if (!strncmp("qop=", p, ++ sizeof("qop=") - 1)) { ++ p += sizeof("qop=") - 1; ++ q = auth->qop; ++ /* "qop" is handled differently */ ++ while (*p && *p != ',') { ++ if (*p == '\\') { ++ ++p; ++ } ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ ++ continue; ++ } else if (!strncmp("algorithm=", p, ++ sizeof("algorithm=") - 1)) { ++ p += sizeof("algorithm=") - 1; ++ q = auth->algorithm; ++ /* "algorithm" is handled differently */ ++ while (*p && *p != ',') { ++ if (*p == '\\') { ++ ++p; ++ } ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ ++ continue; ++ } else if (!strncmp("nc=", p, ++ sizeof("nc=") - 1)) { ++ p += sizeof("nc=") - 1; ++ q = auth->nc; ++ /* "nc" is handled differently */ ++ while (*p && *p != ',') { ++ if (*p < '0' || *p > '9') { ++ return S_BAD_REQUEST; ++ } ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ ++ continue; ++ /* these all are quoted-string */ ++ } else if (!strncmp("response=\"", p, ++ sizeof("response=\"") - 1)) { ++ p += sizeof("response=\"") - 1; ++ q = auth->response; ++ } else if (!strncmp("username=\"", p, ++ sizeof("username=\"") - 1)) { ++ p += sizeof("username=\"") - 1; ++ q = auth->username; ++ } else if (!strncmp("realm=\"", p, ++ sizeof("realm=\"") - 1)) { ++ p += sizeof("realm=\"") - 1; ++ q = auth->realm; ++ } else if (!strncmp("uri=\"", p, ++ sizeof("uri=\"") - 1)) { ++ p += sizeof("uri=\"") - 1; ++ q = auth->uri; ++ } else if (!strncmp("cnonce=\"", p, ++ sizeof("cnonce=\"") - 1)) { ++ p += sizeof("cnonce=\"") - 1; ++ q = auth->cnonce; ++ } else if (!strncmp("nonce=\"", p, ++ sizeof("nonce=\"") - 1)) { ++ p += sizeof("nonce=\"") - 1; ++ q = auth->nonce; ++ } else { ++ return S_BAD_REQUEST; ++ } ++ ++ /* parse quoted-string */ ++ while (*p != '"') { ++ if (*p == '\\') { ++ ++p; ++ } ++ if (!*p) { ++ return S_BAD_REQUEST; ++ } ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ ++p; ++ } ++ ++ /* skip trailing whitespace */ ++ for (++p; *p == ' ' || *p == '\t'; p++) ++ ; ++ ++ if (*p) { ++ return S_BAD_REQUEST; ++ } ++ ++ return 0; ++} ++ ++static enum status ++prepare_digest(char response[MD5_DIGEST_LENGTH * 2 + 1], ++ enum req_method method, const char *uri, const uint8_t *a1, ++ const char *nonce, const char *nc, const char *cnonce, ++ const char *qop) ++{ ++ uint8_t a2[MD5_DIGEST_LENGTH], kdr[MD5_DIGEST_LENGTH]; ++ char scratch[FIELD_MAX]; ++ struct md5 md5; ++ unsigned int i; ++ char *p; ++ ++ /* calculate H(A2) */ ++ if (esnprintf(scratch, sizeof(scratch), "%s:%s", ++ req_method_str[method], uri)) { ++ return S_INTERNAL_SERVER_ERROR; ++ } ++ ++ md5_init(&md5); ++ md5_update(&md5, scratch, strlen(scratch)); ++ md5_sum(&md5, a2); ++ ++ /* calculate response */ ++ if (esnprintf(scratch, sizeof(scratch), "%s:%s:%s:%s:%s:%-*x", ++ a1, nonce, nc, cnonce, qop, ++ MD5_DIGEST_LENGTH * 2 + 1, 0)) { ++ return S_INTERNAL_SERVER_ERROR; ++ } ++ ++ /* replace trailing string of '-' inside scratch with actual H(A2) */ ++ p = &scratch[strlen(scratch) - (MD5_DIGEST_LENGTH * 2 + 1)]; ++ for (i = 0; i < sizeof(a2); i++) { ++ sprintf(&p[i << 1], "%02x", a2[i]); ++ } ++ ++ md5_init(&md5); ++ md5_update(&md5, scratch, strlen(scratch)); ++ md5_sum(&md5, kdr); ++ ++ for (i = 0; i < sizeof(kdr); i++) { ++ sprintf(&response[i << 1], "%02x", kdr[i]); ++ } ++ ++ return 0; ++} ++ + #undef RELPATH + #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) + + void +-http_prepare_response(const struct request *req, struct response *res, +- const struct server *srv) ++http_prepare_response(struct request *req, struct response *res, ++ char nonce[FIELD_MAX], const struct server *srv) + { + enum status s; + struct in6_addr addr; + struct stat st; + struct tm tm = { 0 }; ++ struct auth auth = { 0 }; + struct vhost *vhost; ++ struct realm *realm; ++ struct account *account; + size_t len, i; + int hasport, ipv6host; + static char realuri[PATH_MAX], tmpuri[PATH_MAX]; ++ char response[MD5_DIGEST_LENGTH * 2 + 1]; + char *p, *mime; + const char *targethost; + +@@ -787,14 +969,62 @@ http_prepare_response(const struct request *req, struct response *res, + } + } + +- /* fill response struct */ +- res->type = RESTYPE_FILE; +- + /* check if file is readable */ + res->status = (access(res->path, R_OK)) ? S_FORBIDDEN : + (req->field[REQ_RANGE][0] != '\0') ? + S_PARTIAL_CONTENT : S_OK; + ++ /* check if the client is authorized */ ++ realm = NULL; ++ if (srv->realm) { ++ for (i = 0; i < srv->realm_len; i++) { ++ if (srv->realm[i].gid == st.st_gid) { ++ realm = &(srv->realm[i]); ++ break; ++ } ++ } ++ req->realm = realm; ++ /* if the file belongs to a realm */ ++ if (i < srv->realm_len) { ++ if (req->field[REQ_AUTHORIZATION][0] == '\0') { ++ s = S_UNAUTHORIZED; ++ goto err; ++ } ++ if ((s = parse_auth(req->field[REQ_AUTHORIZATION], ++ &auth))) { ++ goto err; ++ } ++ /* look for the requested user */ ++ for (i = 0; i < realm->account_len; i++) { ++ if (!strcmp(auth.username, ++ realm->account[i].username)) { ++ account = &(realm->account[i]); ++ break; ++ } ++ } ++ if (i == realm->account_len) { ++ s = S_UNAUTHORIZED; ++ goto err; ++ } ++ if ((s = prepare_digest(response, req->method, ++ auth.uri, ++ (uint8_t *)account->crypt, ++ nonce, auth.nc, ++ auth.cnonce, auth.qop))) { ++ goto err; ++ } ++ printf("client nonce: %s\n", auth.nonce); ++ printf("server nonce: %s\n", nonce); ++ if (strncmp(response, auth.response, sizeof(response))) { ++ s = S_UNAUTHORIZED; ++ goto err; ++ } ++ } ++ } ++ ++ /* fill response struct */ ++ res->type = RESTYPE_FILE; ++ + if (esnprintf(res->field[RES_ACCEPT_RANGES], + sizeof(res->field[RES_ACCEPT_RANGES]), + "%s", "bytes")) { +@@ -832,17 +1062,22 @@ http_prepare_response(const struct request *req, struct response *res, + + return; + err: +- http_prepare_error_response(req, res, s); ++ http_prepare_error_response(req, res, nonce, s); + } + + void + http_prepare_error_response(const struct request *req, +- struct response *res, enum status s) ++ struct response *res, char nonce[FIELD_MAX], ++ enum status s) + { ++ struct timespec ts; ++ struct buffer buf; ++ size_t progress; ++ + /* used later */ + (void)req; + +- /* empty all response fields */ ++ /* empty all fields */ + memset(res, 0, sizeof(*res)); + + res->type = RESTYPE_ERROR; +@@ -861,4 +1096,38 @@ http_prepare_error_response(const struct request *req, + res->status = S_INTERNAL_SERVER_ERROR; + } + } ++ ++ if (res->status == S_UNAUTHORIZED) { ++ clock_gettime(CLOCK_MONOTONIC, &ts); ++ if (esnprintf(nonce, FIELD_MAX, ++ "%lus, %luns, %s", ++ ts.tv_sec, ts.tv_nsec, ++ req->realm->name)) { ++ res->status = S_INTERNAL_SERVER_ERROR; ++ } ++ if (esnprintf(res->field[RES_AUTHENTICATE], ++ sizeof(res->field[RES_AUTHENTICATE]), ++ "Digest " ++ "realm=\"%s\", " ++ "qop=\"auth\", " ++ "algorithm=MD5, " ++ "stale=false, " ++ "nonce=\"%s\"", ++ req->realm->name, ++ nonce)) { ++ res->status = S_INTERNAL_SERVER_ERROR; ++ } else { ++ res->keep_alive = 1; ++ } ++ } ++ ++ progress = 0; ++ if (data_prepare_error_buf(res, &buf, &progress) ++ || esnprintf(res->field[RES_CONTENT_LENGTH], ++ sizeof(res->field[RES_CONTENT_LENGTH]), ++ "%zu", buf.len)) { ++ res->field[RES_CONTENT_LENGTH][0] = '\0'; ++ res->keep_alive = 0; ++ s = S_INTERNAL_SERVER_ERROR; ++ } + } +diff --git a/http.h b/http.h +index bfaa807..12de2eb 100644 +--- a/http.h ++++ b/http.h +@@ -12,6 +12,7 @@ enum req_field { + REQ_HOST, + REQ_RANGE, + REQ_IF_MODIFIED_SINCE, ++ REQ_AUTHORIZATION, + NUM_REQ_FIELDS, + }; + +@@ -28,6 +29,7 @@ extern const char *req_method_str[]; + struct request { + enum req_method method; + char uri[PATH_MAX]; ++ struct realm *realm; + char field[NUM_REQ_FIELDS][FIELD_MAX]; + }; + +@@ -37,6 +39,7 @@ enum status { + S_MOVED_PERMANENTLY = 301, + S_NOT_MODIFIED = 304, + S_BAD_REQUEST = 400, ++ S_UNAUTHORIZED = 401, + S_FORBIDDEN = 403, + S_NOT_FOUND = 404, + S_METHOD_NOT_ALLOWED = 405, +@@ -57,6 +60,7 @@ enum res_field { + RES_CONTENT_LENGTH, + RES_CONTENT_RANGE, + RES_CONTENT_TYPE, ++ RES_AUTHENTICATE, + NUM_RES_FIELDS, + }; + +@@ -72,6 +76,7 @@ enum res_type { + struct response { + enum res_type type; + enum status status; ++ int keep_alive; + char field[NUM_RES_FIELDS][FIELD_MAX]; + char uri[PATH_MAX]; + char path[PATH_MAX]; +@@ -83,6 +88,7 @@ struct response { + + enum conn_state { + C_VACANT, ++ C_START, + C_RECV_HEADER, + C_SEND_HEADER, + C_SEND_BODY, +@@ -91,6 +97,7 @@ enum conn_state { + + struct connection { + enum conn_state state; ++ char nonce[FIELD_MAX]; + int fd; + struct sockaddr_storage ia; + struct request req; +@@ -99,13 +106,25 @@ struct connection { + size_t progress; + }; + ++struct auth { ++ char response[FIELD_MAX]; ++ char username[FIELD_MAX]; ++ char realm[FIELD_MAX]; ++ char uri[FIELD_MAX]; ++ char qop[FIELD_MAX]; ++ char cnonce[FIELD_MAX]; ++ char nonce[FIELD_MAX]; ++ char algorithm[FIELD_MAX]; ++ char nc[FIELD_MAX]; ++}; ++ + enum status http_prepare_header_buf(const struct response *, struct buffer *); + enum status http_send_buf(int, struct buffer *); + enum status http_recv_header(int, struct buffer *, int *); + enum status http_parse_header(const char *, struct request *); +-void http_prepare_response(const struct request *, struct response *, +- const struct server *); +-void http_prepare_error_response(const struct request *, +- struct response *, enum status); ++void http_prepare_response(struct request *, struct response *, ++ char nonce[FIELD_MAX], const struct server *); ++void http_prepare_error_response(const struct request *, struct response *, ++ char nonce[FIELD_MAX], enum status); + + #endif /* HTTP_H */ +diff --git a/main.c b/main.c +index d64774b..b23e165 100644 +--- a/main.c ++++ b/main.c +@@ -60,11 +60,17 @@ serve(struct connection *c, const struct server *srv) + + switch (c->state) { + case C_VACANT: ++ /* we were passed a "fresh" connection, reset all state */ ++ ++ c->state = C_START; ++ /* fallthrough */ ++ case C_START: + /* +- * we were passed a "fresh" connection which should now +- * try to receive the header, reset buf beforehand ++ * we start handling a request, so we first must try to ++ * receive the header, reset buf beforehand + */ + memset(&c->buf, 0, sizeof(c->buf)); ++ c->progress = 0; + + c->state = C_RECV_HEADER; + /* fallthrough */ +@@ -72,7 +78,7 @@ serve(struct connection *c, const struct server *srv) + /* receive header */ + done = 0; + if ((s = http_recv_header(c->fd, &c->buf, &done))) { +- http_prepare_error_response(&c->req, &c->res, s); ++ http_prepare_error_response(&c->req, &c->res, c->nonce, s); + goto response; + } + if (!done) { +@@ -82,16 +88,16 @@ serve(struct connection *c, const struct server *srv) + + /* parse header */ + if ((s = http_parse_header(c->buf.data, &c->req))) { +- http_prepare_error_response(&c->req, &c->res, s); ++ http_prepare_error_response(&c->req, &c->res, c->nonce, s); + goto response; + } + + /* prepare response struct */ +- http_prepare_response(&c->req, &c->res, srv); ++ http_prepare_response(&c->req, &c->res, c->nonce, srv); + response: + /* generate response header */ + if ((s = http_prepare_header_buf(&c->res, &c->buf))) { +- http_prepare_error_response(&c->req, &c->res, s); ++ http_prepare_error_response(&c->req, &c->res, c->nonce, s); + if ((s = http_prepare_header_buf(&c->res, &c->buf))) { + /* couldn't generate the header, we failed for good */ + c->res.status = s; +@@ -146,6 +152,20 @@ response: + err: + logmsg(c); + ++ /* don't cleanup if we keep the connection alive */ ++ if (c->res.keep_alive) { ++ /* ++ * if the length is unspecified, a keep-alive connection will ++ * wait timeout: kill the connection to avoid it ++ */ ++ if (c->res.field[RES_CONTENT_LENGTH][0] == '\0') { ++ c->res.status = S_INTERNAL_SERVER_ERROR; ++ } else { ++ c->state = C_START; ++ return; ++ } ++ } ++ + /* clean up and finish */ + shutdown(c->fd, SHUT_RD); + shutdown(c->fd, SHUT_WR); +@@ -257,7 +277,8 @@ static void + usage(void) + { + const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] " +- "[-i file] [-v vhost] ... [-m map] ..."; ++ "[-i file] [-v vhost] ... [-m map] ... " ++ "[-r realm] ... [-a account] ..."; + + die("usage: %s -p port [-h host] %s\n" + " %s -U file [-p port] %s", argv0, +@@ -273,6 +294,7 @@ main(int argc, char *argv[]) + struct server srv = { + .docindex = "index.html", + }; ++ struct realm *realm; + size_t i; + int insock, status = 0; + const char *err; +@@ -285,6 +307,29 @@ main(int argc, char *argv[]) + char *group = "nogroup"; + + ARGBEGIN { ++ case 'a': ++ if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1] || ++ !tok[2]) { ++ usage(); ++ } ++ realm = NULL; ++ for (i = 0; i < srv.realm_len; i++) { ++ if (!strcmp(srv.realm[i].name, tok[0])) { ++ realm = &(srv.realm[i]); ++ break; ++ } ++ } ++ if (!realm) { ++ die("Realm '%s' not found", tok[0]); ++ } ++ if (!(realm->account = reallocarray(realm->account, ++ ++realm->account_len, ++ sizeof(struct account)))) { ++ die("reallocarray:"); ++ } ++ realm->account[i].username = tok[1]; ++ realm->account[i].crypt = tok[2]; ++ break; + case 'd': + servedir = EARGF(usage()); + break; +@@ -324,6 +369,24 @@ main(int argc, char *argv[]) + case 'p': + srv.port = EARGF(usage()); + break; ++ case 'r': ++ if (spacetok(EARGF(usage()), tok, 2) || !tok[0] || !tok[1]) { ++ usage(); ++ } ++ errno = 0; ++ if (!(grp = getgrnam(tok[0]))) { ++ die("getgrnam '%s': %s", tok[0] ? tok[0] : "null", ++ errno ? strerror(errno) : "Entry not found"); ++ } ++ if (!(srv.realm = reallocarray(srv.realm, ++srv.realm_len, ++ sizeof(struct realm)))) { ++ die("reallocarray:"); ++ } ++ srv.realm[srv.realm_len - 1].gid = grp->gr_gid; ++ srv.realm[srv.realm_len - 1].name = tok[1]; ++ srv.realm[srv.realm_len - 1].account = NULL; ++ srv.realm[srv.realm_len - 1].account_len = 0; ++ break; + case 'U': + udsname = EARGF(usage()); + break; +diff --git a/md5.c b/md5.c +new file mode 100644 +index 0000000..f56a501 +--- /dev/null ++++ b/md5.c +@@ -0,0 +1,148 @@ ++/* public domain md5 implementation based on rfc1321 and libtomcrypt */ ++#include <stdint.h> ++#include <string.h> ++ ++#include "md5.h" ++ ++static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); } ++#define F(x,y,z) (z ^ (x & (y ^ z))) ++#define G(x,y,z) (y ^ (z & (y ^ x))) ++#define H(x,y,z) (x ^ y ^ z) ++#define I(x,y,z) (y ^ (x | ~z)) ++#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b ++#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b ++#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b ++#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b ++ ++static const uint32_t tab[64] = { ++ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, ++ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, ++ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, ++ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, ++ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, ++ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, ++ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, ++ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 ++}; ++ ++static void ++processblock(struct md5 *s, const uint8_t *buf) ++{ ++ uint32_t i, W[16], a, b, c, d; ++ ++ for (i = 0; i < 16; i++) { ++ W[i] = buf[4*i]; ++ W[i] |= (uint32_t)buf[4*i+1]<<8; ++ W[i] |= (uint32_t)buf[4*i+2]<<16; ++ W[i] |= (uint32_t)buf[4*i+3]<<24; ++ } ++ ++ a = s->h[0]; ++ b = s->h[1]; ++ c = s->h[2]; ++ d = s->h[3]; ++ ++ i = 0; ++ while (i < 16) { ++ FF(a,b,c,d, W[i], 7, tab[i]); i++; ++ FF(d,a,b,c, W[i], 12, tab[i]); i++; ++ FF(c,d,a,b, W[i], 17, tab[i]); i++; ++ FF(b,c,d,a, W[i], 22, tab[i]); i++; ++ } ++ while (i < 32) { ++ GG(a,b,c,d, W[(5*i+1)%16], 5, tab[i]); i++; ++ GG(d,a,b,c, W[(5*i+1)%16], 9, tab[i]); i++; ++ GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++; ++ GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++; ++ } ++ while (i < 48) { ++ HH(a,b,c,d, W[(3*i+5)%16], 4, tab[i]); i++; ++ HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++; ++ HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++; ++ HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++; ++ } ++ while (i < 64) { ++ II(a,b,c,d, W[7*i%16], 6, tab[i]); i++; ++ II(d,a,b,c, W[7*i%16], 10, tab[i]); i++; ++ II(c,d,a,b, W[7*i%16], 15, tab[i]); i++; ++ II(b,c,d,a, W[7*i%16], 21, tab[i]); i++; ++ } ++ ++ s->h[0] += a; ++ s->h[1] += b; ++ s->h[2] += c; ++ s->h[3] += d; ++} ++ ++static void ++pad(struct md5 *s) ++{ ++ unsigned r = s->len % 64; ++ ++ s->buf[r++] = 0x80; ++ if (r > 56) { ++ memset(s->buf + r, 0, 64 - r); ++ r = 0; ++ processblock(s, s->buf); ++ } ++ memset(s->buf + r, 0, 56 - r); ++ s->len *= 8; ++ s->buf[56] = s->len; ++ s->buf[57] = s->len >> 8; ++ s->buf[58] = s->len >> 16; ++ s->buf[59] = s->len >> 24; ++ s->buf[60] = s->len >> 32; ++ s->buf[61] = s->len >> 40; ++ s->buf[62] = s->len >> 48; ++ s->buf[63] = s->len >> 56; ++ processblock(s, s->buf); ++} ++ ++void ++md5_init(void *ctx) ++{ ++ struct md5 *s = ctx; ++ s->len = 0; ++ s->h[0] = 0x67452301; ++ s->h[1] = 0xefcdab89; ++ s->h[2] = 0x98badcfe; ++ s->h[3] = 0x10325476; ++} ++ ++void ++md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]) ++{ ++ struct md5 *s = ctx; ++ int i; ++ ++ pad(s); ++ for (i = 0; i < 4; i++) { ++ md[4*i] = s->h[i]; ++ md[4*i+1] = s->h[i] >> 8; ++ md[4*i+2] = s->h[i] >> 16; ++ md[4*i+3] = s->h[i] >> 24; ++ } ++} ++ ++void ++md5_update(void *ctx, const void *m, unsigned long len) ++{ ++ struct md5 *s = ctx; ++ const uint8_t *p = m; ++ unsigned r = s->len % 64; ++ ++ s->len += len; ++ if (r) { ++ if (len < 64 - r) { ++ memcpy(s->buf + r, p, len); ++ return; ++ } ++ memcpy(s->buf + r, p, 64 - r); ++ len -= 64 - r; ++ p += 64 - r; ++ processblock(s, s->buf); ++ } ++ for (; len >= 64; len -= 64, p += 64) ++ processblock(s, p); ++ memcpy(s->buf, p, len); ++} +diff --git a/md5.h b/md5.h +new file mode 100644 +index 0000000..0b5005e +--- /dev/null ++++ b/md5.h +@@ -0,0 +1,18 @@ ++/* public domain md5 implementation based on rfc1321 and libtomcrypt */ ++ ++struct md5 { ++ uint64_t len; /* processed message length */ ++ uint32_t h[4]; /* hash state */ ++ uint8_t buf[64]; /* message block buffer */ ++}; ++ ++enum { MD5_DIGEST_LENGTH = 16 }; ++ ++/* reset state */ ++void md5_init(void *ctx); ++/* process message */ ++void md5_update(void *ctx, const void *m, unsigned long len); ++/* get message digest */ ++/* state is ruined after sum, keep a copy if multiple sum is needed */ ++/* part of the message might be left in s, zero it if secrecy is needed */ ++void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]); +diff --git a/quark.1 b/quark.1 +index 6e0e5f8..3394639 100644 +--- a/quark.1 ++++ b/quark.1 +@@ -16,6 +16,8 @@ + .Op Fl i Ar file + .Oo Fl v Ar vhost Oc ... + .Oo Fl m Ar map Oc ... ++.Oo Fl r Ar realm Oc ... ++.Oo Fl a Ar account Oc ... + .Nm + .Fl U Ar file + .Op Fl p Ar port +@@ -27,6 +29,8 @@ + .Op Fl i Ar file + .Oo Fl v Ar vhost Oc ... + .Oo Fl m Ar map Oc ... ++.Oo Fl r Ar realm Oc ... ++.Oo Fl a Ar account Oc ... + .Sh DESCRIPTION + .Nm + is a simple HTTP GET/HEAD-only web server for static content. +@@ -36,11 +40,26 @@ explicit redirects (see + .Fl m ) , + directory listings (see + .Fl l ) , ++Digest authentication (RFC 7616, see ++.Fl r ++and ++.Fl a ) , + conditional "If-Modified-Since"-requests (RFC 7232), range requests + (RFC 7233) and well-known URIs (RFC 8615), while refusing to serve + hidden files and directories. + .Sh OPTIONS + .Bl -tag -width Ds ++.It Fl a Ar account ++Add the account specified by ++.Ar account , ++which has the form ++.Qq Pa realm username crypt , ++where each element is separated with spaces (0x20) that can be ++escaped with '\\'. The ++.Pa crypt ++parameter can be generated as follows: ++.Pp ++echo -n 'username:realm:password' | md5sum | awk '{ print $1 }' + .It Fl d Ar dir + Serve + .Ar dir +@@ -92,6 +111,13 @@ In socket mode, use + .Ar port + for constructing proper virtual host + redirects on non-standard ports. ++.It Fl r Ar realm ++Add mapping from group to realm as specified by ++.Ar realm , ++which has the form ++.Qq Pa group name , ++where each element is separated with spaces (0x20) that can be ++escaped with '\\'. + .It Fl U Ar file + Create the UNIX-domain socket + .Ar file , +diff --git a/util.h b/util.h +index 983abd2..0307a34 100644 +--- a/util.h ++++ b/util.h +@@ -23,6 +23,18 @@ struct map { + char *to; + }; + ++struct account { ++ char *username; ++ char *crypt; ++}; ++ ++struct realm { ++ gid_t gid; ++ char *name; ++ struct account *account; ++ size_t account_len; ++}; ++ + struct server { + char *host; + char *port; +@@ -32,6 +44,8 @@ struct server { + size_t vhost_len; + struct map *map; + size_t map_len; ++ struct realm *realm; ++ size_t realm_len; + }; + + /* general purpose buffer */ +-- +2.29.0 +