quark-digestauth-20201101-dff98c0.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) 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  <soy.jmi2k@gmail.com>
            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, 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 @@ -549,21 +555,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 @@ -809,14 +991,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 @@ -854,17 +1085,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 @@ -883,4 +1119,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 e26cc77..65dacf4 100644
          530 --- a/main.c
          531 +++ b/main.c
          532 @@ -66,11 +66,17 @@ serve_connection(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 @@ -78,7 +84,7 @@ serve_connection(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 @@ -88,16 +94,16 @@ serve_connection(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 @@ -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 connection will
          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[1] ||
          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[1]) {
          665 +                        usage();
          666 +                }
          667 +                errno = 0;
          668 +                if (!(grp = getgrnam(tok[0]))) {
          669 +                        die("getgrnam '%s': %s", tok[0] ? tok[0] : "null",
          670 +                            errno ? strerror(errno) : "Entry not found");
          671 +                }
          672 +                if (!(srv.realm = reallocarray(srv.realm, ++srv.realm_len,
          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, 0x4787c62a, 0xa8304613, 0xfd469501,
          708 +        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
          709 +        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
          710 +        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
          711 +        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
          712 +        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
          713 +        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
          714 +        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
          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