summaryrefslogtreecommitdiffstats
path: root/lib/libfetch/http.c
diff options
context:
space:
mode:
authordes <des@FreeBSD.org>2010-01-19 10:19:55 +0000
committerdes <des@FreeBSD.org>2010-01-19 10:19:55 +0000
commit709f5f5dad5d51c3e44c9536f251fb5b4226e454 (patch)
tree051a65f5a2827c19cb52ae3b0ad396eb3fd7ecf7 /lib/libfetch/http.c
parente3e987d4d1f2740c3f6820b69ac9170ffb38cfa5 (diff)
downloadFreeBSD-src-709f5f5dad5d51c3e44c9536f251fb5b4226e454.zip
FreeBSD-src-709f5f5dad5d51c3e44c9536f251fb5b4226e454.tar.gz
Add HTTP digest authentication.
Submitted by: Jean-Francois Dockes <jf@dockes.org> Forgotten by: des (repeatedly)
Diffstat (limited to 'lib/libfetch/http.c')
-rw-r--r--lib/libfetch/http.c882
1 files changed, 816 insertions, 66 deletions
diff --git a/lib/libfetch/http.c b/lib/libfetch/http.c
index dd98349..f44366e 100644
--- a/lib/libfetch/http.c
+++ b/lib/libfetch/http.c
@@ -76,6 +76,7 @@ __FBSDID("$FreeBSD$");
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <md5.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
@@ -343,7 +344,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 */
@@ -357,6 +359,7 @@ static struct {
{ hdr_location, "Location" },
{ hdr_transfer_encoding, "Transfer-Encoding" },
{ hdr_www_authenticate, "WWW-Authenticate" },
+ { hdr_proxy_authenticate, "Proxy-Authenticate" },
{ hdr_unknown, NULL },
};
@@ -446,21 +449,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)
+{
+ 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
@@ -468,11 +564,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
*/
@@ -618,6 +993,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
+#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
*/
@@ -627,8 +1287,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);
@@ -641,33 +1301,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);
+ }
+
+ /* 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];
}
- return (-1);
-}
+ /* 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
@@ -797,13 +1473,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;
@@ -811,6 +1487,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');
@@ -830,7 +1514,6 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
i = 0;
e = HTTP_PROTOCOL_ERROR;
- need_auth = 0;
do {
new = NULL;
chunked = 0;
@@ -895,27 +1578,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 */
@@ -965,7 +1688,7 @@ http_request(struct url *URL, const char *op, struct url_stat *us,
*/
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.
@@ -978,13 +1701,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
@@ -1004,9 +1732,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;
@@ -1054,7 +1786,12 @@ 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 */
+ http_parse_authenticate(p, &server_challenges);
+ break;
+ case hdr_proxy_authenticate:
+ if (conn->err != HTTP_NEED_PROXY_AUTH)
+ break;
+ http_parse_authenticate(p, &proxy_challenges);
break;
case hdr_end:
/* fall through */
@@ -1065,9 +1802,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;
@@ -1096,7 +1841,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) {
@@ -1172,7 +1917,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:
@@ -1182,6 +1929,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);
}
OpenPOWER on IntegriCloud