libfetch: Implement HTTP digest authentication, HTTP 305, and HTTP 308
authorJohn Marino <draco@marino.st>
Fri, 2 Nov 2012 19:56:28 +0000 (20:56 +0100)
committerJohn Marino <draco@marino.st>
Fri, 2 Nov 2012 20:11:05 +0000 (21:11 +0100)
Implement HTTP status 305: Use proxy
Implement HTTP status 308: Permanent redirect
Implement HTTP Digest Authentication

Taken-from:
FreeBSD SVN 202613 (19 JAN 2010)
FreeBSD SVN 202623 (19 JAN 2010)
FreeBSD SVN 203028 (26 JAN 2010)
FreeBSD SVN 209632 (01 JUL 2010)
FreeBSD SVN 210563 (28 JUL 2010)
FreeBSD SVN 221820 (12 MAY 2011)
FreeBSD SVN 221821 (12 MAY 2011)
FreeBSD SVN 221822 (12 MAY 2011)
FreeBSD SVN 221823 (12 MAY 2011)
FreeBSD SVN 234838 (30 APR 2012)
FreeBSD SVN 240496 (14 SEP 2012)
FreeBSD SVN 241840 (21 OCT 2012)
FreeBSD SVN 241841 (21 OCT 2012)

lib/libfetch/Makefile
lib/libfetch/fetch.3
lib/libfetch/http.c
lib/libfetch/http.errors

index cbf3f7a..48a7054 100644 (file)
@@ -13,6 +13,9 @@ CLEANFILES=   ftperr.h httperr.h
 CFLAGS+=       -DWITH_SSL
 DPADD=         ${LIBSSL} ${LIBCRYPTO}
 LDADD=         -lssl -lcrypto
+.else
+DPADD=         ${LIBMD}
+LDADD=         -lmd
 .endif
 
 SHLIB_MAJOR=    4
index 169bae1..89614f1 100644 (file)
@@ -521,9 +521,13 @@ Specifies HTTP authorization parameters as a colon-separated list of
 items.
 The first and second item are the authorization scheme and realm
 respectively; further items are scheme-dependent.
-Currently, only basic authorization is supported.
+Currently, the
+.Dq basic
+and
+.Dq digest
+authorization methods are supported.
 .Pp
-Basic authorization requires two parameters: the user name and
+Both methods require two parameters: the user name and
 password, in that order.
 .Pp
 This variable is only used if the server requires authorization and
@@ -668,12 +672,14 @@ The
 .Nm fetch
 library was mostly written by
 .An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org
-with numerous suggestions from
+with numerous suggestions and contributions from
 .An Jordan K. Hubbard Aq jkh@FreeBSD.org ,
-.An Eugene Skepner Aq eu@qub.com
-and other
-.Fx
-developers.
+.An Eugene Skepner Aq eu@qub.com ,
+.An Hajimu Umemoto Aq ume@FreeBSD.org ,
+.An Henry Whincup Aq henry@techiebod.com ,
+.An Jukka A. Ukkonen Aq jau@iki.fi ,
+.An Jean-Fran\(,cois Dockes Aq jf@dockes.org
+and others.
 It replaces the older
 .Nm ftpio
 library written by
index 30ec92d..c757f89 100644 (file)
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2000-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2000-2011 Dag-Erling Smørgrav
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -24,9 +24,6 @@
  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * $FreeBSD: src/lib/libfetch/http.c,v 1.86 2008/12/15 08:27:44 murray Exp $
- * $DragonFly: src/lib/libfetch/http.c,v 1.4 2007/08/05 21:48:12 swildner Exp $
  */
 
 /*
 #include <time.h>
 #include <unistd.h>
 
+#ifdef WITH_SSL
+#include <openssl/md5.h>
+#define MD5Init(c) MD5_Init(c)
+#define MD5Update(c, data, len) MD5_Update(c, data, len)
+#define MD5Final(md, c) MD5_Final(md, c)
+#else
+#include <md5.h>
+#endif
+
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 
 #define HTTP_MOVED_TEMP                302
 #define HTTP_SEE_OTHER         303
 #define HTTP_NOT_MODIFIED      304
+#define HTTP_USE_PROXY         305
 #define HTTP_TEMP_REDIRECT     307
+#define HTTP_PERM_REDIRECT     308
 #define HTTP_NEED_AUTH         401
 #define HTTP_NEED_PROXY_AUTH   407
 #define HTTP_BAD_RANGE         416
 #define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
                            || (xyz) == HTTP_MOVED_TEMP \
                            || (xyz) == HTTP_TEMP_REDIRECT \
+                           || (xyz) == HTTP_USE_PROXY \
                            || (xyz) == HTTP_SEE_OTHER)
 
 #define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
@@ -352,7 +361,8 @@ typedef enum {
        hdr_last_modified,
        hdr_location,
        hdr_transfer_encoding,
-       hdr_www_authenticate
+       hdr_www_authenticate,
+       hdr_proxy_authenticate,
 } hdr_t;
 
 /* Names of interesting headers */
@@ -366,6 +376,7 @@ static struct {
        { hdr_location,                 "Location" },
        { hdr_transfer_encoding,        "Transfer-Encoding" },
        { hdr_www_authenticate,         "WWW-Authenticate" },
+       { hdr_proxy_authenticate,       "Proxy-Authenticate" },
        { hdr_unknown,                  NULL },
 };
 
@@ -455,21 +466,114 @@ http_match(const char *str, const char *hdr)
        return (hdr);
 }
 
+
 /*
- * Get the next header and return the appropriate symbolic code.
+ * Get the next header and return the appropriate symbolic code.  We
+ * need to read one line ahead for checking for a continuation line
+ * belonging to the current header (continuation lines start with
+ * white space).
+ *
+ * We get called with a fresh line already in the conn buffer, either
+ * from the previous http_next_header() invocation, or, the first
+ * time, from a fetch_getln() performed by our caller.
+ *
+ * This stops when we encounter an empty line (we dont read beyond the header
+ * area).
+ *
+ * Note that the "headerbuf" is just a place to return the result. Its
+ * contents are not used for the next call. This means that no cleanup
+ * is needed when ie doing another connection, just call the cleanup when
+ * fully done to deallocate memory.
  */
-static hdr_t
-http_next_header(conn_t *conn, const char **p)
+
+/* Limit the max number of continuation lines to some reasonable value */
+#define HTTP_MAX_CONT_LINES 10
+
+/* Place into which to build a header from one or several lines */
+typedef struct {
+       char    *buf;           /* buffer */
+       size_t   bufsize;       /* buffer size */
+       size_t   buflen;        /* length of buffer contents */
+} http_headerbuf_t;
+
+static void
+init_http_headerbuf(http_headerbuf_t *buf)
 {
-       int i;
+       buf->buf = NULL;
+       buf->bufsize = 0;
+       buf->buflen = 0;
+}
 
-       if (fetch_getln(conn) == -1)
-               return (hdr_syserror);
-       while (conn->buflen && isspace((unsigned char)conn->buf[conn->buflen - 1]))
+static void
+clean_http_headerbuf(http_headerbuf_t *buf)
+{
+       if (buf->buf)
+               free(buf->buf);
+       init_http_headerbuf(buf);
+}
+
+/* Remove whitespace at the end of the buffer */
+static void
+http_conn_trimright(conn_t *conn)
+{
+       while (conn->buflen &&
+              isspace((unsigned char)conn->buf[conn->buflen - 1]))
                conn->buflen--;
        conn->buf[conn->buflen] = '\0';
+}
+
+static hdr_t
+http_next_header(conn_t *conn, http_headerbuf_t *hbuf, const char **p)
+{
+       unsigned int i, len;
+
+       /*
+        * Have to do the stripping here because of the first line. So
+        * it's done twice for the subsequent lines. No big deal
+        */
+       http_conn_trimright(conn);
        if (conn->buflen == 0)
                return (hdr_end);
+
+       /* Copy the line to the headerbuf */
+       if (hbuf->bufsize < conn->buflen + 1) {
+               if ((hbuf->buf = realloc(hbuf->buf, conn->buflen + 1)) == NULL)
+                       return (hdr_syserror);
+               hbuf->bufsize = conn->buflen + 1;
+       }
+       strcpy(hbuf->buf, conn->buf);
+       hbuf->buflen = conn->buflen;
+
+       /*
+        * Fetch possible continuation lines. Stop at 1st non-continuation
+        * and leave it in the conn buffer
+        */
+       for (i = 0; i < HTTP_MAX_CONT_LINES; i++) {
+               if (fetch_getln(conn) == -1)
+                       return (hdr_syserror);
+
+               /*
+                * Note: we carry on the idea from the previous version
+                * that a pure whitespace line is equivalent to an empty
+                * one (so it's not continuation and will be handled when
+                * we are called next)
+                */
+               http_conn_trimright(conn);
+               if (conn->buf[0] != ' ' && conn->buf[0] != "\t"[0])
+                       break;
+
+               /* Got a continuation line. Concatenate to previous */
+               len = hbuf->buflen + conn->buflen;
+               if (hbuf->bufsize < len + 1) {
+                       len *= 2;
+                       if ((hbuf->buf = realloc(hbuf->buf, len + 1)) == NULL)
+                               return (hdr_syserror);
+                       hbuf->bufsize = len + 1;
+               }
+               strcpy(hbuf->buf + hbuf->buflen, conn->buf);
+               hbuf->buflen += conn->buflen;
+       }
+
        /*
         * We could check for malformed headers but we don't really care.
         * A valid header starts with a token immediately followed by a
@@ -477,11 +581,290 @@ http_next_header(conn_t *conn, const char **p)
         * characters except "()<>@,;:\\\"{}".
         */
        for (i = 0; hdr_names[i].num != hdr_unknown; i++)
-               if ((*p = http_match(hdr_names[i].name, conn->buf)) != NULL)
+               if ((*p = http_match(hdr_names[i].name, hbuf->buf)) != NULL)
                        return (hdr_names[i].num);
+
        return (hdr_unknown);
 }
 
+/**************************
+ * [Proxy-]Authenticate header parsing
+ */
+
+/*
+ * Read doublequote-delimited string into output buffer obuf (allocated
+ * by caller, whose responsibility it is to ensure that it's big enough)
+ * cp points to the first char after the initial '"'
+ * Handles \ quoting
+ * Returns pointer to the first char after the terminating double quote, or
+ * NULL for error.
+ */
+static const char *
+http_parse_headerstring(const char *cp, char *obuf)
+{
+       for (;;) {
+               switch (*cp) {
+               case 0: /* Unterminated string */
+                       *obuf = 0;
+                       return (NULL);
+               case '"': /* Ending quote */
+                       *obuf = 0;
+                       return (++cp);
+               case '\\':
+                       if (*++cp == 0) {
+                               *obuf = 0;
+                               return (NULL);
+                       }
+                       /* FALLTHROUGH */
+               default:
+                       *obuf++ = *cp++;
+               }
+       }
+}
+
+/* Http auth challenge schemes */
+typedef enum {HTTPAS_UNKNOWN, HTTPAS_BASIC,HTTPAS_DIGEST} http_auth_schemes_t;
+
+/* Data holder for a Basic or Digest challenge. */
+typedef struct {
+       http_auth_schemes_t scheme;
+       char    *realm;
+       char    *qop;
+       char    *nonce;
+       char    *opaque;
+       char    *algo;
+       int      stale;
+       int      nc; /* Nonce count */
+} http_auth_challenge_t;
+
+static void
+init_http_auth_challenge(http_auth_challenge_t *b)
+{
+       b->scheme = HTTPAS_UNKNOWN;
+       b->realm = b->qop = b->nonce = b->opaque = b->algo = NULL;
+       b->stale = b->nc = 0;
+}
+
+static void
+clean_http_auth_challenge(http_auth_challenge_t *b)
+{
+       if (b->realm)
+               free(b->realm);
+       if (b->qop)
+               free(b->qop);
+       if (b->nonce)
+               free(b->nonce);
+       if (b->opaque)
+               free(b->opaque);
+       if (b->algo)
+               free(b->algo);
+       init_http_auth_challenge(b);
+}
+
+/* Data holder for an array of challenges offered in an http response. */
+#define MAX_CHALLENGES 10
+typedef struct {
+       http_auth_challenge_t *challenges[MAX_CHALLENGES];
+       int     count; /* Number of parsed challenges in the array */
+       int     valid; /* We did parse an authenticate header */
+} http_auth_challenges_t;
+
+static void
+init_http_auth_challenges(http_auth_challenges_t *cs)
+{
+       int i;
+       for (i = 0; i < MAX_CHALLENGES; i++)
+               cs->challenges[i] = NULL;
+       cs->count = cs->valid = 0;
+}
+
+static void
+clean_http_auth_challenges(http_auth_challenges_t *cs)
+{
+       int i;
+       /* We rely on non-zero pointers being allocated, not on the count */
+       for (i = 0; i < MAX_CHALLENGES; i++) {
+               if (cs->challenges[i] != NULL) {
+                       clean_http_auth_challenge(cs->challenges[i]);
+                       free(cs->challenges[i]);
+               }
+       }
+       init_http_auth_challenges(cs);
+}
+
+/*
+ * Enumeration for lexical elements. Separators will be returned as their own
+ * ascii value
+ */
+typedef enum {HTTPHL_WORD=256, HTTPHL_STRING=257, HTTPHL_END=258,
+             HTTPHL_ERROR = 259} http_header_lex_t;
+
+/*
+ * Determine what kind of token comes next and return possible value
+ * in buf, which is supposed to have been allocated big enough by
+ * caller. Advance input pointer and return element type.
+ */
+static int
+http_header_lex(const char **cpp, char *buf)
+{
+       size_t l;
+       /* Eat initial whitespace */
+       *cpp += strspn(*cpp, " \t");
+       if (**cpp == 0)
+               return (HTTPHL_END);
+
+       /* Separator ? */
+       if (**cpp == ',' || **cpp == '=')
+               return (*((*cpp)++));
+
+       /* String ? */
+       if (**cpp == '"') {
+               *cpp = http_parse_headerstring(++*cpp, buf);
+               if (*cpp == NULL)
+                       return (HTTPHL_ERROR);
+               return (HTTPHL_STRING);
+       }
+
+       /* Read other token, until separator or whitespace */
+       l = strcspn(*cpp, " \t,=");
+       memcpy(buf, *cpp, l);
+       buf[l] = 0;
+       *cpp += l;
+       return (HTTPHL_WORD);
+}
+
+/*
+ * Read challenges from http xxx-authenticate header and accumulate them
+ * in the challenges list structure.
+ *
+ * Headers with multiple challenges are specified by rfc2617, but
+ * servers (ie: squid) often send them in separate headers instead,
+ * which in turn is forbidden by the http spec (multiple headers with
+ * the same name are only allowed for pure comma-separated lists, see
+ * rfc2616 sec 4.2).
+ *
+ * We support both approaches anyway
+ */
+static int
+http_parse_authenticate(const char *cp, http_auth_challenges_t *cs)
+{
+       int ret = -1;
+       http_header_lex_t lex;
+       char *key = malloc(strlen(cp) + 1);
+       char *value = malloc(strlen(cp) + 1);
+       char *buf = malloc(strlen(cp) + 1);
+
+       if (key == NULL || value == NULL || buf == NULL) {
+               fetch_syserr();
+               goto out;
+       }
+
+       /* In any case we've seen the header and we set the valid bit */
+       cs->valid = 1;
+
+       /* Need word first */
+       lex = http_header_lex(&cp, key);
+       if (lex != HTTPHL_WORD)
+               goto out;
+
+       /* Loop on challenges */
+       for (; cs->count < MAX_CHALLENGES; cs->count++) {
+               cs->challenges[cs->count] =
+                       malloc(sizeof(http_auth_challenge_t));
+               if (cs->challenges[cs->count] == NULL) {
+                       fetch_syserr();
+                       goto out;
+               }
+               init_http_auth_challenge(cs->challenges[cs->count]);
+               if (!strcasecmp(key, "basic")) {
+                       cs->challenges[cs->count]->scheme = HTTPAS_BASIC;
+               } else if (!strcasecmp(key, "digest")) {
+                       cs->challenges[cs->count]->scheme = HTTPAS_DIGEST;
+               } else {
+                       cs->challenges[cs->count]->scheme = HTTPAS_UNKNOWN;
+                       /*
+                        * Continue parsing as basic or digest may
+                        * follow, and the syntax is the same for
+                        * all. We'll just ignore this one when
+                        * looking at the list
+                        */
+               }
+
+               /* Loop on attributes */
+               for (;;) {
+                       /* Key */
+                       lex = http_header_lex(&cp, key);
+                       if (lex != HTTPHL_WORD)
+                               goto out;
+
+                       /* Equal sign */
+                       lex = http_header_lex(&cp, buf);
+                       if (lex != '=')
+                               goto out;
+
+                       /* Value */
+                       lex = http_header_lex(&cp, value);
+                       if (lex != HTTPHL_WORD && lex != HTTPHL_STRING)
+                               goto out;
+
+                       if (!strcasecmp(key, "realm"))
+                               cs->challenges[cs->count]->realm =
+                                       strdup(value);
+                       else if (!strcasecmp(key, "qop"))
+                               cs->challenges[cs->count]->qop =
+                                       strdup(value);
+                       else if (!strcasecmp(key, "nonce"))
+                               cs->challenges[cs->count]->nonce =
+                                       strdup(value);
+                       else if (!strcasecmp(key, "opaque"))
+                               cs->challenges[cs->count]->opaque =
+                                       strdup(value);
+                       else if (!strcasecmp(key, "algorithm"))
+                               cs->challenges[cs->count]->algo =
+                                       strdup(value);
+                       else if (!strcasecmp(key, "stale"))
+                               cs->challenges[cs->count]->stale =
+                                       strcasecmp(value, "no");
+                       /* Else ignore unknown attributes */
+
+                       /* Comma or Next challenge or End */
+                       lex = http_header_lex(&cp, key);
+                       /*
+                        * If we get a word here, this is the beginning of the
+                        * next challenge. Break the attributes loop
+                        */
+                       if (lex == HTTPHL_WORD)
+                               break;
+
+                       if (lex == HTTPHL_END) {
+                               /* End while looking for ',' is normal exit */
+                               cs->count++;
+                               ret = 0;
+                               goto out;
+                       }
+                       /* Anything else is an error */
+                       if (lex != ',')
+                               goto out;
+
+               } /* End attributes loop */
+       } /* End challenge loop */
+
+       /*
+        * Challenges max count exceeded. This really can't happen
+        * with normal data, something's fishy -> error
+        */
+
+out:
+       if (key)
+               free(key);
+       if (value)
+               free(value);
+       if (buf)
+               free(buf);
+       return (ret);
+}
+
+
 /*
  * Parse a last-modified header
  */
@@ -627,6 +1010,291 @@ http_base64(const char *src)
        return (str);
 }
 
+
+/*
+ * Extract authorization parameters from environment value.
+ * The value is like scheme:realm:user:pass
+ */
+typedef struct {
+       char    *scheme;
+       char    *realm;
+       char    *user;
+       char    *password;
+} http_auth_params_t;
+
+static void
+init_http_auth_params(http_auth_params_t *s)
+{
+       s->scheme = s->realm = s->user = s->password = 0;
+}
+
+static void
+clean_http_auth_params(http_auth_params_t *s)
+{
+       if (s->scheme)
+               free(s->scheme);
+       if (s->realm)
+               free(s->realm);
+       if (s->user)
+               free(s->user);
+       if (s->password)
+               free(s->password);
+       init_http_auth_params(s);
+}
+
+static int
+http_authfromenv(const char *p, http_auth_params_t *parms)
+{
+       int ret = -1;
+       char *v, *ve;
+       char *str = strdup(p);
+
+       if (str == NULL) {
+               fetch_syserr();
+               return (-1);
+       }
+       v = str;
+
+       if ((ve = strchr(v, ':')) == NULL)
+               goto out;
+
+       *ve = 0;
+       if ((parms->scheme = strdup(v)) == NULL) {
+               fetch_syserr();
+               goto out;
+       }
+       v = ve + 1;
+
+       if ((ve = strchr(v, ':')) == NULL)
+               goto out;
+
+       *ve = 0;
+       if ((parms->realm = strdup(v)) == NULL) {
+               fetch_syserr();
+               goto out;
+       }
+       v = ve + 1;
+
+       if ((ve = strchr(v, ':')) == NULL)
+               goto out;
+
+       *ve = 0;
+       if ((parms->user = strdup(v)) == NULL) {
+               fetch_syserr();
+               goto out;
+       }
+       v = ve + 1;
+
+
+       if ((parms->password = strdup(v)) == NULL) {
+               fetch_syserr();
+               goto out;
+       }
+       ret = 0;
+out:
+       if (ret == -1)
+               clean_http_auth_params(parms);
+       if (str)
+               free(str);
+       return (ret);
+}
+
+
+/*
+ * Digest response: the code to compute the digest is taken from the
+ * sample implementation in RFC2616
+ */
+#define IN const
+#define OUT
+
+#define HASHLEN 16
+typedef char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+
+static const char *hexchars = "0123456789abcdef";
+static void
+CvtHex(IN HASH Bin, OUT HASHHEX Hex)
+{
+       unsigned short i;
+       unsigned char j;
+
+       for (i = 0; i < HASHLEN; i++) {
+               j = (Bin[i] >> 4) & 0xf;
+               Hex[i*2] = hexchars[j];
+               j = Bin[i] & 0xf;
+               Hex[i*2+1] = hexchars[j];
+       };
+       Hex[HASHHEXLEN] = '\0';
+};
+
+/* calculate H(A1) as per spec */
+static void
+DigestCalcHA1(
+       IN char * pszAlg,
+       IN char * pszUserName,
+       IN char * pszRealm,
+       IN char * pszPassword,
+       IN char * pszNonce,
+       IN char * pszCNonce,
+       OUT HASHHEX SessionKey
+       )
+{
+       MD5_CTX Md5Ctx;
+       HASH HA1;
+
+       MD5Init(&Md5Ctx);
+       MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
+       MD5Update(&Md5Ctx, ":", 1);
+       MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
+       MD5Update(&Md5Ctx, ":", 1);
+       MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
+       MD5Final(HA1, &Md5Ctx);
+       if (strcasecmp(pszAlg, "md5-sess") == 0) {
+
+               MD5Init(&Md5Ctx);
+               MD5Update(&Md5Ctx, HA1, HASHLEN);
+               MD5Update(&Md5Ctx, ":", 1);
+               MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
+               MD5Update(&Md5Ctx, ":", 1);
+               MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
+               MD5Final(HA1, &Md5Ctx);
+       };
+       CvtHex(HA1, SessionKey);
+}
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+static void
+DigestCalcResponse(
+       IN HASHHEX HA1,           /* H(A1) */
+       IN char * pszNonce,       /* nonce from server */
+       IN char * pszNonceCount,  /* 8 hex digits */
+       IN char * pszCNonce,      /* client nonce */
+       IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
+       IN char * pszMethod,      /* method from the request */
+       IN char * pszDigestUri,   /* requested URL */
+       IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
+       OUT HASHHEX Response      /* request-digest or response-digest */
+       )
+{
+/*     DEBUG(fprintf(stderr,
+                     "Calc: HA1[%s] Nonce[%s] qop[%s] method[%s] URI[%s]\n",
+                     HA1, pszNonce, pszQop, pszMethod, pszDigestUri));*/
+       MD5_CTX Md5Ctx;
+       HASH HA2;
+       HASH RespHash;
+       HASHHEX HA2Hex;
+
+       // calculate H(A2)
+       MD5Init(&Md5Ctx);
+       MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
+       MD5Update(&Md5Ctx, ":", 1);
+       MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
+       if (strcasecmp(pszQop, "auth-int") == 0) {
+               MD5Update(&Md5Ctx, ":", 1);
+               MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
+       };
+       MD5Final(HA2, &Md5Ctx);
+       CvtHex(HA2, HA2Hex);
+
+       // calculate response
+       MD5Init(&Md5Ctx);
+       MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
+       MD5Update(&Md5Ctx, ":", 1);
+       MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
+       MD5Update(&Md5Ctx, ":", 1);
+       if (*pszQop) {
+               MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
+               MD5Update(&Md5Ctx, ":", 1);
+               MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
+               MD5Update(&Md5Ctx, ":", 1);
+               MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
+               MD5Update(&Md5Ctx, ":", 1);
+       };
+       MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
+       MD5Final(RespHash, &Md5Ctx);
+       CvtHex(RespHash, Response);
+}
+
+/*
+ * Generate/Send a Digest authorization header
+ * This looks like: [Proxy-]Authorization: credentials
+ *
+ *  credentials      = "Digest" digest-response
+ *  digest-response  = 1#( username | realm | nonce | digest-uri
+ *                      | response | [ algorithm ] | [cnonce] |
+ *                      [opaque] | [message-qop] |
+ *                          [nonce-count]  | [auth-param] )
+ *  username         = "username" "=" username-value
+ *  username-value   = quoted-string
+ *  digest-uri       = "uri" "=" digest-uri-value
+ *  digest-uri-value = request-uri   ; As specified by HTTP/1.1
+ *  message-qop      = "qop" "=" qop-value
+ *  cnonce           = "cnonce" "=" cnonce-value
+ *  cnonce-value     = nonce-value
+ *  nonce-count      = "nc" "=" nc-value
+ *  nc-value         = 8LHEX
+ *  response         = "response" "=" request-digest
+ *  request-digest = <"> 32LHEX <">
+ */
+static int
+http_digest_auth(conn_t *conn, const char *hdr, http_auth_challenge_t *c,
+                http_auth_params_t *parms, struct url *url)
+{
+       int r;
+       char noncecount[10];
+       char cnonce[40];
+       char *options = 0;
+
+       if (!c->realm || !c->nonce) {
+               DEBUG(fprintf(stderr, "realm/nonce not set in challenge\n"));
+               return(-1);
+       }
+       if (!c->algo)
+               c->algo = strdup("");
+
+       if (asprintf(&options, "%s%s%s%s",
+                    *c->algo? ",algorithm=" : "", c->algo,
+                    c->opaque? ",opaque=" : "", c->opaque?c->opaque:"")== -1)
+               return (-1);
+
+       if (!c->qop) {
+               c->qop = strdup("");
+               *noncecount = 0;
+               *cnonce = 0;
+       } else {
+               c->nc++;
+               sprintf(noncecount, "%08x", c->nc);
+               /* We don't try very hard with the cnonce ... */
+               sprintf(cnonce, "%x%lx", getpid(), (unsigned long)time(0));
+       }
+
+       HASHHEX HA1;
+       DigestCalcHA1(c->algo, parms->user, c->realm,
+                     parms->password, c->nonce, cnonce, HA1);
+       DEBUG(fprintf(stderr, "HA1: [%s]\n", HA1));
+       HASHHEX digest;
+       DigestCalcResponse(HA1, c->nonce, noncecount, cnonce, c->qop,
+                          "GET", url->doc, "", digest);
+
+       if (c->qop[0]) {
+               r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
+                            "nonce=\"%s\",uri=\"%s\",response=\"%s\","
+                            "qop=\"auth\", cnonce=\"%s\", nc=%s%s",
+                            hdr, parms->user, c->realm,
+                            c->nonce, url->doc, digest,
+                            cnonce, noncecount, options);
+       } else {
+               r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
+                            "nonce=\"%s\",uri=\"%s\",response=\"%s\"%s",
+                            hdr, parms->user, c->realm,
+                            c->nonce, url->doc, digest, options);
+       }
+       if (options)
+               free(options);
+       return (r);
+}
+
 /*
  * Encode username and password
  */
@@ -636,8 +1304,8 @@ http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
        char *upw, *auth;
        int r;
 
-       DEBUG(fprintf(stderr, "usr: [%s]\n", usr));
-       DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd));
+       DEBUG(fprintf(stderr, "basic: usr: [%s]\n", usr));
+       DEBUG(fprintf(stderr, "basic: pwd: [%s]\n", pwd));
        if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
                return (-1);
        auth = http_base64(upw);
@@ -650,33 +1318,49 @@ http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
 }
 
 /*
- * Send an authorization header
+ * Chose the challenge to answer and call the appropriate routine to
+ * produce the header.
  */
 static int
-http_authorize(conn_t *conn, const char *hdr, const char *p)
+http_authorize(conn_t *conn, const char *hdr, http_auth_challenges_t *cs,
+              http_auth_params_t *parms, struct url *url)
 {
-       /* basic authorization */
-       if (strncasecmp(p, "basic:", 6) == 0) {
-               char *user, *pwd, *str;
-               int r;
-
-               /* skip realm */
-               for (p += 6; *p && *p != ':'; ++p)
-                       /* nothing */ ;
-               if (!*p || strchr(++p, ':') == NULL)
-                       return (-1);
-               if ((str = strdup(p)) == NULL)
-                       return (-1); /* XXX */
-               user = str;
-               pwd = strchr(str, ':');
-               *pwd++ = '\0';
-               r = http_basic_auth(conn, hdr, user, pwd);
-               free(str);
-               return (r);
+       http_auth_challenge_t *basic = NULL;
+       http_auth_challenge_t *digest = NULL;
+       int i;
+
+       /* If user or pass are null we're not happy */
+       if (!parms->user || !parms->password) {
+               DEBUG(fprintf(stderr, "NULL usr or pass\n"));
+               return (-1);
        }
-       return (-1);
-}
 
+       /* Look for a Digest and a Basic challenge */
+       for (i = 0; i < cs->count; i++) {
+               if (cs->challenges[i]->scheme == HTTPAS_BASIC)
+                       basic = cs->challenges[i];
+               if (cs->challenges[i]->scheme == HTTPAS_DIGEST)
+                       digest = cs->challenges[i];
+       }
+
+       /* Error if "Digest" was specified and there is no Digest challenge */
+       if (!digest && (parms->scheme &&
+                       !strcasecmp(parms->scheme, "digest"))) {
+               DEBUG(fprintf(stderr,
+                             "Digest auth in env, not supported by peer\n"));
+               return (-1);
+       }
+       /*
+        * If "basic" was specified in the environment, or there is no Digest
+        * challenge, do the basic thing. Don't need a challenge for this,
+        * so no need to check basic!=NULL
+        */
+       if (!digest || (parms->scheme && !strcasecmp(parms->scheme,"basic")))
+               return (http_basic_auth(conn,hdr,parms->user,parms->password));
+
+       /* Else, prefer digest. We just checked that it's not NULL */
+       return (http_digest_auth(conn, hdr, digest, parms, url));
+}
 
 /*****************************************************************************
  * Helper functions for connecting to a server or proxy
@@ -806,13 +1490,13 @@ http_print_html(FILE *out, FILE *in)
  */
 FILE *
 http_request(struct url *URL, const char *op, struct url_stat *us,
-    struct url *purl, const char *flags)
+       struct url *purl, const char *flags)
 {
        char timebuf[80];
        char hbuf[MAXHOSTNAMELEN + 7], *host;
        conn_t *conn;
        struct url *url, *new;
-       int chunked, direct, ims, need_auth, noredirect, verbose;
+       int chunked, direct, ims, noredirect, verbose;
        int e, i, n, val;
        off_t offset, clength, length, size;
        time_t mtime;
@@ -820,6 +1504,14 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
        FILE *f;
        hdr_t h;
        struct tm *timestruct;
+       http_headerbuf_t headerbuf;
+       http_auth_challenges_t server_challenges;
+       http_auth_challenges_t proxy_challenges;
+
+       /* The following calls don't allocate anything */
+       init_http_headerbuf(&headerbuf);
+       init_http_auth_challenges(&server_challenges);
+       init_http_auth_challenges(&proxy_challenges);
 
        direct = CHECK_FLAG('d');
        noredirect = CHECK_FLAG('A');
@@ -834,12 +1526,10 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
        /* try the provided URL first */
        url = URL;
 
-       /* if the A flag is set, we only get one try */
-       n = noredirect ? 1 : MAX_REDIRECT;
+       n = MAX_REDIRECT;
        i = 0;
 
        e = HTTP_PROTOCOL_ERROR;
-       need_auth = 0;
        do {
                new = NULL;
                chunked = 0;
@@ -904,27 +1594,67 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                /* virtual host */
                http_cmd(conn, "Host: %s", host);
 
-               /* proxy authorization */
-               if (purl) {
-                       if (*purl->user || *purl->pwd)
-                               http_basic_auth(conn, "Proxy-Authorization",
-                                   purl->user, purl->pwd);
-                       else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
-                               http_authorize(conn, "Proxy-Authorization", p);
+               /*
+                * Proxy authorization: we only send auth after we received
+                * a 407 error. We do not first try basic anyway (changed
+                * when support was added for digest-auth)
+                */
+               if (purl && proxy_challenges.valid) {
+                       http_auth_params_t aparams;
+                       init_http_auth_params(&aparams);
+                       if (*purl->user || *purl->pwd) {
+                               aparams.user = purl->user ?
+                                       strdup(purl->user) : strdup("");
+                               aparams.password = purl->pwd?
+                                       strdup(purl->pwd) : strdup("");
+                       } else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL &&
+                                  *p != '\0') {
+                               if (http_authfromenv(p, &aparams) < 0) {
+                                       http_seterr(HTTP_NEED_PROXY_AUTH);
+                                       goto ouch;
+                               }
+                       }
+                       http_authorize(conn, "Proxy-Authorization",
+                                      &proxy_challenges, &aparams, url);
+                       clean_http_auth_params(&aparams);
                }
 
-               /* server authorization */
-               if (need_auth || *url->user || *url->pwd) {
-                       if (*url->user || *url->pwd)
-                               http_basic_auth(conn, "Authorization", url->user, url->pwd);
-                       else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
-                               http_authorize(conn, "Authorization", p);
-                       else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
-                               http_basic_auth(conn, "Authorization", url->user, url->pwd);
+               /*
+                * Server authorization: we never send "a priori"
+                * Basic auth, which used to be done if user/pass were
+                * set in the url. This would be weird because we'd send the
+                * password in the clear even if Digest is finally to be
+                * used (it would have made more sense for the
+                * pre-digest version to do this when Basic was specified
+                * in the environment)
+                */
+               if (server_challenges.valid) {
+                       http_auth_params_t aparams;
+                       init_http_auth_params(&aparams);
+                       if (*url->user || *url->pwd) {
+                               aparams.user = url->user ?
+                                       strdup(url->user) : strdup("");
+                               aparams.password = url->pwd ?
+                                       strdup(url->pwd) : strdup("");
+                       } else if ((p = getenv("HTTP_AUTH")) != NULL &&
+                                  *p != '\0') {
+                               if (http_authfromenv(p, &aparams) < 0) {
+                                       http_seterr(HTTP_NEED_AUTH);
+                                       goto ouch;
+                               }
+                       } else if (fetchAuthMethod &&
+                                  fetchAuthMethod(url) == 0) {
+                               aparams.user = url->user ?
+                                       strdup(url->user) : strdup("");
+                               aparams.password = url->pwd ?
+                                       strdup(url->pwd) : strdup("");
                        } else {
                                http_seterr(HTTP_NEED_AUTH);
                                goto ouch;
                        }
+                       http_authorize(conn, "Authorization",
+                                      &server_challenges, &aparams, url);
+                       clean_http_auth_params(&aparams);
                }
 
                /* other headers */
@@ -968,13 +1698,14 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                case HTTP_MOVED_PERM:
                case HTTP_MOVED_TEMP:
                case HTTP_SEE_OTHER:
+               case HTTP_USE_PROXY:
                        /*
                         * Not so fine, but we still have to read the
                         * headers to get the new location.
                         */
                        break;
                case HTTP_NEED_AUTH:
-                       if (need_auth) {
+                       if (server_challenges.valid) {
                                /*
                                 * We already sent out authorization code,
                                 * so there's nothing more we can do.
@@ -987,13 +1718,18 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                                fetch_info("server requires authorization");
                        break;
                case HTTP_NEED_PROXY_AUTH:
-                       /*
-                        * If we're talking to a proxy, we already sent
-                        * our proxy authorization code, so there's
-                        * nothing more we can do.
-                        */
-                       http_seterr(conn->err);
-                       goto ouch;
+                       if (proxy_challenges.valid) {
+                               /*
+                                * We already sent our proxy
+                                * authorization code, so there's
+                                * nothing more we can do. */
+                               http_seterr(conn->err);
+                               goto ouch;
+                       }
+                       /* try again, but send the password this time */
+                       if (verbose)
+                               fetch_info("proxy requires authorization");
+                       break;
                case HTTP_BAD_RANGE:
                        /*
                         * This can happen if we ask for 0 bytes because
@@ -1013,9 +1749,13 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                        /* fall through so we can get the full error message */
                }
 
-               /* get headers */
+               /* get headers. http_next_header expects one line readahead */
+               if (fetch_getln(conn) == -1) {
+                   fetch_syserr();
+                   goto ouch;
+               }
                do {
-                       switch ((h = http_next_header(conn, &p))) {
+                   switch ((h = http_next_header(conn, &headerbuf, &p))) {
                        case hdr_syserror:
                                fetch_syserr();
                                goto ouch;
@@ -1034,6 +1774,17 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                        case hdr_location:
                                if (!HTTP_REDIRECT(conn->err))
                                        break;
+                               /*
+                                * if the A flag is set, we don't follow
+                                * temporary redirects.
+                                */
+                               if (noredirect &&
+                                   conn->err != HTTP_MOVED_PERM &&
+                                   conn->err != HTTP_PERM_REDIRECT &&
+                                   conn->err != HTTP_USE_PROXY) {
+                                       n = 1;
+                                       break;
+                                }
                                if (new)
                                        free(new);
                                if (verbose)
@@ -1049,7 +1800,9 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                                        DEBUG(fprintf(stderr, "failed to parse new URL\n"));
                                        goto ouch;
                                }
-                               if (!*new->user && !*new->pwd) {
+
+                               /* Only copy credentials if the host matches */
+                               if (!strcmp(new->host, url->host) && !*new->user && !*new->pwd) {
                                        strcpy(new->user, url->user);
                                        strcpy(new->pwd, url->pwd);
                                }
@@ -1063,7 +1816,14 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                        case hdr_www_authenticate:
                                if (conn->err != HTTP_NEED_AUTH)
                                        break;
-                               /* if we were smarter, we'd check the method and realm */
+                               if (http_parse_authenticate(p, &server_challenges) == 0)
+                                       ++n;
+                               break;
+                       case hdr_proxy_authenticate:
+                               if (conn->err != HTTP_NEED_PROXY_AUTH)
+                                       break;
+                               if (http_parse_authenticate(p, &proxy_challenges) == 0)
+                                       ++n;
                                break;
                        case hdr_end:
                                /* fall through */
@@ -1074,9 +1834,17 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                } while (h > hdr_end);
 
                /* we need to provide authentication */
-               if (conn->err == HTTP_NEED_AUTH) {
+               if (conn->err == HTTP_NEED_AUTH ||
+                   conn->err == HTTP_NEED_PROXY_AUTH) {
                        e = conn->err;
-                       need_auth = 1;
+                       if ((conn->err == HTTP_NEED_AUTH &&
+                            !server_challenges.valid) ||
+                           (conn->err == HTTP_NEED_PROXY_AUTH &&
+                            !proxy_challenges.valid)) {
+                               /* 401/7 but no www/proxy-authenticate ?? */
+                               DEBUG(fprintf(stderr, "401/7 and no auth header\n"));
+                               goto ouch;
+                       }
                        fetch_close(conn);
                        conn = NULL;
                        continue;
@@ -1105,7 +1873,7 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
 
                /* all other cases: we got a redirect */
                e = conn->err;
-               need_auth = 0;
+               clean_http_auth_challenges(&server_challenges);
                fetch_close(conn);
                conn = NULL;
                if (!new) {
@@ -1181,7 +1949,9 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
                fclose(f);
                f = NULL;
        }
-
+       clean_http_headerbuf(&headerbuf);
+       clean_http_auth_challenges(&server_challenges);
+       clean_http_auth_challenges(&proxy_challenges);
        return (f);
 
 ouch:
@@ -1191,6 +1961,9 @@ ouch:
                fetchFreeURL(purl);
        if (conn != NULL)
                fetch_close(conn);
+       clean_http_headerbuf(&headerbuf);
+       clean_http_auth_challenges(&server_challenges);
+       clean_http_auth_challenges(&proxy_challenges);
        return (NULL);
 }
 
index bb6a461..e5389fe 100644 (file)
@@ -1,5 +1,4 @@
-# $FreeBSD: src/lib/libfetch/http.errors,v 1.5 2001/05/23 18:52:02 des Exp $
-# $DragonFly: src/lib/libfetch/http.errors,v 1.3 2007/08/05 21:48:12 swildner Exp $
+# $FreeBSD$
 #
 # This list is taken from RFC 2068.
 #
@@ -19,6 +18,7 @@
 304 OK         Not Modified
 305 INFO       Use Proxy
 307 MOVED      Temporary Redirect
+308 MOVED      Permanent Redirect
 400 PROTO      Bad Request
 401 AUTH       Unauthorized
 402 AUTH       Payment Required