[wiki] [sites] [quark][patch][digestauth] add digestauth patch || José Miguel Sánchez García

From: <git_AT_suckless.org>
Date: Fri, 30 Oct 2020 15:44:51 +0100

commit bd9ab73ac75cc2c47964a88e46019840e71d6614
Author: José Miguel Sánchez García <soy.jmi2k_AT_gmail.com>
Date: Fri Oct 30 14:42:58 2020 +0000

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

diff --git a/tools.suckless.org/quark/patches/digestauth/index.md b/tools.suckless.org/quark/patches/digestauth/index.md
new file mode 100644
index 00000000..a6e67f17
--- /dev/null
+++ b/tools.suckless.org/quark/patches/digestauth/index.md
_AT_@ -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
new file mode 100644
index 00000000..d1abdfd3
--- /dev/null
+++ b/tools.suckless.org/quark/patches/digestauth/quark-digestauth-20200916-5d0221d.diff
_AT_@ -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_AT_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
+_AT_@ -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
+_AT_@ -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
+_AT_@ -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[] = {
+_AT_@ -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",
+_AT_@ -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
+_AT_@ -75,8 +80,9 @@ http_prepare_header_buf(const struct response *res, struct buffer *buf)
+ if (buffer_appendf(buf,
+ "HTTP/1.1 %d %s
"
+ "Date: %s
"
+- "Connection: close
",
+- res->status, status_str[res->status], tstmp)) {
++ "Connection: %s
",
++ res->status, status_str[res->status], tstmp,
++ res->keep_alive ? "keep-alive" : "close")) {
+ goto err;
+ }
+
+_AT_@ -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 == '++ 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 == ' '; 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 = '++
++ 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 = '++
++ 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 = '++
++ 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 = '++ ++p;
++ }
++
++ /* skip trailing whitespace */
++ for (++p; *p == ' ' || *p == ' '; 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;
+
+_AT_@ -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] != '+ 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] == '++ 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
", auth.nonce);
++ printf("server nonce: %s
", 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")) {
+_AT_@ -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;
+_AT_@ -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] = '++ 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
+_AT_@ -12,6 +12,7 @@ enum req_field {
+ REQ_HOST,
+ REQ_RANGE,
+ REQ_IF_MODIFIED_SINCE,
++ REQ_AUTHORIZATION,
+ NUM_REQ_FIELDS,
+ };
+
+_AT_@ -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];
+ };
+
+_AT_@ -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,
+_AT_@ -57,6 +60,7 @@ enum res_field {
+ RES_CONTENT_LENGTH,
+ RES_CONTENT_RANGE,
+ RES_CONTENT_TYPE,
++ RES_AUTHENTICATE,
+ NUM_RES_FIELDS,
+ };
+
+_AT_@ -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];
+_AT_@ -83,6 +88,7 @@ struct response {
+
+ enum conn_state {
+ C_VACANT,
++ C_START,
+ C_RECV_HEADER,
+ C_SEND_HEADER,
+ C_SEND_BODY,
+_AT_@ -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;
+_AT_@ -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
+_AT_@ -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 */
+_AT_@ -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) {
+_AT_@ -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;
+_AT_@ -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] == '++ 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);
+_AT_@ -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
"
+ " %s -U file [-p port] %s", argv0,
+_AT_@ -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;
+_AT_@ -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;
+_AT_@ -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
+_AT_@ -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
+_AT_@ -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
+_AT_@ -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
+_AT_@ -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.
+_AT_@ -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
+_AT_@ -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
+_AT_@ -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;
+_AT_@ -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
+
Received on Fri Oct 30 2020 - 15:44:51 CET

This archive was generated by hypermail 2.3.0 : Fri Oct 30 2020 - 15:48:44 CET