From owner-freebsd-bugs@FreeBSD.ORG Mon Feb 25 10:00:01 2008 Return-Path: Delivered-To: freebsd-bugs@hub.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id D56F916A404 for ; Mon, 25 Feb 2008 10:00:01 +0000 (UTC) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (freefall.freebsd.org [IPv6:2001:4f8:fff6::28]) by mx1.freebsd.org (Postfix) with ESMTP id B35F513C50C for ; Mon, 25 Feb 2008 10:00:01 +0000 (UTC) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.14.2/8.14.2) with ESMTP id m1PA01wt026551 for ; Mon, 25 Feb 2008 10:00:01 GMT (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.14.2/8.14.1/Submit) id m1PA01JN026550; Mon, 25 Feb 2008 10:00:01 GMT (envelope-from gnats) Resent-Date: Mon, 25 Feb 2008 10:00:01 GMT Resent-Message-Id: <200802251000.m1PA01JN026550@freefall.freebsd.org> Resent-From: FreeBSD-gnats-submit@FreeBSD.org (GNATS Filer) Resent-To: freebsd-bugs@FreeBSD.org Resent-Reply-To: FreeBSD-gnats-submit@FreeBSD.org, Yen-Ming Lee Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 6D7D816A402 for ; Mon, 25 Feb 2008 09:55:31 +0000 (UTC) (envelope-from leeym@db1.leeym.com) Received: from db1.leeym.com (db1.leeym.com [66.79.165.131]) by mx1.freebsd.org (Postfix) with ESMTP id 5438C13C46E for ; Mon, 25 Feb 2008 09:55:31 +0000 (UTC) (envelope-from leeym@db1.leeym.com) Received: from db1.leeym.com (localhost [127.0.0.1]) by db1.leeym.com (8.14.1/8.14.1) with ESMTP id m1P9uCKh065231; Mon, 25 Feb 2008 01:56:12 -0800 (PST) (envelope-from leeym@db1.leeym.com) Received: (from leeym@localhost) by db1.leeym.com (8.14.2/8.14.2/Submit) id m1P9uCKG065230; Mon, 25 Feb 2008 01:56:12 -0800 (PST) (envelope-from leeym) Message-Id: <200802250956.m1P9uCKG065230@db1.leeym.com> Date: Mon, 25 Feb 2008 01:56:12 -0800 (PST) From: Yen-Ming Lee To: FreeBSD-gnats-submit@FreeBSD.org X-Send-Pr-Version: 3.113 Cc: phk@FreeBSD.org, des@FreeBSD.org Subject: bin/121074: Add RFC2617 digest authentication to fetch(3) X-BeenThere: freebsd-bugs@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list Reply-To: Yen-Ming Lee List-Id: Bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 25 Feb 2008 10:00:02 -0000 >Number: 121074 >Category: bin >Synopsis: Add RFC2617 digest authentication to fetch(3) >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: change-request >Submitter-Id: current-users >Arrival-Date: Mon Feb 25 10:00:01 UTC 2008 >Closed-Date: >Last-Modified: >Originator: Yen-Ming Lee >Release: FreeBSD 6.2-RELEASE i386 >Organization: >Environment: System: FreeBSD db1.leeym.com 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40:27 UTC 2007 root@dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC i386 >Description: Add RFC2617 digest authentication. qop=auth-int and non-MD5 algorithm are not implemented yet. http://www.freebsd.org/projects/ideas/#p-libfetchauth >How-To-Repeat: >Fix: --- libfetchauth.diff begins here --- Index: Makefile =================================================================== RCS file: /home/ncvs/src/lib/libfetch/Makefile,v retrieving revision 1.51 diff -u -r1.51 Makefile --- Makefile 19 Dec 2007 05:10:07 -0000 1.51 +++ Makefile 25 Feb 2008 09:49:50 -0000 @@ -21,6 +21,7 @@ .endif CFLAGS+= -DFTP_COMBINE_CWDS +LDADD+= -lmd CSTD?= c99 WARNS?= 2 Index: http.c =================================================================== RCS file: /home/ncvs/src/lib/libfetch/http.c,v retrieving revision 1.84 diff -u -r1.84 http.c --- http.c 8 Feb 2008 09:48:48 -0000 1.84 +++ http.c 25 Feb 2008 09:49:50 -0000 @@ -75,6 +75,7 @@ #include #include #include +#include #include #include @@ -127,6 +128,127 @@ }; /* + * Authentication scheme types + */ +typedef enum { + auth_unknown = -1, + auth_basic = 0, + auth_digest = 1, +} auth_scheme_t; + +/* + * Information needed to construct request_digest + */ +struct httpdigest +{ + /* RFC 2617: 3.2.1 digest-challenge */ + char *realm; + char *domain; + char *nonce; + char *opaque; + char *stale; + char *algorithm; + char *qop; + /* RFC 2617: 3.2.2.3 A2 */ + char *method; + char *uri; +}; + +/* + * Free a digest + */ +void +http_free_digest(struct httpdigest *d) +{ + if (!d) + return; + if (d->realm) + free(d->realm); + if (d->domain) + free(d->domain); + if (d->nonce) + free(d->nonce); + if (d->opaque) + free(d->opaque); + if (d->stale) + free(d->stale); + if (d->algorithm) + free(d->algorithm); + if (d->qop) + free(d->qop); + if (d->method) + free(d->method); + if (d->uri) + free(d->uri); + free(d); +} + +static char * +http_parse_value(const char *hdr, const char *key) +{ + char *begin, *_key, *end, *value, *sep = ","; + int len; + len = strlen(key); + if ((_key = malloc(len+1)) == NULL) + return NULL; + sprintf(_key, "%s=", key); + begin = strcasestr(hdr, _key); + if (!begin) + goto ouch; + begin += len + 1; + if (*begin == '"') { + begin++; + sep = "\""; + } + end = strstr(begin, sep); + if (!end) + goto ouch; + len = end - begin + 1; + value = malloc(len); + strlcpy(value, begin, len); + return value; + +ouch: + free(_key); + return NULL; +} + +/* + * Construct a digest based on WWW-Authenticate header + */ +auth_scheme_t +http_parse_authenticate(const char *p, const char *method, const char *uri, struct httpdigest *d) +{ + /* Only handle "digest" authentication scheme */ + if (strncasecmp(p, "Basic", 5) == 0) { + return auth_basic; + } else if (strncasecmp(p, "Digest", 6)) { + return auth_unknown; + } + + d->realm = http_parse_value(p, "realm"); + d->domain = http_parse_value(p, "domain"); + d->nonce = http_parse_value(p, "nonce"); + d->opaque = http_parse_value(p, "opaque"); + d->stale = http_parse_value(p, "stale"); + d->algorithm = http_parse_value(p, "algorithm"); + d->qop = http_parse_value(p, "qop"); + d->method = strdup(method); + d->uri = strdup(uri); + + if (!d->realm || !d->nonce || !d->method || !d->uri) { + http_seterr(HTTP_PROTOCOL_ERROR); + goto ouch; + } + + return auth_digest; + +ouch: + http_free_digest(d); + return auth_unknown; +} + +/* * Get next chunk header */ static int @@ -617,6 +739,18 @@ } /* + * MD5 encoding + */ +static char * +http_md5(const char *src, char *dst) +{ + MD5_CTX Md5Ctx; + MD5Init(&Md5Ctx); + MD5Update(&Md5Ctx, src, strlen(src)); + return MD5End(&Md5Ctx, dst); +} + +/* * Encode username and password */ static int @@ -639,10 +773,95 @@ } /* + * RFC 2617: Digest Access Authentication + */ +static int +http_digest_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd, struct httpdigest *d) +{ + char buf[8192]; + char HA1[33], HA2[33], request_digest[33]; + char cnonce[9]; + int r; + static int nc = 0; + + DEBUG(fprintf(stderr, "usr: [%s]\n", usr)); + DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd)); + + if (!d || !d->realm) + return(-1); + ++nc; + sprintf(cnonce, "%08X", time(NULL)); + + /* RFC 2617: 3.2.2.2 A1 */ + if (!d->algorithm || strcasecmp(d->algorithm, "MD5") == 0) { + sprintf(buf, "%s:%s:%s", usr, d->realm, pwd); + } else if (strcasecmp(d->algorithm, "MD5-sess") == 0) { + sprintf(buf, "%s:%s:%s", usr, d->realm, pwd); + http_md5(buf, HA1); + strcpy(buf, HA1); + strcat(buf, ":"); + strcat(buf, d->nonce); + strcat(buf, ":"); + strcat(buf, cnonce); + } else { + warnx("http_digest_auth(): non-MD5 algorithm not implemented"); + return (-1); + } + http_md5(buf, HA1); + + /* RFC 2617: 3.2.2.3 A2 */ + if (!d->qop || strcasecmp(d->qop, "auth") == 0) { + sprintf(buf, "%s:%s", d->method, d->uri); + http_md5(buf, HA2); + } else if (strcasecmp(d->qop, "auth-int") == 0) { + warnx("http_digest_auth(): qop=auth-int not implemented"); + return (-1); + } + /* RFC 2617: 3.2.2.1 Request-Digest */ + if (!d->qop) { + sprintf(buf, "%s:%s:%s", HA1, d->nonce, HA2); + http_md5(buf, request_digest); + } else if (strcasecmp(d->qop, "auth") == 0 || strcasecmp(d->qop, "auth-int") == 0) { + sprintf(buf, "%s:%s:%08d:%s:%s:%s", HA1, d->nonce, nc, cnonce, d->qop, HA2); + http_md5(buf, request_digest); + } + if (d->qop) { + sprintf(buf, + "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=\"%s\", nc=\"%08d\", cnonce=\"%s\", response=\"%s\"", + usr, d->realm, d->nonce, d->uri, d->qop, nc, cnonce, request_digest); + } else { + sprintf(buf, + "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=\"%s\", response=\"%s\"", + usr, d->realm, d->nonce, d->uri, d->qop, request_digest); + } + if (d->opaque) { + strcat(buf, ", opaque=\""); + strcat(buf, d->opaque); + strcat(buf, "\""); + } + r = http_cmd(conn, "%s: Digest %s", hdr, buf); + return (r); +} + +/* + * Send an authorization header based on authentication scheme + */ +static int +http_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd, auth_scheme_t auth, struct httpdigest *d) +{ + if (auth == auth_basic) + return http_basic_auth(conn, hdr, usr, pwd); + else if (auth == auth_digest) + return http_digest_auth(conn, hdr, usr, pwd, d); + else + return (-1); +} + +/* * Send an authorization header */ static int -http_authorize(conn_t *conn, const char *hdr, const char *p) +http_authorize(conn_t *conn, const char *hdr, const char *p, struct httpdigest *digest) { /* basic authorization */ if (strncasecmp(p, "basic:", 6) == 0) { @@ -663,6 +882,26 @@ free(str); return (r); } + /* digest authorization */ + else if (strncasecmp(p, "digest:", 7) == 0) { + char *user, *pwd, *str; + int r; + if (!digest) + return (-1); + /* skip realm */ + for (p += 7; *p && *p != ':'; ++p) + /* nothing */ ; + if (!*p || strchr(++p, ':') == NULL) + return (-1); + if ((str = strdup(p)) == NULL) + return (-1); /* XXX */ + user = str; + pwd = strchr(user, ':'); + *pwd++ = '\0'; + r = http_digest_auth(conn, hdr, user, pwd, digest); + free(str); + return (r); + } return (-1); } @@ -807,6 +1046,12 @@ FILE *f; hdr_t h; char hbuf[MAXHOSTNAMELEN + 7], *host; + auth_scheme_t auth; + struct httpdigest *digest = calloc(1, sizeof(struct httpdigest)); + if (digest == NULL) { + DEBUG(fprintf(stderr, "failed to allocate httpdigest\n")); + goto ouch; + } direct = CHECK_FLAG('d'); noredirect = CHECK_FLAG('A'); @@ -888,23 +1133,29 @@ 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); + http_authorize(conn, "Proxy-Authorization", p, digest); } /* 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); + if (*url->user || *url->pwd) { + http_auth(conn, "Authorization", url->user, url->pwd, auth, digest); + } + else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0') { + http_authorize(conn, "Authorization", p, digest); + } else if (fetchAuthMethod && fetchAuthMethod(url) == 0) { - http_basic_auth(conn, "Authorization", url->user, url->pwd); + http_auth(conn, "Authorization", url->user, url->pwd, auth, digest); } else { http_seterr(HTTP_NEED_AUTH); goto ouch; } } + if (purl || need_auth || *url->user || *url->pwd) { + http_free_digest(digest); + } + /* other headers */ if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') { if (strcasecmp(p, "auto") == 0) @@ -1038,9 +1289,9 @@ chunked = (strcasecmp(p, "chunked") == 0); break; case hdr_www_authenticate: + auth = http_parse_authenticate(p, op, url->doc, digest); if (conn->err != HTTP_NEED_AUTH) break; - /* if we were smarter, we'd check the method and realm */ break; case hdr_end: /* fall through */ --- libfetchauth.diff ends here --- >Release-Note: >Audit-Trail: >Unformatted: