| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "config.h" |
| |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #ifdef HAVE_LIBPROXY |
| #include <proxy.h> |
| #endif |
| |
| #include "ne_session.h" |
| #include "ne_alloc.h" |
| #include "ne_utils.h" |
| #include "ne_internal.h" |
| #include "ne_string.h" |
| #include "ne_dates.h" |
| |
| #include "ne_private.h" |
| |
| |
| static void destroy_hooks(struct hook *hooks) |
| { |
| struct hook *nexthk; |
| |
| while (hooks) { |
| nexthk = hooks->next; |
| ne_free(hooks); |
| hooks = nexthk; |
| } |
| } |
| |
| static void free_hostinfo(struct host_info *hi) |
| { |
| if (hi->hostname) ne_free(hi->hostname); |
| if (hi->hostport) ne_free(hi->hostport); |
| if (hi->address) ne_addr_destroy(hi->address); |
| } |
| |
| |
| static void free_proxies(ne_session *sess) |
| { |
| struct host_info *hi, *nexthi; |
| |
| for (hi = sess->proxies; hi; hi = nexthi) { |
| nexthi = hi->next; |
| free_hostinfo(hi); |
| ne_free(hi); |
| } |
| |
| sess->proxies = NULL; |
| sess->any_proxy_http = 0; |
| } |
| |
| void ne_session_destroy(ne_session *sess) |
| { |
| struct hook *hk; |
| |
| NE_DEBUG(NE_DBG_HTTP, "sess: Destroying session.\n"); |
| |
| |
| for (hk = sess->destroy_sess_hooks; hk != NULL; hk = hk->next) { |
| ne_destroy_sess_fn fn = (ne_destroy_sess_fn)hk->fn; |
| fn(hk->userdata); |
| } |
| |
| |
| |
| if (sess->connected) { |
| ne_close_connection(sess); |
| } |
| |
| destroy_hooks(sess->create_req_hooks); |
| destroy_hooks(sess->pre_send_hooks); |
| destroy_hooks(sess->post_headers_hooks); |
| destroy_hooks(sess->post_send_hooks); |
| destroy_hooks(sess->destroy_req_hooks); |
| destroy_hooks(sess->destroy_sess_hooks); |
| destroy_hooks(sess->close_conn_hooks); |
| destroy_hooks(sess->private); |
| |
| ne_free(sess->scheme); |
| |
| free_hostinfo(&sess->server); |
| free_proxies(sess); |
| |
| if (sess->user_agent) ne_free(sess->user_agent); |
| if (sess->socks_user) ne_free(sess->socks_user); |
| if (sess->socks_password) ne_free(sess->socks_password); |
| |
| #ifdef NE_HAVE_SSL |
| if (sess->ssl_context) |
| ne_ssl_context_destroy(sess->ssl_context); |
| |
| if (sess->server_cert) |
| ne_ssl_cert_free(sess->server_cert); |
| |
| if (sess->client_cert) |
| ne_ssl_clicert_free(sess->client_cert); |
| #endif |
| |
| ne_free(sess); |
| } |
| |
| int ne_version_pre_http11(ne_session *s) |
| { |
| return !s->is_http11; |
| } |
| |
| |
| static void set_hostport(struct host_info *host, unsigned int defaultport) |
| { |
| size_t len = strlen(host->hostname); |
| host->hostport = ne_malloc(len + 10); |
| strcpy(host->hostport, host->hostname); |
| if (host->port != defaultport) |
| ne_snprintf(host->hostport + len, 9, ":%u", host->port); |
| } |
| |
| |
| |
| static void set_hostinfo(struct host_info *hi, enum proxy_type type, |
| const char *hostname, unsigned int port) |
| { |
| hi->hostname = ne_strdup(hostname); |
| hi->port = port; |
| hi->proxy = type; |
| } |
| |
| ne_session *ne_session_create(const char *scheme, |
| const char *hostname, unsigned int port) |
| { |
| ne_session *sess = ne_calloc(sizeof *sess); |
| |
| NE_DEBUG(NE_DBG_HTTP, "HTTP session to %s://%s:%d begins.\n", |
| scheme, hostname, port); |
| |
| strcpy(sess->error, "Unknown error."); |
| |
| |
| sess->use_ssl = !strcmp(scheme, "https"); |
| |
| |
| set_hostinfo(&sess->server, PROXY_NONE, hostname, port); |
| set_hostport(&sess->server, sess->use_ssl?443:80); |
| |
| #ifdef NE_HAVE_SSL |
| if (sess->use_ssl) { |
| ne_inet_addr *ia; |
| |
| sess->ssl_context = ne_ssl_context_create(0); |
| sess->flags[NE_SESSFLAG_SSLv2] = 1; |
| |
| |
| |
| ia = ne_iaddr_parse(hostname, ne_iaddr_ipv4); |
| if (ia == NULL) |
| ia = ne_iaddr_parse(hostname, ne_iaddr_ipv6); |
| |
| if (ia) { |
| ne_iaddr_free(ia); |
| } |
| else { |
| sess->flags[NE_SESSFLAG_TLS_SNI] = 1; |
| } |
| NE_DEBUG(NE_DBG_SSL, "ssl: SNI %s by default.\n", |
| sess->flags[NE_SESSFLAG_TLS_SNI] ? |
| "enabled" : "disabled"); |
| } |
| #endif |
| |
| sess->scheme = ne_strdup(scheme); |
| |
| |
| sess->flags[NE_SESSFLAG_PERSIST] = 1; |
| |
| return sess; |
| } |
| |
| void ne_session_proxy(ne_session *sess, const char *hostname, |
| unsigned int port) |
| { |
| free_proxies(sess); |
| |
| sess->proxies = ne_calloc(sizeof *sess->proxies); |
| |
| sess->any_proxy_http = 1; |
| |
| set_hostinfo(sess->proxies, PROXY_HTTP, hostname, port); |
| } |
| |
| void ne_session_socks_proxy(ne_session *sess, enum ne_sock_sversion vers, |
| const char *hostname, unsigned int port, |
| const char *username, const char *password) |
| { |
| free_proxies(sess); |
| |
| sess->proxies = ne_calloc(sizeof *sess->proxies); |
| |
| set_hostinfo(sess->proxies, PROXY_SOCKS, hostname, port); |
| |
| sess->socks_ver = vers; |
| |
| if (username) sess->socks_user = ne_strdup(username); |
| if (password) sess->socks_password = ne_strdup(password); |
| } |
| |
| void ne_session_system_proxy(ne_session *sess, unsigned int flags) |
| { |
| #ifdef HAVE_LIBPROXY |
| pxProxyFactory *pxf = px_proxy_factory_new(); |
| struct host_info *hi, **lasthi; |
| char *url, **proxies; |
| ne_uri uri; |
| unsigned n; |
| |
| free_proxies(sess); |
| |
| |
| memset(&uri, 0, sizeof uri); |
| ne_fill_server_uri(sess, &uri); |
| |
| uri.path = "/"; |
| url = ne_uri_unparse(&uri); |
| uri.path = NULL; |
| |
| |
| proxies = px_proxy_factory_get_proxies(pxf, url); |
| |
| for (n = 0, lasthi = &sess->proxies; proxies[n]; n++) { |
| enum proxy_type ptype; |
| |
| ne_uri_free(&uri); |
| |
| NE_DEBUG(NE_DBG_HTTP, "sess: libproxy #%u=%s\n", |
| n, proxies[n]); |
| |
| if (ne_uri_parse(proxies[n], &uri)) |
| continue; |
| |
| if (!uri.scheme) continue; |
| |
| if (ne_strcasecmp(uri.scheme, "http") == 0) |
| ptype = PROXY_HTTP; |
| else if (ne_strcasecmp(uri.scheme, "socks") == 0) |
| ptype = PROXY_SOCKS; |
| else if (ne_strcasecmp(uri.scheme, "direct") == 0) |
| ptype = PROXY_NONE; |
| else |
| continue; |
| |
| |
| if (ptype != PROXY_NONE && !(uri.host && uri.port)) |
| continue; |
| |
| |
| |
| |
| if (n == 0 && proxies[1] == NULL && ptype == PROXY_NONE) |
| break; |
| |
| NE_DEBUG(NE_DBG_HTTP, "sess: Got proxy %s://%s:%d\n", |
| uri.scheme, uri.host ? uri.host : "(none)", |
| uri.port); |
| |
| hi = *lasthi = ne_calloc(sizeof *hi); |
| |
| if (ptype == PROXY_NONE) { |
| |
| |
| set_hostinfo(hi, ptype, sess->server.hostname, |
| sess->server.port); |
| } |
| else { |
| |
| set_hostinfo(hi, ptype, uri.host, uri.port); |
| |
| if (ptype == PROXY_HTTP) |
| sess->any_proxy_http = 1; |
| else if (ptype == PROXY_SOCKS) |
| sess->socks_ver = NE_SOCK_SOCKSV5; |
| } |
| |
| lasthi = &hi->next; |
| } |
| |
| |
| for (n = 0; proxies[n]; n++) |
| free(proxies[n]); |
| free(proxies[n]); |
| |
| ne_free(url); |
| ne_uri_free(&uri); |
| px_proxy_factory_free(pxf); |
| #endif |
| } |
| |
| void ne_set_addrlist2(ne_session *sess, unsigned int port, |
| const ne_inet_addr **addrs, size_t n) |
| { |
| struct host_info *hi, **lasthi; |
| size_t i; |
| |
| free_proxies(sess); |
| |
| lasthi = &sess->proxies; |
| |
| for (i = 0; i < n; i++) { |
| *lasthi = hi = ne_calloc(sizeof *hi); |
| |
| hi->proxy = PROXY_NONE; |
| hi->network = addrs[i]; |
| hi->port = port; |
| |
| lasthi = &hi->next; |
| } |
| } |
| |
| void ne_set_addrlist(ne_session *sess, const ne_inet_addr **addrs, size_t n) |
| { |
| ne_set_addrlist2(sess, sess->server.port, addrs, n); |
| } |
| |
| void ne_set_localaddr(ne_session *sess, const ne_inet_addr *addr) |
| { |
| sess->local_addr = addr; |
| } |
| |
| void ne_set_error(ne_session *sess, const char *format, ...) |
| { |
| va_list params; |
| |
| va_start(params, format); |
| ne_vsnprintf(sess->error, sizeof sess->error, format, params); |
| va_end(params); |
| } |
| |
| void ne_set_session_flag(ne_session *sess, ne_session_flag flag, int value) |
| { |
| if (flag < NE_SESSFLAG_LAST) { |
| sess->flags[flag] = value; |
| #ifdef NE_HAVE_SSL |
| if (flag == NE_SESSFLAG_SSLv2 && sess->ssl_context) { |
| ne_ssl_context_set_flag(sess->ssl_context, NE_SSL_CTX_SSLv2, value); |
| sess->flags[flag] = ne_ssl_context_get_flag(sess->ssl_context, NE_SSL_CTX_SSLv2); |
| } |
| #endif |
| } |
| } |
| |
| int ne_get_session_flag(ne_session *sess, ne_session_flag flag) |
| { |
| if (flag < NE_SESSFLAG_LAST) { |
| return sess->flags[flag]; |
| } |
| return -1; |
| } |
| |
| static void progress_notifier(void *userdata, ne_session_status status, |
| const ne_session_status_info *info) |
| { |
| ne_session *sess = userdata; |
| |
| if (status == ne_status_sending || status == ne_status_recving) { |
| sess->progress_cb(sess->progress_ud, info->sr.progress, info->sr.total); |
| } |
| } |
| |
| void ne_set_progress(ne_session *sess, ne_progress progress, void *userdata) |
| { |
| if (progress) { |
| sess->progress_cb = progress; |
| sess->progress_ud = userdata; |
| ne_set_notifier(sess, progress_notifier, sess); |
| } |
| else { |
| ne_set_notifier(sess, NULL, NULL); |
| } |
| } |
| |
| void ne_set_notifier(ne_session *sess, |
| ne_notify_status status, void *userdata) |
| { |
| sess->notify_cb = status; |
| sess->notify_ud = userdata; |
| } |
| |
| void ne_set_read_timeout(ne_session *sess, int timeout) |
| { |
| sess->rdtimeout = timeout; |
| } |
| |
| void ne_set_connect_timeout(ne_session *sess, int timeout) |
| { |
| sess->cotimeout = timeout; |
| } |
| |
| #define UAHDR "User-Agent: " |
| #define AGENT " neon/" NEON_VERSION "\r\n" |
| |
| void ne_set_useragent(ne_session *sess, const char *token) |
| { |
| if (sess->user_agent) ne_free(sess->user_agent); |
| sess->user_agent = ne_malloc(strlen(UAHDR) + strlen(AGENT) + |
| strlen(token) + 1); |
| #ifdef HAVE_STPCPY |
| strcpy(stpcpy(stpcpy(sess->user_agent, UAHDR), token), AGENT); |
| #else |
| strcat(strcat(strcpy(sess->user_agent, UAHDR), token), AGENT); |
| #endif |
| } |
| |
| const char *ne_get_server_hostport(ne_session *sess) |
| { |
| return sess->server.hostport; |
| } |
| |
| const char *ne_get_scheme(ne_session *sess) |
| { |
| return sess->scheme; |
| } |
| |
| void ne_fill_server_uri(ne_session *sess, ne_uri *uri) |
| { |
| uri->host = ne_strdup(sess->server.hostname); |
| uri->port = sess->server.port; |
| uri->scheme = ne_strdup(sess->scheme); |
| } |
| |
| void ne_fill_proxy_uri(ne_session *sess, ne_uri *uri) |
| { |
| if (sess->proxies) { |
| struct host_info *hi = sess->nexthop ? sess->nexthop : sess->proxies; |
| |
| if (hi->proxy == PROXY_HTTP) { |
| uri->host = ne_strdup(hi->hostname); |
| uri->port = hi->port; |
| } |
| } |
| } |
| |
| const char *ne_get_error(ne_session *sess) |
| { |
| return sess->error; |
| } |
| |
| void ne_close_connection(ne_session *sess) |
| { |
| if (sess->connected) { |
| struct hook *hk; |
| |
| NE_DEBUG(NE_DBG_SOCKET, "sess: Closing connection.\n"); |
| |
| if (sess->notify_cb) { |
| sess->status.cd.hostname = sess->nexthop->hostname; |
| sess->notify_cb(sess->notify_ud, ne_status_disconnected, |
| &sess->status); |
| } |
| |
| |
| for (hk = sess->close_conn_hooks; hk != NULL; hk = hk->next) { |
| ne_close_conn_fn fn = (ne_close_conn_fn)hk->fn; |
| fn(hk->userdata); |
| } |
| |
| ne_sock_close(sess->socket); |
| sess->socket = NULL; |
| NE_DEBUG(NE_DBG_SOCKET, "sess: Connection closed.\n"); |
| } else { |
| NE_DEBUG(NE_DBG_SOCKET, "sess: Not closing closed connection.\n"); |
| } |
| sess->connected = 0; |
| } |
| |
| void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata) |
| { |
| sess->ssl_verify_fn = fn; |
| sess->ssl_verify_ud = userdata; |
| } |
| |
| void ne_ssl_provide_clicert(ne_session *sess, |
| ne_ssl_provide_fn fn, void *userdata) |
| { |
| sess->ssl_provide_fn = fn; |
| sess->ssl_provide_ud = userdata; |
| } |
| |
| void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert) |
| { |
| #ifdef NE_HAVE_SSL |
| if (sess->ssl_context) { |
| ne_ssl_context_trustcert(sess->ssl_context, cert); |
| } |
| #endif |
| } |
| |
| void ne_ssl_cert_validity(const ne_ssl_certificate *cert, char *from, char *until) |
| { |
| #ifdef NE_HAVE_SSL |
| time_t tf, tu; |
| char *date; |
| |
| ne_ssl_cert_validity_time(cert, &tf, &tu); |
| |
| if (from) { |
| if (tf != (time_t) -1) { |
| date = ne_rfc1123_date(tf); |
| ne_strnzcpy(from, date, NE_SSL_VDATELEN); |
| ne_free(date); |
| } |
| else { |
| ne_strnzcpy(from, _("[invalid date]"), NE_SSL_VDATELEN); |
| } |
| } |
| |
| if (until) { |
| if (tu != (time_t) -1) { |
| date = ne_rfc1123_date(tu); |
| ne_strnzcpy(until, date, NE_SSL_VDATELEN); |
| ne_free(date); |
| } |
| else { |
| ne_strnzcpy(until, _("[invalid date]"), NE_SSL_VDATELEN); |
| } |
| } |
| #endif |
| } |
| |
| #ifdef NE_HAVE_SSL |
| void ne__ssl_set_verify_err(ne_session *sess, int failures) |
| { |
| static const struct { |
| int bit; |
| const char *str; |
| } reasons[] = { |
| { NE_SSL_NOTYETVALID, N_("certificate is not yet valid") }, |
| { NE_SSL_EXPIRED, N_("certificate has expired") }, |
| { NE_SSL_IDMISMATCH, N_("certificate issued for a different hostname") }, |
| { NE_SSL_UNTRUSTED, N_("issuer is not trusted") }, |
| { NE_SSL_BADCHAIN, N_("bad certificate chain") }, |
| { NE_SSL_REVOKED, N_("certificate has been revoked") }, |
| { 0, NULL } |
| }; |
| int n, flag = 0; |
| |
| strcpy(sess->error, _("Server certificate verification failed: ")); |
| |
| for (n = 0; reasons[n].bit; n++) { |
| if (failures & reasons[n].bit) { |
| if (flag) strncat(sess->error, ", ", sizeof sess->error - 1); |
| strncat(sess->error, _(reasons[n].str), sizeof sess->error - 1); |
| flag = 1; |
| } |
| } |
| } |
| |
| |
| |
| int ne__ssl_match_hostname(const char *cn, size_t cnlen, const char *hostname) |
| { |
| const char *dot; |
| |
| NE_DEBUG(NE_DBG_SSL, "ssl: Match common name '%s' against '%s'\n", |
| cn, hostname); |
| |
| if (strncmp(cn, "*.", 2) == 0 && cnlen > 2 |
| && (dot = strchr(hostname, '.')) != NULL) { |
| ne_inet_addr *ia; |
| |
| |
| |
| |
| |
| |
| |
| ia = ne_iaddr_parse(hostname, ne_iaddr_ipv4); |
| if (ia == NULL) |
| ia = ne_iaddr_parse(hostname, ne_iaddr_ipv6); |
| |
| if (ia) { |
| NE_DEBUG(NE_DBG_SSL, "ssl: Denying wildcard match for numeric " |
| "IP address.\n"); |
| ne_iaddr_free(ia); |
| return 0; |
| } |
| |
| hostname = dot + 1; |
| cn += 2; |
| cnlen -= 2; |
| } |
| |
| return cnlen == strlen(hostname) && !ne_strcasecmp(cn, hostname); |
| } |
| |
| #endif |
| |
| typedef void (*void_fn)(void); |
| |
| #define ADD_HOOK(hooks, fn, ud) add_hook(&(hooks), NULL, (void_fn)(fn), (ud)) |
| |
| static void add_hook(struct hook **hooks, const char *id, void_fn fn, void *ud) |
| { |
| struct hook *hk = ne_malloc(sizeof (struct hook)), *pos; |
| |
| if (*hooks != NULL) { |
| for (pos = *hooks; pos->next != NULL; pos = pos->next) |
| ; |
| pos->next = hk; |
| } else { |
| *hooks = hk; |
| } |
| |
| hk->id = id; |
| hk->fn = fn; |
| hk->userdata = ud; |
| hk->next = NULL; |
| } |
| |
| void ne_hook_create_request(ne_session *sess, |
| ne_create_request_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->create_req_hooks, fn, userdata); |
| } |
| |
| void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->pre_send_hooks, fn, userdata); |
| } |
| |
| void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->post_send_hooks, fn, userdata); |
| } |
| |
| void ne_hook_post_headers(ne_session *sess, ne_post_headers_fn fn, |
| void *userdata) |
| { |
| ADD_HOOK(sess->post_headers_hooks, fn, userdata); |
| } |
| |
| void ne_hook_destroy_request(ne_session *sess, |
| ne_destroy_req_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->destroy_req_hooks, fn, userdata); |
| } |
| |
| void ne_hook_destroy_session(ne_session *sess, |
| ne_destroy_sess_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->destroy_sess_hooks, fn, userdata); |
| } |
| |
| void ne_hook_close_conn(ne_session *sess, |
| ne_close_conn_fn fn, void *userdata) |
| { |
| ADD_HOOK(sess->close_conn_hooks, fn, userdata); |
| } |
| |
| void ne_set_session_private(ne_session *sess, const char *id, void *userdata) |
| { |
| add_hook(&sess->private, id, NULL, userdata); |
| } |
| |
| static void remove_hook(struct hook **hooks, void_fn fn, void *ud) |
| { |
| struct hook **p = hooks; |
| |
| while (*p) { |
| if ((*p)->fn == fn && (*p)->userdata == ud) { |
| struct hook *next = (*p)->next; |
| ne_free(*p); |
| (*p) = next; |
| break; |
| } |
| p = &(*p)->next; |
| } |
| } |
| |
| #define REMOVE_HOOK(hooks, fn, ud) remove_hook(&hooks, (void_fn)fn, ud) |
| |
| void ne_unhook_create_request(ne_session *sess, |
| ne_create_request_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->create_req_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->pre_send_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_post_headers(ne_session *sess, ne_post_headers_fn fn, |
| void *userdata) |
| { |
| REMOVE_HOOK(sess->post_headers_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->post_send_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_destroy_request(ne_session *sess, |
| ne_destroy_req_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->destroy_req_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_destroy_session(ne_session *sess, |
| ne_destroy_sess_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->destroy_sess_hooks, fn, userdata); |
| } |
| |
| void ne_unhook_close_conn(ne_session *sess, |
| ne_close_conn_fn fn, void *userdata) |
| { |
| REMOVE_HOOK(sess->close_conn_hooks, fn, userdata); |
| } |