diff --git a/include/re_dns.h b/include/re_dns.h index 8c63ef6a8..9c43243d5 100644 --- a/include/re_dns.h +++ b/include/re_dns.h @@ -204,6 +204,7 @@ struct dnsc_conf { int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf, const struct sa *srvv, uint32_t srvc); +int dnsc_conf_set(struct dnsc *dnsc, const struct dnsc_conf *conf); int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc); int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, diff --git a/include/re_http.h b/include/re_http.h index 14b95377f..b51d6a36e 100644 --- a/include/re_http.h +++ b/include/re_http.h @@ -87,6 +87,15 @@ struct http_msg { uint32_t clen; /**< Content length */ }; +struct http_uri { + struct pl scheme; + struct pl host; + struct pl port; + struct pl path; +}; + +int http_uri_decode(struct http_uri *hu, const struct pl *uri); + typedef bool(http_hdr_h)(const struct http_hdr *hdr, void *arg); int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req); @@ -125,15 +134,24 @@ typedef void (http_conn_h)(struct tcp_conn *tc, struct tls_conn *sc, void *arg); int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc); -int http_client_add_ca(struct http_cli *cli, const char *tls_ca); -int http_client_set_tls_hostname(struct http_cli *cli, - const struct pl *hostname); +int http_client_set_timeout(struct http_cli *cli, uint32_t ms); int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, http_resp_h *resph, http_data_h *datah, void *arg, const char *fmt, ...); void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh); -void http_client_set_laddr(struct http_cli *cli, struct sa *addr); -void http_client_set_laddr6(struct http_cli *cli, struct sa *addr); +void http_client_set_laddr(struct http_cli *cli, const struct sa *addr); +void http_client_set_laddr6(struct http_cli *cli, const struct sa *addr); + +#ifdef USE_TLS +int http_client_add_ca(struct http_cli *cli, const char *tls_ca); +int http_client_add_capem(struct http_cli *cli, const char *capem); +int http_client_set_tls_hostname(struct http_cli *cli, + const struct pl *hostname); +int http_client_set_cert(struct http_cli *cli, const char *path); +int http_client_set_certpem(struct http_cli *cli, const char *pem); +int http_client_set_key(struct http_cli *cli, const char *path); +int http_client_set_keypem(struct http_cli *cli, const char *pem); +#endif /* Server */ struct http_sock; @@ -173,3 +191,24 @@ bool http_auth_check(const struct pl *hval, const struct pl *method, bool http_auth_check_request(const struct http_msg *msg, struct http_auth *auth, http_auth_h *authh, void *arg); + +/* http_reqconn - HTTP request connection */ +struct http_reqconn; +int http_reqconn_alloc(struct http_reqconn **pconn, + struct http_cli *client, + http_resp_h *resph, http_data_h *datah, void* arg); +int http_reqconn_set_auth(struct http_reqconn *conn, const struct pl *user, + const struct pl *pass); +int http_reqconn_set_bearer(struct http_reqconn *conn, + const struct pl *bearer); +int http_reqconn_set_method(struct http_reqconn *conn, const struct pl *met); +int http_reqconn_set_body(struct http_reqconn *conn, const struct pl *body); +int http_reqconn_set_ctype(struct http_reqconn *conn, const struct pl *ctype); +int http_reqconn_add_header(struct http_reqconn *conn, + const struct pl *header); +int http_reqconn_clr_header(struct http_reqconn *conn); +int http_reqconn_send(struct http_reqconn *conn, const struct pl *uri); +#ifdef USE_TLS +int http_reqconn_set_tls_hostname(struct http_reqconn *conn, + const struct pl *hostname); +#endif diff --git a/include/re_httpauth.h b/include/re_httpauth.h index 32786e3a4..cf36e0ad2 100644 --- a/include/re_httpauth.h +++ b/include/re_httpauth.h @@ -29,6 +29,16 @@ struct httpauth_digest_resp { struct pl nc; struct pl cnonce; struct pl qop; + + struct mbuf *mb; +}; + + +/** HTTP Basic */ +struct httpauth_basic { + struct mbuf *mb; + struct pl realm; + struct pl auth; }; @@ -38,3 +48,16 @@ int httpauth_digest_response_decode(struct httpauth_digest_resp *resp, const struct pl *hval); int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp, const struct pl *method, const uint8_t *ha1); +int httpauth_digest_make_response(struct httpauth_digest_resp **resp, + const struct httpauth_digest_chall *chall, + const char *path, const char *method, const char *user, + const char *pwd, const char *body); +int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, + struct mbuf *mb); + +struct httpauth_basic *httpauth_basic_alloc(void); +int httpauth_basic_decode(struct httpauth_basic *basic, + const struct pl *hval); +int httpauth_basic_make_response(struct httpauth_basic *basic, + const char *user, const char *pwd); +int httpauth_basic_encode(const struct httpauth_basic *basic, struct mbuf *mb); diff --git a/include/re_tls.h b/include/re_tls.h index 0bfff72f7..83090f145 100644 --- a/include/re_tls.h +++ b/include/re_tls.h @@ -33,6 +33,7 @@ enum tls_keytype { int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, const char *pwd); int tls_add_ca(struct tls *tls, const char *cafile); +int tls_add_capem(struct tls *tls, const char *capem); int tls_set_selfsigned(struct tls *tls, const char *cn); int tls_set_selfsigned_rsa(struct tls *tls, const char *cn, size_t bits); int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert, @@ -51,7 +52,6 @@ int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type, int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size); int tls_peer_set_verify_host(struct tls_conn *tc, const char *hostname); int tls_set_verify_purpose(struct tls *tls, const char *purpose); -int tls_set_hostname(char *tls_hostname, const struct pl *hostname); int tls_peer_verify(const struct tls_conn *tc); int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite, uint8_t *cli_key, size_t cli_key_size, diff --git a/src/dns/client.c b/src/dns/client.c index 99124e044..006b81e6b 100644 --- a/src/dns/client.c +++ b/src/dns/client.c @@ -903,6 +903,33 @@ int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf, } +int dnsc_conf_set(struct dnsc *dnsc, const struct dnsc_conf *conf) +{ + int err; + if (!dnsc) + return EINVAL; + + if (conf) + dnsc->conf = *conf; + else + dnsc->conf = default_conf; + + + dnsc->ht_query = mem_deref(dnsc->ht_query); + dnsc->ht_tcpconn = mem_deref(dnsc->ht_tcpconn); + + err = hash_alloc(&dnsc->ht_query, dnsc->conf.query_hash_size); + if (err) + return err; + + err = hash_alloc(&dnsc->ht_tcpconn, dnsc->conf.tcp_hash_size); + if (err) + return err; + + return err; +} + + /** * Set the DNS Servers on a DNS Client * diff --git a/src/http/client.c b/src/http/client.c index 36f464558..173dcb255 100644 --- a/src/http/client.c +++ b/src/http/client.c @@ -22,12 +22,19 @@ #include "http.h" +#define DEBUG_MODULE "http_client" +#define DEBUG_LEVEL 5 +#include + + enum { CONN_TIMEOUT = 30000, RECV_TIMEOUT = 60000, IDLE_TIMEOUT = 900000, BUFSIZE_MAX = 524288, CONN_BSIZE = 256, + QUERY_HASH_SIZE = 16, + TCP_HASH_SIZE = 2, }; struct http_cli { @@ -35,7 +42,9 @@ struct http_cli { struct hash *ht_conn; struct dnsc *dnsc; struct tls *tls; - char *tls_hostname; + char *tlshn; + char *cert; + char *key; struct sa laddr; #ifdef HAVE_INET6 struct sa laddr6; @@ -100,9 +109,11 @@ static void cli_destructor(void *arg) hash_flush(cli->ht_conn); mem_deref(cli->ht_conn); + mem_deref(cli->cert); + mem_deref(cli->key); mem_deref(cli->dnsc); mem_deref(cli->tls); - mem_deref(cli->tls_hostname); + mem_deref(cli->tlshn); } @@ -461,9 +472,9 @@ static int conn_connect(struct http_req *req) if (err) goto out; - if (req->cli->tls_hostname) + if (req->cli->tlshn) err = tls_peer_set_verify_host(conn->sc, - req->cli->tls_hostname); + req->cli->tlshn); if (err) goto out; @@ -556,6 +567,65 @@ static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, } +#ifdef USE_TLS +static int read_file(char **buf, const char *path) +{ + FILE *f = NULL; + size_t s = 0; + size_t n = 0; + + if (!buf || !path) + return EINVAL; + + f = fopen(path, "r"); + if (!f) { + DEBUG_WARNING("Could not open cert file '%s'\n", path); + return EIO; + } + + fseek(f, 0L, SEEK_END); + s = ftell(f); + fseek(f, 0L, SEEK_SET); + + *buf = mem_alloc(s + 1, NULL); + if (!buf) { + DEBUG_WARNING("Could not allocate cert file buffer\n"); + fclose(f); + return ENOMEM; + } + + n = fread(buf, 1, s, f); + fclose(f); + buf[s] = 0; + if (n < s) { + *buf = mem_deref(*buf); + return EIO; + } + + return 0; +} +#endif + + +int http_uri_decode(struct http_uri *hu, const struct pl *uri) +{ + if (!hu) + return EINVAL; + + memset(hu, 0, sizeof(*hu)); + + /* Try IPv6 first */ + if (!re_regex(uri->p, uri->l, "[a-z]+://\\[[^\\]]+\\][:]*[0-9]*[^]+", + &hu->scheme, &hu->host, NULL, &hu->port, &hu->path)) + return hu->scheme.p == uri->p ? 0 : EINVAL; + + /* Then non-IPv6 host */ + return re_regex(uri->p, uri->l, "[a-z]+://[^:/]+[:]*[0-9]*[^]+", + &hu->scheme, &hu->host, NULL, &hu->port, &hu->path) || + hu->scheme.p != uri->p; +} + + /** * Send an HTTP request * @@ -574,7 +644,8 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, http_resp_h *resph, http_data_h *datah, void *arg, const char *fmt, ...) { - struct pl scheme, host, port, path; + struct http_uri http_uri; + struct pl pl; struct http_req *req; uint16_t defport; bool secure; @@ -584,18 +655,18 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, if (!cli || !met || !uri) return EINVAL; - if (re_regex(uri, strlen(uri), "[a-z]+://[^:/]+[:]*[0-9]*[^]+", - &scheme, &host, NULL, &port, &path) || scheme.p != uri) + pl_set_str(&pl, uri); + if (http_uri_decode(&http_uri, &pl)) return EINVAL; - if (!pl_strcasecmp(&scheme, "http") || - !pl_strcasecmp(&scheme, "ws")) { + if (!pl_strcasecmp(&http_uri.scheme, "http") || + !pl_strcasecmp(&http_uri.scheme, "ws")) { secure = false; defport = 80; } #ifdef USE_TLS - else if (!pl_strcasecmp(&scheme, "https") || - !pl_strcasecmp(&scheme, "wss")) { + else if (!pl_strcasecmp(&http_uri.scheme, "https") || + !pl_strcasecmp(&http_uri.scheme, "wss")) { secure = true; defport = 443; } @@ -611,12 +682,13 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, req->cli = cli; req->secure = secure; - req->port = pl_isset(&port) ? pl_u32(&port) : defport; + req->port = pl_isset(&http_uri.port) ? pl_u32(&http_uri.port) : + defport; req->resph = resph; req->datah = datah; req->arg = arg; - err = pl_strdup(&req->host, &host); + err = pl_strdup(&req->host, &http_uri.host); if (err) goto out; @@ -629,7 +701,7 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, err = mbuf_printf(req->mbreq, "%s %r HTTP/1.1\r\n" "Host: %r\r\n", - met, &path, &host); + met, &http_uri.path, &http_uri.host); if (fmt) { va_start(ap, fmt); err |= mbuf_vprintf(req->mbreq, fmt, ap); @@ -643,6 +715,18 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, req->mbreq->pos = 0; +#ifdef USE_TLS + if (cli->cert && cli->key) { + err = tls_set_certificate_pem(cli->tls, + cli->cert, strlen(cli->cert), + cli->key, strlen(cli->key)); + } + else if (cli->cert) { + err = tls_set_certificate(cli->tls, + cli->cert, strlen(cli->cert)); + } +#endif + if (!sa_set_str(&req->srvv[0], req->host, req->port)) { req->srvc = 1; @@ -687,9 +771,9 @@ void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh) /** - * Allocate an HTTP client instance + * Allocate an HTTP Client instance * - * @param clip Pointer to allocated HTTP client + * @param clip Pointer to allocated HTTP Client * @param dnsc DNS Client * * @return 0 if success, otherwise errorcode @@ -738,7 +822,7 @@ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc) /** * Add trusted CA certificates * - * @param cli HTTP client + * @param cli HTTP Client * @param capath Path to CA certificates * * @return 0 if success, otherwise errorcode @@ -752,10 +836,108 @@ int http_client_add_ca(struct http_cli *cli, const char *tls_ca) } +/** + * Add trusted CA certificates given as string + * + * @param cli HTTP Client + * @param capem The trusted CA as 0-terminated string given in PEM format + * + * @return 0 if success, otherwise errorcode + */ +int http_client_add_capem(struct http_cli *cli, const char *capem) +{ + if (!cli || !capem) + return EINVAL; + + return tls_add_capem(cli->tls, capem); +} + + +/** + * Set client certificate + * @param cli HTTP Client + * @param path File path to client certificate + * + * @return 0 for success, error code otherwise. + */ +int http_client_set_cert(struct http_cli *cli, const char *path) +{ + int err = 0; + + if (!cli || !path) + return EINVAL; + + cli->cert = mem_deref(cli->cert); + err = read_file(&cli->cert, path); + if (err) { + cli->cert = mem_deref(cli->cert); + return err; + } + + return 0; +} + + +/** + * Set client certificate in PEM format + * @param cli HTTP Client + * @param pem Client certificate in PEM format + * + * @return 0 for success, error code otherwise. + */ +/* ------------------------------------------------------------------------- */ +int http_client_set_certpem(struct http_cli *cli, const char *pem) +{ + if (!cli || !str_isset(pem)) + return EINVAL; + + cli->cert = mem_deref(cli->cert); + cli->cert = mem_zalloc(strlen(pem) + 1, NULL); + if (!cli->cert) + return ENOMEM; + + strcpy(cli->cert, pem); + return 0; +} + + +int http_client_set_key(struct http_cli *cli, const char *path) +{ + int err = 0; + + if (!cli || !path) + return EINVAL; + + cli->key = mem_deref(cli->key); + err = read_file(&cli->key, path); + if (err) { + cli->key = mem_deref(cli->key); + return err; + } + + return 0; +} + + +int http_client_set_keypem(struct http_cli *cli, const char *pem) +{ + if (!cli || !str_isset(pem)) + return EINVAL; + + cli->key = mem_deref(cli->key); + cli->key = mem_zalloc(strlen(pem) + 1, NULL); + if (!cli->key) + return ENOMEM; + + strcpy(cli->key, pem); + return 0; +} + + /** * Set verify host name * - * @param cli HTTP client + * @param cli HTTP Client * @param hostname String for alternative name validation. * * @return 0 if success, otherwise errorcode @@ -763,10 +945,14 @@ int http_client_add_ca(struct http_cli *cli, const char *tls_ca) int http_client_set_tls_hostname(struct http_cli *cli, const struct pl *hostname) { - if (!cli || !hostname) + if (!cli) return EINVAL; - return tls_set_hostname(cli->tls_hostname, hostname); + cli->tlshn = mem_deref(cli->tlshn); + if (!hostname) + return 0; + + return pl_strdup(&cli->tlshn, hostname); } #endif @@ -778,7 +964,7 @@ int http_client_set_tls_hostname(struct http_cli *cli, * @param addr Bind to local v4 address * */ -void http_client_set_laddr(struct http_cli *cli, struct sa *addr) +void http_client_set_laddr(struct http_cli *cli, const struct sa *addr) { if (cli && addr) sa_cpy(&cli->laddr, addr); @@ -792,10 +978,34 @@ void http_client_set_laddr(struct http_cli *cli, struct sa *addr) * @param addr Bind to local v6 address * */ -void http_client_set_laddr6(struct http_cli *cli, struct sa *addr) +void http_client_set_laddr6(struct http_cli *cli, const struct sa *addr) { #ifdef HAVE_INET6 if (cli && addr) sa_cpy(&cli->laddr6, addr); #endif } + + +/** + * Set timeout for the HTTP Client in milli seconds. + * + * @param cli HTTP Client + * @param ms Timeout in milli seconds + * + * @return 0 if success, otherwise errorcode + */ +int http_client_set_timeout(struct http_cli *cli, uint32_t ms) +{ + struct dnsc_conf conf; + if (!cli) + return EINVAL; + + /* TODO: TCP timeout connect/send/idle */ + conf.query_hash_size = QUERY_HASH_SIZE; + conf.tcp_hash_size = TCP_HASH_SIZE; + conf.conn_timeout = ms; + conf.idle_timeout = ms; + + return dnsc_conf_set(cli->dnsc, &conf); +} diff --git a/src/http/mod.mk b/src/http/mod.mk index 4394f5805..8c54e6b55 100644 --- a/src/http/mod.mk +++ b/src/http/mod.mk @@ -7,5 +7,6 @@ SRCS += http/auth.c SRCS += http/chunk.c SRCS += http/client.c +SRCS += http/request.c SRCS += http/msg.c SRCS += http/server.c diff --git a/src/http/request.c b/src/http/request.c new file mode 100644 index 000000000..17f45c990 --- /dev/null +++ b/src/http/request.c @@ -0,0 +1,579 @@ +/** + * @file http/request.c HTTP request connection + * + * Supports: + * - GET, POST and PUT requests + * - basic, digest and bearer authentication + * - TLS + * + * Copyright (C) 2020 Commend.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "http.h" + +#define DEBUG_MODULE "reqconn" +#define DEBUG_LEVEL 5 +#include + +#ifndef VERSION +#define VERSION "???" +#endif + + +enum { + MAX_RETRIES = 3, +}; + +struct http_reqconn { + struct le le; + + struct http_cli *client; /**< HTTP client */ + + struct sa peer; /**< Peer address */ + struct http_req *req; /**< Current HTTP request */ + struct tcp_conn *tc; /**< TCP connection */ + struct tls_conn *sc; /**< TLS connection */ + + char *uri; /**< Request URI */ + char *met; /**< Request Method */ + char *path; /**< Request path/resource */ + char *ctype; /**< Content-type */ + uint32_t timeout; /**< Timeout for DNS and HTTP */ + char *user; /**< Auth user */ + char *pass; /**< Auth password */ + char *body; /**< HTTP body for POST/PUT request */ + char *bearer; /**< Auth bearer */ + struct mbuf *custhdr; /**< Custom HTTP headers */ + + int retries; /**< Auth retry counter */ + http_resp_h *resph; /**< HTTP response handler */ + http_data_h *datah; /**< HTTP data handler for downloads */ + void *arg; /**< User data pointer for resph and datah */ + +#ifdef USE_TLS + char *tlshn; /**< TLS host name */ +#endif +}; + + +static void destructor(void *arg) +{ + struct http_reqconn *conn = arg; + + mem_deref(conn->req); + mem_deref(conn->tc); + mem_deref(conn->sc); + mem_deref(conn->client); + + mem_deref(conn->uri); + mem_deref(conn->met); + mem_deref(conn->path); + mem_deref(conn->ctype); + mem_deref(conn->user); + mem_deref(conn->pass); + mem_deref(conn->body); + mem_deref(conn->bearer); + mem_deref(conn->custhdr); +#ifdef USE_TLS + mem_deref(conn->tlshn); +#endif +} + + +static int make_digest_mb(struct mbuf *mb, + struct httpauth_digest_chall *digest, + struct http_reqconn *conn) +{ + struct httpauth_digest_resp *resp = NULL; + int err; + + err = httpauth_digest_make_response(&resp, digest, conn->path, + conn->met, conn->user, conn->pass, conn->body); + if (err) + return err; + + err = httpauth_digest_response_encode(resp, mb); + mem_deref(resp); + return err; +} + + +static int make_bearer_mb(struct mbuf *mb, const struct http_reqconn *conn) +{ + int err; + const char auth[] = "Authorization: Bearer "; + + if (!conn || !mb) + return EINVAL; + + if (!str_isset(conn->bearer) || !mb) + return EINVAL; + + err = mbuf_resize(mb, strlen(conn->bearer) + sizeof(auth)); + if (err) + return err; + + err = mbuf_write_str(mb, auth); + err |= mbuf_write_str(mb, conn->bearer); + mbuf_set_pos(mb, 0); + return err; +} + + +static int make_basic_mb(struct mbuf *mb, struct http_reqconn *conn) +{ + int err; + struct httpauth_basic *basic; + + if (!conn || !mb) + return EINVAL; + + basic = httpauth_basic_alloc(); + if (!basic) + return ENOMEM; + + err = httpauth_basic_make_response(basic, conn->user, conn->pass); + if (err) + goto out; + + err = httpauth_basic_encode(basic, mb); + +out: + mem_deref(basic); + return err; +} + + +static int send_req(struct http_reqconn *conn, const struct pl *auth); + + +static void resp_handler(int err, const struct http_msg *msg, void *arg) +{ + struct http_reqconn *conn = arg; + const struct http_hdr *hdr; + struct httpauth_digest_chall digest; + struct httpauth_basic *basic = NULL; + struct pl auth; + struct mbuf *abuf = NULL; + + if (!conn) + return; + + if (!msg) { + DEBUG_INFO("no http_msg (%m)\n", err); + goto disconnect; + } + else { + DEBUG_INFO("scode=%u (%m)\n", msg->scode, err); + } + + if (err || (msg->scode != 401 && msg->scode != 403)) + goto disconnect; + + hdr = http_msg_hdr(msg, HTTP_HDR_WWW_AUTHENTICATE); + if (!hdr) + goto disconnect; + + conn->retries++; + if (conn->retries > MAX_RETRIES) { + err = EAUTH; + DEBUG_INFO("not authorized\n"); + goto disconnect; + } + + if (httpauth_digest_challenge_decode(&digest, &hdr->val)) { + /* It's not digest. Now try basic. */ + basic = httpauth_basic_alloc(); + if (!basic) { + err = ENOMEM; + goto disconnect; + } + + if (httpauth_basic_decode(basic, &hdr->val)) { + err = EBADMSG; + goto disconnect; + } + } + + abuf = mbuf_alloc(1); + if (!abuf) { + err = ENOMEM; + goto disconnect; + } + + if (pl_isset(&digest.nonce)) + err = make_digest_mb(abuf, &digest, conn); + else if (pl_isset(&basic->realm)) + err = make_basic_mb(abuf, conn); + else + err = EBADMSG; + + if (err) { + DEBUG_WARNING("Authentication failed (%m)\n", err); + goto disconnect; + } + + pl_set_mbuf(&auth, abuf); + err = send_req(conn, &auth); + if (err) + goto disconnect; + + mem_deref(abuf); + mem_deref(basic); + return; + + disconnect: + if (conn && conn->resph) + conn->resph(err, msg, conn->arg); + + conn->tc = mem_deref(conn->tc); + conn->sc = mem_deref(conn->sc); + mem_deref(abuf); + mem_deref(basic); +} + + +static int data_handler(const uint8_t *buf, size_t size, + const struct http_msg *msg, void *arg) +{ + struct http_reqconn *conn = arg; + + if (!conn) + return EINVAL; + + if (!conn->datah) + return 0; + + return conn->datah(buf, size, msg, conn->arg); +} + + +static void conn_handler(struct tcp_conn *tc, struct tls_conn *sc, void *arg) +{ + struct http_reqconn *conn = arg; + + conn->tc = mem_ref(tc); + conn->sc = mem_ref(sc); +} + + +static int send_req(struct http_reqconn *conn, const struct pl *auth) +{ + int err; + struct mbuf *ctbuf = NULL; + struct mbuf *clbuf = NULL; + struct pl ct = PL_INIT; + struct pl cl = PL_INIT; + struct pl custh = PL_INIT; + size_t len; + + if (!conn) + return EINVAL; + + if (conn->body) { + len = strlen(conn->body); + clbuf = mbuf_alloc(22); + mbuf_printf(clbuf, "Content-Length: %lu\r\n", len); + mbuf_set_pos(clbuf, 0); + pl_set_mbuf(&cl, clbuf); + } + + if (conn->ctype) { + ctbuf = mbuf_alloc(17 + strlen(conn->ctype)); + mbuf_printf(ctbuf, "Content-Type: %s\r\n", conn->ctype); + mbuf_set_pos(ctbuf, 0); + pl_set_mbuf(&ct, ctbuf); + } + + DEBUG_INFO("send %s uri=%s path=%s len=%lu %s auth.\n", + conn->met, conn->uri, conn->path, + conn->body ? strlen(conn->body) : 0, + auth ? "with" : "without"); + + if (auth) + DEBUG_INFO("auth=|%r|\n", auth); + +#if (DEBUG_LEVEL >= 7) + if (conn->body) { + DEBUG_PRINTF("postdata:\n%r\n", &conn->body); + } +#endif + + if (conn->custhdr) + pl_set_mbuf(&custh, conn->custhdr); + + conn->tc = mem_deref(conn->tc); + conn->sc = mem_deref(conn->sc); + err = http_request(&conn->req, conn->client, + conn->met, conn->uri, + resp_handler, conn->datah ? data_handler : NULL, conn, + "%r%s" + "User-Agent: re " VERSION "\r\n" + "%r" + "%r" + "%r" + "\r\n" + "%s", + auth, auth ? "\r\n" : "", + &ct, + &custh, + &cl, + conn->body ? conn->body : ""); + + + mem_deref(clbuf); + mem_deref(ctbuf); + if (err) { + DEBUG_WARNING("Could not send %s request. (%m)\n", conn->met); + return err; + } + + http_req_set_conn_handler(conn->req, conn_handler); + return 0; +} + + +static int send_bearer(struct http_reqconn *conn) +{ + struct pl auth; + int err = 0; + struct mbuf *mb = mbuf_alloc(1); + + if (!mb) { + err = ENOMEM; + goto out; + } + + err = make_bearer_mb(mb, conn); + if (err) + goto out; + + pl_set_mbuf(&auth, mb); + err = send_req(conn, &auth); + +out: + mem_deref(mb); + return err; +} + + +int http_reqconn_alloc(struct http_reqconn **pconn, + struct http_cli *client, + http_resp_h *resph, http_data_h *datah, void* arg) +{ + struct http_reqconn *conn = NULL; + int err; + struct pl pl = PL("GET"); + + if (!pconn || !client) + return EINVAL; + + conn = mem_zalloc(sizeof(*conn), destructor); + if (!conn) + return ENOMEM; + + conn->client = mem_ref(client); + conn->resph = resph; + conn->datah = datah; + conn->arg = arg; + + err = http_reqconn_set_method(conn, &pl); + if (err) + conn = mem_deref(conn); + + *pconn = conn; + return err; +} + + +int http_reqconn_set_auth(struct http_reqconn *conn, const struct pl *user, + const struct pl *pass) +{ + int err = 0; + + if (!conn) + return EINVAL; + + conn->user = mem_deref(conn->user); + conn->pass = mem_deref(conn->pass); + if (pl_isset(user)) + err |= pl_strdup(&conn->user, user); + + if (pl_isset(pass)) + err |= pl_strdup(&conn->pass, pass); + + return err; +} + + +int http_reqconn_set_bearer(struct http_reqconn *conn, const struct pl *bearer) +{ + if (!conn) + return EINVAL; + + conn->bearer = mem_deref(conn->bearer); + if (!pl_isset(bearer)) + return 0; + + return pl_strdup(&conn->bearer, bearer); +} + + +int http_reqconn_set_method(struct http_reqconn *conn, const struct pl *met) +{ + if (!conn) + return EINVAL; + + conn->met = mem_deref(conn->met); + return pl_strdup(&conn->met, met); +} + + +int http_reqconn_set_body(struct http_reqconn *conn, const struct pl *body) +{ + if (!conn) + return EINVAL; + + conn->body = mem_deref(conn->body); + if (!pl_isset(body)) + return 0; + + return pl_strdup(&conn->body, body); +} + + +int http_reqconn_set_ctype(struct http_reqconn *conn, const struct pl *ctype) +{ + if (!conn) + return EINVAL; + + conn->ctype = mem_deref(conn->ctype); + if (!pl_isset(ctype)) + return 0; + + return pl_strdup(&conn->ctype, ctype); +} + + +int http_reqconn_add_header(struct http_reqconn *conn, const struct pl *header) +{ + int err; + if (!conn) + return EINVAL; + + if (!pl_isset(header)) + return 0; + + if (!conn->custhdr) + conn->custhdr = mbuf_alloc(8); + + if (!conn->custhdr) + return ENOMEM; + + err = mbuf_write_pl(conn->custhdr, header); + err |= mbuf_write_str(conn->custhdr, "\r\n"); + if (err) + conn->custhdr = mem_deref(conn->custhdr); + + return err; +} + + +int http_reqconn_clr_header(struct http_reqconn *conn) +{ + if (!conn) + return EINVAL; + + conn->custhdr = mem_deref(conn->custhdr); + return 0; +} + + +#ifdef USE_TLS +int http_reqconn_set_tls_hostname(struct http_reqconn *conn, + const struct pl *hostname) +{ + if (!conn) + return EINVAL; + + conn->tlshn = mem_deref(conn->tlshn); + if (!pl_isset(hostname)) + return 0; + + return pl_strdup(&conn->tlshn, hostname); +} +#endif + + +int http_reqconn_send(struct http_reqconn *conn, const struct pl *uri) +{ + int err; + struct http_uri hu; + char *host = NULL; +#ifdef USE_TLS + struct pl tlshn; + struct sa sa; +#endif + + if (!pl_isset(uri)) + return EINVAL; + + err = http_uri_decode(&hu, uri); + if (err) { + DEBUG_WARNING("http uri %r decode error (%m)", uri, err); + return EINVAL; + } + + conn->uri = mem_deref(conn->uri); + conn->path = mem_deref(conn->path); + err |= pl_strdup(&conn->uri, uri); + err |= pl_strdup(&conn->path, &hu.path); + err |= pl_strdup(&host, &hu.host); + if (err) + return err; + +#ifdef USE_TLS + if (conn->tlshn) { + pl_set_str(&tlshn, conn->tlshn); + err = http_client_set_tls_hostname(conn->client, &tlshn); + } + else if (sa_set_str(&sa, host, 0) && ( + !pl_strcasecmp(&hu.scheme, "https") || + !pl_strcasecmp(&hu.scheme, "wss"))) + err = http_client_set_tls_hostname(conn->client, &hu.host); + + if (err) { + DEBUG_WARNING("Could not set TLS hostname.\n"); + mem_deref(host); + return err; + } +#endif + + mem_deref(host); + if (conn->custhdr) + mbuf_set_pos(conn->custhdr, 0); + + conn->retries = 0; + if (conn->bearer) + err = send_bearer(conn); + else + err = send_req(conn, NULL); + + return err; +} diff --git a/src/httpauth/basic.c b/src/httpauth/basic.c index e7cc34fe9..39fcb92c4 100644 --- a/src/httpauth/basic.c +++ b/src/httpauth/basic.c @@ -3,11 +3,123 @@ * * Copyright (C) 2010 Creytiv.com */ +#include #include #include -#include +#include +#include #include #include -/* todo */ +#define DEBUG_MODULE "httpauth_basic" +#define DEBUG_LEVEL 5 +#include + + +static void httpauth_basic_destr(void *arg) +{ + struct httpauth_basic *basic = arg; + + mem_deref(basic->mb); +} + + +struct httpauth_basic *httpauth_basic_alloc(void) +{ + struct httpauth_basic *basic = mem_zalloc(sizeof(*basic), + httpauth_basic_destr); + + if (!basic) + DEBUG_WARNING("could not allocate httpauth_basic\n"); + + return basic; +} + + +/** + * Decode a Basic response + * + * @param basic Basic response object + * @param hval Header value to decode from + * + * @return 0 if successfully decoded, otherwise errorcode + */ +int httpauth_basic_decode(struct httpauth_basic *basic, + const struct pl *hval) +{ + if (!basic || !hval) + return EINVAL; + + if (re_regex(hval->p, hval->l, + "[ \t\r\n]*Basic[ \t\r\n]+realm[ \t\r\n]*=[ \t\r\n]*" + "[~ \t\r\n,]*", + NULL, NULL, NULL, NULL, &basic->realm) || + !pl_isset(&basic->realm)) + return EBADMSG; + + return 0; +} + + +int httpauth_basic_make_response(struct httpauth_basic *basic, + const char *user, const char *pwd) +{ + uint8_t *in; + char *out; + size_t si, so; + size_t poso; + int err; + + if (!basic || !user || !pwd) + return EINVAL; + + si = strlen(user) + strlen(pwd) + 1; + so = 4 * (si + 2) / 3; + basic->mb = mbuf_alloc(si + so + 1); + if (!basic->mb) + return ENOMEM; + + err = mbuf_printf(basic->mb, "%s:%s", user, pwd); + poso = basic->mb->pos; + + err |= mbuf_fill(basic->mb, 0, so + 1); + if (err) + goto fault; + + mbuf_set_pos(basic->mb, 0); + in = mbuf_buf(basic->mb); + mbuf_set_pos(basic->mb, poso); + out = (char*) mbuf_buf(basic->mb); + err = base64_encode(in, si, out, &so); + if (err) + goto fault; + + pl_set_str(&basic->auth, out); + + return 0; + +fault: + mem_deref(basic->mb); + return err; +} + +int httpauth_basic_encode(const struct httpauth_basic *basic, struct mbuf *mb) +{ + int err; + + if (!basic || !mb || !pl_isset(&basic->auth)) + return EINVAL; + + err = mbuf_resize(mb, basic->auth.l + 21); + if (err) + return err; + + err = mbuf_write_str(mb, "Authorization: Basic "); + err |= mbuf_write_pl(mb, &basic->auth); + if (err) + return err; + + mbuf_set_pos(mb, 0); + return 0; +} diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index 584511e64..f5157f73b 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,14 @@ static int digest_decode(const struct pl *hval, digest_decode_h *dech, } +static void response_destructor(void *data) +{ + struct httpauth_digest_resp *resp = data; + + mem_deref(resp->mb); +} + + /** * Decode a Digest challenge * @@ -212,3 +221,190 @@ int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp, return 0; } + + +static uint32_t nc = 1; + +int httpauth_digest_make_response(struct httpauth_digest_resp **presp, + const struct httpauth_digest_chall *chall, + const char *path, const char *method, const char *user, + const char *pwd, const char *body) +{ + struct httpauth_digest_resp *resp; + size_t p1, p2; + uint8_t ha1[MD5_SIZE], ha2[MD5_SIZE], response[MD5_SIZE]; + uint32_t cnonce; + struct mbuf *mb = NULL; + int err; + + if (!presp || !chall || !method || !user || !path || !pwd) + return EINVAL; + + resp = mem_zalloc(sizeof(*resp), response_destructor); + if (!resp) { + err = ENOMEM; + goto out; + } + + mb = mbuf_alloc(256); + if (!mb) { + err = ENOMEM; + goto out; + } + + resp->realm = chall->realm; + resp->nonce = chall->nonce; + pl_set_str(&resp->username, user); + pl_set_str(&resp->uri, path); + resp->qop = chall->qop; + + err = mbuf_printf(mb, "%x", nc); + err |= mbuf_write_u8(mb, 0); + if (err) + goto out; + + /* Client nonce should change, so we use random value. */ + cnonce = rand_u32(); + p1 = mb->pos; + err = mbuf_printf(mb, "%x", cnonce); + err |= mbuf_write_u8(mb, 0); + if (err) + goto out; + + /* compute response */ + /* HA1 = MD5(username:realm:password) */ + p2 = mb->pos; + err = mbuf_printf(mb, "%r:%r:%s", &resp->username, &resp->realm, + pwd); + if (err) + goto out; + + mbuf_set_pos(mb, p2); + md5(mbuf_buf(mb), mbuf_get_left(mb), ha1); + mbuf_skip_to_end(mb); + if (0 == pl_strcmp(&chall->algorithm, "MD5-sess")) { + /* HA1 = MD5(HA1:nonce:cnonce) */ + p2 = mb->pos; + err = mbuf_printf(mb, "%w:%r:%x", ha1, sizeof(ha1), + &resp->nonce, cnonce); + if (err) + goto out; + + mbuf_set_pos(mb, p2); + md5(mbuf_buf(mb), mbuf_get_left(mb), ha1); + mbuf_skip_to_end(mb); + } + + /* HA2 */ + p2 = mb->pos; + if (0 == pl_strcmp(&resp->qop, "auth-int") && str_isset(body)) { + /* HA2 = MD5(method:digestURI:MD5(entityBody)) */ + err = mbuf_printf(mb, "%s", body); + if (err) + goto out; + + mbuf_set_pos(mb, p2); + md5(mbuf_buf(mb), mbuf_get_left(mb), ha2); + mbuf_skip_to_end(mb); + p2 = mb->pos; + err = mbuf_printf(mb, "%s:%r:%w", method, &resp->uri, + ha2, sizeof(ha2)); + } + else { + /* HA2 = MD5(method:digestURI) */ + err = mbuf_printf(mb, "%s:%r", method, &resp->uri); + + } + + if (err) + goto out; + + mbuf_set_pos(mb, p2); + md5(mbuf_buf(mb), mbuf_get_left(mb), ha2); + mbuf_skip_to_end(mb); + + /* repsonse */ + p2 = mb->pos; + if (0 == pl_strcmp(&resp->qop, "auth-int") || + 0 == pl_strcmp(&resp->qop, "auth")) { + /* response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) */ + err = mbuf_printf(mb, "%w:%r:%x:%x:%r:%w", + ha1, sizeof(ha1), &resp->nonce, nc, cnonce, + &resp->qop, ha2, sizeof(ha2)); + } + else { + /* response = MD5(HA1:nonce:HA2) */ + err = mbuf_printf(mb, "%w:%r:%w", ha1, sizeof(ha1), + &resp->nonce, ha2, sizeof(ha2)); + } + + if (err) + goto out; + + mbuf_set_pos(mb, p2); + md5(mbuf_buf(mb), mbuf_get_left(mb), response); + mbuf_skip_to_end(mb); + + p2 = mb->pos; + err = mbuf_printf(mb, "%w", response, sizeof(response)); + err |= mbuf_write_u8(mb, 0); + if (err) + goto out; + + ++nc; + mbuf_set_pos(mb, 0); + pl_set_str(&resp->nc, (const char*) mbuf_buf(mb)); + mbuf_set_pos(mb, p1); + pl_set_str(&resp->cnonce, (const char*) mbuf_buf(mb)); + mbuf_set_pos(mb, p2); + pl_set_str(&resp->response, (const char*) mbuf_buf(mb)); +out: + resp->mb = mb; + if (err) + mem_deref(resp); + else + *presp = resp; + + return err; +} + + +int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, + struct mbuf *mb) +{ + int err; + size_t s; + + if (!resp || !mb) + return EINVAL; + + /* lenth of string literals */ + s = 93; + if (pl_isset(&resp->qop)) + s += 26; + + /* length of values */ + s += resp->username.l + resp->realm.l + resp->nonce.l + resp->uri.l; + s += resp->response.l; + if (pl_isset(&resp->qop)) + s += resp->qop.l + resp->nc.l + resp->cnonce.l; + + if (s > mb->size) + mbuf_resize(mb, s); + + err = mbuf_write_str(mb, "Authorization: "); + err |= mbuf_printf(mb, "Digest username=\"%r\"", &resp->username); + err |= mbuf_printf(mb, ", realm=\"%r\"", &resp->realm); + err |= mbuf_printf(mb, ", nonce=\"%r\"", &resp->nonce); + err |= mbuf_printf(mb, ", uri=\"%r\"", &resp->uri); + err |= mbuf_printf(mb, ", response=\"%r\"", &resp->response); + + if (pl_isset(&resp->qop)) { + err |= mbuf_printf(mb, ", qop=%r", &resp->qop); + err |= mbuf_printf(mb, ", nc=%r", &resp->nc); + err |= mbuf_printf(mb, ", cnonce=\"%r\"", &resp->cnonce); + } + + mbuf_set_pos(mb, 0); + return err; +} diff --git a/src/httpauth/mod.mk b/src/httpauth/mod.mk index da0c931f0..9d25b4aa2 100644 --- a/src/httpauth/mod.mk +++ b/src/httpauth/mod.mk @@ -4,5 +4,5 @@ # Copyright (C) 2010 Creytiv.com # -#SRCS += httpauth/basic.c +SRCS += httpauth/basic.c SRCS += httpauth/digest.c diff --git a/src/tls/openssl/tls.c b/src/tls/openssl/tls.c index ee7db290d..f3ca1be71 100644 --- a/src/tls/openssl/tls.c +++ b/src/tls/openssl/tls.c @@ -223,6 +223,54 @@ int tls_add_ca(struct tls *tls, const char *cafile) } +/** + * Add trusted CA certificates given as string. + * + * @param tls TLS Context + * @param capem The trusted CA as null-terminated string given in PEM format. + * + * @return 0 if success, otherwise errorcode + */ +int tls_add_capem(struct tls *tls, const char *capem) +{ + X509_STORE *store; + X509 *x509; + BIO *bio; + int ok; + int err = 0; + + if (!tls || !capem || !tls->ctx) + return EINVAL; + + store = SSL_CTX_get_cert_store(tls->ctx); + if (!store) + return EINVAL; + + bio = BIO_new_mem_buf((char *)capem, strlen(capem)); + if (!bio) + return EINVAL; + + x509 = PEM_read_bio_X509(bio, NULL, 0, NULL); + if (!x509) { + err = EINVAL; + DEBUG_WARNING("Could not read certificate capem\n"); + goto out; + } + + ok = X509_STORE_add_cert(store, x509); + if (!ok) { + err = EINVAL; + DEBUG_WARNING("Could not add certificate capem\n"); + } + +out: + X509_free(x509); + BIO_free(bio); + + return err; +} + + /** * Set SSL verification of the certificate purpose * @@ -285,28 +333,6 @@ int tls_peer_set_verify_host(struct tls_conn *tc, const char *hostname) } -/** - * Convert string hostname to pl hostname - * - * @param tls_hostname Certificate hostname as string - * @param hostname Certificate hostname as pl - * - * @return int 0 if success, errorcode otherwise - */ -int tls_set_hostname(char *tls_hostname, const struct pl *hostname) -{ - if (!tls_hostname || !hostname) - return EINVAL; - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - DEBUG_WARNING("verify hostname needs openssl version 1.1.0\n"); - return ENOSYS; -#endif - - return pl_strdup(&tls_hostname, hostname); -} - - /** * Generate and set selfsigned certificate on TLS context *