From 3c990ad7cf0dc83f74bf58c4e62443c8866ebea6 Mon Sep 17 00:00:00 2001 From: bagyenda <> Date: Tue, 10 Apr 2007 17:09:05 +0000 Subject: [PATCH] Added Digest HTTP authentication --- mbuni/ChangeLog | 2 + mbuni/mmlib/mms_util.c | 310 +++++++++++++++++++++++++++++++++++ mbuni/mmlib/mms_util.h | 4 + mbuni/mmsbox/mmsbox.c | 68 -------- mbuni/mmsbox/mmsbox.h | 5 +- mbuni/mmsc/mmsglobalsender.c | 14 +- 6 files changed, 323 insertions(+), 80 deletions(-) diff --git a/mbuni/ChangeLog b/mbuni/ChangeLog index d013899..ca8dbfc 100644 --- a/mbuni/ChangeLog +++ b/mbuni/ChangeLog @@ -1,3 +1,5 @@ +2007-04-10 P. A. Bagyenda + * Added Digest/MD5 HTTP authentication support (out-going) 2007-04-10 P. A. Bagyenda * MM7/SOAP XMLNS string now configurable via setting per-MMC (or per-VASP) interface version 2007-04-02 Vincent Chavanis diff --git a/mbuni/mmlib/mms_util.c b/mbuni/mmlib/mms_util.c index 7ba07b2..c5b1161 100644 --- a/mbuni/mmlib/mms_util.c +++ b/mbuni/mmlib/mms_util.c @@ -1182,3 +1182,313 @@ void strip_boundary_element(List *headers, char *s) if (value) octstr_destroy(value); } + +static int fetch_url_with_auth(HTTPCaller *c, int method, Octstr *url, List *request_headers, + Octstr *body, Octstr *auth_hdr, List **reply_headers, Octstr **reply_body); + +int mms_url_fetch_content(int method, Octstr *url, List *request_headers, + Octstr *body, List **reply_headers, Octstr **reply_body) +{ + + int status = 0; + Octstr *furl = NULL; + + if (octstr_search(url, octstr_imm("data:"), 0) == 0) { + int i = octstr_search_char(url, ',',0); + Octstr *ctype = (i >= 0) ? octstr_copy(url, 5, i-5) : octstr_create("text/plain; charset=us-ascii"); + Octstr *data = (i >= 0) ? octstr_copy(url, i+1, octstr_len(url)) : octstr_duplicate(url); + + Octstr *n = NULL, *h = NULL; + + if (octstr_len(ctype) == 0) + octstr_append_cstr(ctype, "text/plain; charset=us-ascii"); + + split_header_value(ctype, &n, &h); + + if (h) { + List *ph = get_value_parameters(h); + Octstr *v = NULL; + + if (ph && (v = http_header_value(ph, octstr_imm("base64"))) != NULL) { /* has base64 item */ + Octstr *p = NULL; + + octstr_base64_to_binary(data); + http_header_remove_all(ph, "base64"); + + octstr_destroy(ctype); + + if (gwlist_len(ph) > 0) { + p = make_value_parameters(ph); + ctype = octstr_format("%S; %S", + n,p); + octstr_destroy(p); + } else + ctype = octstr_format("%S", n); + } + + if (ph) + http_destroy_headers(ph); + + octstr_destroy(v); + octstr_destroy(h); + } + + if (n) + octstr_destroy(n); + + *reply_body = data; + *reply_headers = http_create_empty_headers(); + http_header_add(*reply_headers, "Content-Type", octstr_get_cstr(ctype)); + + octstr_destroy(ctype); + status = HTTP_OK; + } else { + HTTPCaller *c = http_caller_create(); + http_start_request(c, method, url, request_headers, body, 1, NULL, NULL); + if (http_receive_result_real(c, &status, &furl, reply_headers, reply_body,1) == NULL) + status = -1; + if (status == HTTP_UNAUTHORIZED) { + Octstr *v = http_header_value(*reply_headers, octstr_imm("WWW-Authenticate")); + + status = fetch_url_with_auth(c, method, url, request_headers, body, v, + reply_headers, reply_body); + + octstr_destroy(v); + } + http_caller_destroy(c); + } + if (furl) + octstr_destroy(furl); + + return status; +} + + Octstr *get_stripped_param_value(Octstr *value, Octstr *param) +{ + Octstr *x = http_get_header_parameter(value, param); + + if (x != NULL && + octstr_get_char(x, 0) == '"' && + octstr_get_char(x, octstr_len(x) - 1) == '"') { + octstr_delete(x, 0, 1); + octstr_delete(x, octstr_len(x) - 1, 1); + } + return x; +} + +#define HASHLEN 16 +static Octstr *make_url(HTTPURLParse *h); + +/* Fetch a url with authentication as necessary. */ +static int fetch_url_with_auth(HTTPCaller *c, int method, Octstr *url, List *request_headers, + Octstr *body, Octstr *auth_hdr, List **reply_headers, Octstr **reply_body) +{ + Octstr *xauth_value = auth_hdr ? octstr_duplicate(auth_hdr) : octstr_create(""); + Octstr *domain = NULL, *nonce = NULL, *opaque = NULL, *algo = NULL, *auth_type = NULL, *x; + Octstr *realm = NULL, *xurl = NULL; + Octstr *cnonce = NULL; + char *nonce_count = "00000001"; + Octstr *A1 = NULL, *A2 = NULL, *rd = NULL; + List *qop = NULL, *l = NULL; + int i, status = HTTP_UNAUTHORIZED; + HTTPURLParse *h = parse_url(url); + unsigned char mdbuf[HASHLEN*2], *xs; + char *m_qop = NULL; + time_t t = time(NULL); + + /* Check that there is a username and password in the URL! */ + + if (h == NULL || h->user == NULL || octstr_len(h->user) == 0) + goto done; + + /* First we get the auth type: */ + + if ((i = octstr_search_char(xauth_value, ' ', 0)) < 0) { + warning(0, "Mal-formed WWW-Authenticate header (%s) received while fetching %s!", + octstr_get_cstr(xauth_value), url ? octstr_get_cstr(url) : ""); + status = -1; + goto done; + } + auth_type = octstr_copy(xauth_value, 0, i); + octstr_delete(xauth_value, 0, i+1); + + if (octstr_str_case_compare(auth_type, "Basic") == 0) { + status = HTTP_UNAUTHORIZED; /* suported by default by GWLIB so if we get here, means bad passwd. */ + goto done; + } /* else digest. */ + + /* Put back some fake data so what we have can be parsed easily. */ + if ((l = http_header_split_auth_value(xauth_value)) != NULL) { + Octstr *x = gwlist_get(l, 0); + octstr_insert(x, octstr_imm("_none; "), 0); /* make it easier to parse. */ + octstr_destroy(xauth_value); + xauth_value = x; + } else + warning(0, "Mal-formed Digest header (%s) while fetching (%s)!", + octstr_get_cstr(xauth_value), url ? octstr_get_cstr(url) : ""); + + realm = get_stripped_param_value(xauth_value, octstr_imm("realm")); + domain = get_stripped_param_value(xauth_value, octstr_imm("domain")); + nonce = get_stripped_param_value(xauth_value, octstr_imm("nonce")); + opaque = get_stripped_param_value(xauth_value, octstr_imm("opaque")); + algo = get_stripped_param_value(xauth_value, octstr_imm("algorithm")); + + if ((x = get_stripped_param_value(xauth_value, octstr_imm("qop"))) != NULL) { + qop = octstr_split(x, octstr_imm(",")); + octstr_destroy(x); + } + + mdbuf[0] = 0; + /* from here on, libssl is required. */ +#ifdef HAVE_LIBSSL + if (qop || + (algo != NULL && octstr_str_case_compare(algo, "MD5-sess") == 0)) { + unsigned char *x = MD5((void *)&t, sizeof t, (void *)mdbuf); + cnonce = octstr_create_from_data((void *)x, HASHLEN); + octstr_binary_to_hex(cnonce,0); + } + + /* Make A1 */ + x = octstr_format("%S:%S:%S", + h->user, realm, h->pass ? h->pass : octstr_imm("")); + // memset(mdbuf, 0, sizeof mdbuf); + xs = MD5((void *)octstr_get_cstr(x), octstr_len(x), (void *)mdbuf); + A1 = octstr_create_from_data((char *)xs, HASHLEN); + octstr_destroy(x); + + if (algo != NULL && octstr_str_case_compare(algo, "MD5-sess") == 0) { + x = octstr_format("%S:%S:%S", + A1, nonce, cnonce); + // memset(mdbuf, 0, sizeof mdbuf); + xs = MD5((void *)octstr_get_cstr(x), octstr_len(x), (void *)mdbuf); + octstr_destroy(A1); + A1 = octstr_create_from_data((char *)xs, HASHLEN); + octstr_destroy(x); + } + octstr_binary_to_hex(A1,0); + + /* Make A2. */ + x = octstr_format("%s:%S", + http_method2name(method), + h->path); + if (qop != NULL && /* if qop, and qop=auth-int */ + gwlist_search(qop, "auth-int", + (gwlist_item_matches_t *)octstr_str_case_compare) != NULL && + gwlist_search(qop, "auth", + (gwlist_item_matches_t *)octstr_str_case_compare) == NULL) { + Octstr *y; + m_qop = "auth-int"; + +// memset(mdbuf, 0, sizeof mdbuf); + xs = MD5((void *)octstr_get_cstr(body), octstr_len(body), (void *)mdbuf); + y = octstr_create_from_data((char *)xs, HASHLEN); + octstr_binary_to_hex(y,0); + + octstr_append_char(x, ':'); + octstr_append(x, y); + + octstr_destroy(y); + } else if (qop) + m_qop = "auth"; +// memset(mdbuf, 0, sizeof mdbuf); + xs = MD5((void *)octstr_get_cstr(x), octstr_len(x), (void *)mdbuf); + A2 = octstr_create_from_data((char *)xs, HASHLEN); + octstr_destroy(x); + octstr_binary_to_hex(A2,0); + + /* Finally make the digest response */ + if (qop) + x = octstr_format("%S:%S:%s:%S:%s:%S", + A1, nonce, nonce_count, cnonce, + m_qop, A2); + else + x = octstr_format("%S:%S:%S", A1, nonce, A2); +// memset(mdbuf, 0, sizeof mdbuf); + xs = MD5((void *)octstr_get_cstr(x), octstr_len(x), (void *)mdbuf); + octstr_destroy(x); + + rd = octstr_create_from_data((char *)xs, HASHLEN); + octstr_binary_to_hex(rd, 0); + + + /* make the header value */ + x = octstr_format("Digest username=\"%S\", realm=\"%S\", response=\"%S\", nonce=\"%S\", uri=\"%S\"", + h->user, realm, rd, nonce, h->path); + + if (opaque) + octstr_format_append(x, ", opaque=\"%S\"", opaque); + + if (cnonce) + octstr_format_append(x, ", cnonce=\"%S\", nc=%s", cnonce, nonce_count); + if (m_qop) + octstr_format_append(x,", qop=%s", m_qop); + if (algo) + octstr_format_append(x,", algorithm=%S", algo); + + http_header_remove_all(request_headers, "Authorization"); + http_header_add(request_headers, "Authorization", octstr_get_cstr(x)); + octstr_destroy(x); + + /* Remove username, password, then remake URL */ + if (h->user) octstr_destroy(h->user); + h->user = NULL; + + if (h->pass) octstr_destroy(h->pass); + h->pass = NULL; + + xurl = make_url(h); + x = NULL; + http_start_request(c, method, xurl, request_headers, body, 1, NULL, NULL); + if (http_receive_result_real(c, &status, &x, reply_headers, reply_body,1) == NULL) + status = -1; + if (x) + octstr_destroy(x); +#else + error(0, "Digest authentication requested on url (%s), but SSL not compiled!", + octstr_get_cstr(url)); +#endif + done: + octstr_destroy(xauth_value); + if (realm) + octstr_destroy(realm); + if (domain) + octstr_destroy(domain); + if (nonce) + octstr_destroy(nonce); + if (opaque) + octstr_destroy(opaque); + if (algo) + octstr_destroy(algo); + if (xurl) + octstr_destroy(xurl); + if (qop) + gwlist_destroy(qop, (gwlist_item_destructor_t *)octstr_destroy); + + if (h) + http_urlparse_destroy(h); + if (cnonce) + octstr_destroy(cnonce); + return status; +} + + +static Octstr *make_url(HTTPURLParse *h) +{ + Octstr *url = octstr_duplicate(h->scheme); + + if (h->user) { + octstr_format_append(url, "%S", h->user); + + if (h->pass) + octstr_format_append(url, ":%S", h->pass); + octstr_format_append(url, "@"); + } + octstr_format_append(url, "%S:%d%S", h->host, h->port, h->path); + + if (h->query) + octstr_format_append(url, "?%S", h->query); + + if (h->fragment) + octstr_format_append(url, "#%S", h->fragment); + return url; +} diff --git a/mbuni/mmlib/mms_util.h b/mbuni/mmlib/mms_util.h index 5604485..7f2c6d3 100644 --- a/mbuni/mmlib/mms_util.h +++ b/mbuni/mmlib/mms_util.h @@ -168,6 +168,10 @@ Octstr *_x_get_content_id(List *headers); /* Remove the boundary element from a list of headers. */ void strip_boundary_element(List *headers, char *s); +/* Fetch a URL. If necessary, authenticate, etc. also understands data: url scheme. */ +int mms_url_fetch_content(int method, Octstr *url, List *request_headers, + Octstr *body, List **reply_headers, Octstr **reply_body); + #define MAXQTRIES 100 #define BACKOFF_FACTOR 5*60 /* In seconds */ #define QUEUERUN_INTERVAL 1*60 /* 1 minutes. */ diff --git a/mbuni/mmsbox/mmsbox.c b/mbuni/mmsbox/mmsbox.c index 17442f2..2026cff 100644 --- a/mbuni/mmsbox/mmsbox.c +++ b/mbuni/mmsbox/mmsbox.c @@ -579,74 +579,6 @@ int main(int argc, char *argv[]) return 0; } -int mmsbox_url_fetch_content(int method, Octstr *url, List *request_headers, - Octstr *body, List **reply_headers, Octstr **reply_body) -{ - - int status = 0; - Octstr *furl = NULL; - - if (octstr_search(url, octstr_imm("data:"), 0) == 0) { - int i = octstr_search_char(url, ',',0); - Octstr *ctype = (i >= 0) ? octstr_copy(url, 5, i-5) : octstr_create("text/plain; charset=us-ascii"); - Octstr *data = (i >= 0) ? octstr_copy(url, i+1, octstr_len(url)) : octstr_duplicate(url); - - Octstr *n = NULL, *h = NULL; - - if (octstr_len(ctype) == 0) - octstr_append_cstr(ctype, "text/plain; charset=us-ascii"); - - split_header_value(ctype, &n, &h); - - if (h) { - List *ph = get_value_parameters(h); - Octstr *v = NULL; - - if (ph && (v = http_header_value(ph, octstr_imm("base64"))) != NULL) { /* has base64 item */ - Octstr *p = NULL; - - octstr_base64_to_binary(data); - http_header_remove_all(ph, "base64"); - - octstr_destroy(ctype); - - if (gwlist_len(ph) > 0) { - p = make_value_parameters(ph); - ctype = octstr_format("%S; %S", - n,p); - octstr_destroy(p); - } else - ctype = octstr_format("%S", n); - } - - if (ph) - http_destroy_headers(ph); - - octstr_destroy(v); - octstr_destroy(h); - } - - if (n) - octstr_destroy(n); - - *reply_body = data; - *reply_headers = http_create_empty_headers(); - http_header_add(*reply_headers, "Content-Type", octstr_get_cstr(ctype)); - - octstr_destroy(ctype); - status = HTTP_OK; - } else { - HTTPCaller *c = http_caller_create(); - http_start_request(c, method, url, request_headers, body, 1, NULL, NULL); - if (http_receive_result_real(c, &status, &furl, reply_headers, reply_body,1) == NULL) - status = -1; - http_caller_destroy(c); - } - if (furl) - octstr_destroy(furl); - - return status; -} /* Mapping file extensions to content types. */ static struct { diff --git a/mbuni/mmsbox/mmsbox.h b/mbuni/mmsbox/mmsbox.h index 78b61a9..e7c7196 100644 --- a/mbuni/mmsbox/mmsbox.h +++ b/mbuni/mmsbox/mmsbox.h @@ -21,7 +21,6 @@ void mms_dlr_url_remove(Octstr *msgid, char *rtype, Octstr *mmc_gid); void mmsc_receive_func(MmscGrp *m); void mmsbox_outgoing_queue_runner(int *rstop); -/* Fetch content given method. also understands data: url scheme. */ -int mmsbox_url_fetch_content(int method, Octstr *url, List *request_headers, - Octstr *body, List **reply_headers, Octstr **reply_body); +/* Just a convenience, should go away in future! */ +#define mmsbox_url_fetch_content mms_url_fetch_content #endif diff --git a/mbuni/mmsc/mmsglobalsender.c b/mbuni/mmsc/mmsglobalsender.c index f00baa6..ec37864 100644 --- a/mbuni/mmsc/mmsglobalsender.c +++ b/mbuni/mmsc/mmsglobalsender.c @@ -534,8 +534,7 @@ static int mm7soap_send(MmsVasp *vasp, Octstr *from, Octstr *to, Octstr *msgId, List *xto = gwlist_create(); MSoapMsg_t *mreq = NULL, *mresp = NULL; List *rh = NULL, *ph = NULL; - Octstr *body = NULL, *rbody = NULL, *url = NULL; - HTTPCaller *caller = http_caller_create(); + Octstr *body = NULL, *rbody = NULL; void *xx; Octstr *s; @@ -558,11 +557,10 @@ static int mm7soap_send(MmsVasp *vasp, Octstr *from, Octstr *to, Octstr *msgId, *error = octstr_format("Failed to convert SOAP message 2 HTTP Msg!"); goto done1; } - - http_start_request(caller, HTTP_METHOD_POST, vasp->vasp_url, rh, body, 1, NULL, NULL); + + hstatus = mms_url_fetch_content(HTTP_METHOD_POST, vasp->vasp_url, rh, body, &ph, &rbody); - if ((xx = http_receive_result_real(caller, &hstatus, &url, &ph, &rbody,1)) == NULL || - http_status_class(hstatus) != HTTP_STATUS_SUCCESSFUL) { + if (http_status_class(hstatus) != HTTP_STATUS_SUCCESSFUL) { *error = octstr_format("Failed to contact VASP[url=%s] => HTTP returned status = %d, id=%s !", octstr_get_cstr(vasp->vasp_url), hstatus, xx ? "Ok" : "not OK"); goto done1; @@ -616,9 +614,7 @@ static int mm7soap_send(MmsVasp *vasp, Octstr *from, Octstr *to, Octstr *msgId, http_destroy_headers(ph); if (rbody) octstr_destroy(rbody); - if (url) - octstr_destroy(url); - http_caller_destroy(caller); + gwlist_destroy(xto, NULL); return ret;