/* HTTP session handling Copyright (C) 1999-2009, Joe Orton This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "config.h" #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_LIBPROXY #include #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" /* Destroy a a list of hooks. */ 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); } /* Destroy the sess->proxies array. */ 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"); /* Run the destroy hooks. */ 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); } /* Close the connection; note that the notifier callback could * still be invoked here. */ 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; } /* Stores the "hostname[:port]" segment */ 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); } /* Stores the hostname/port in *info, setting up the "hostport" * segment correctly. */ 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."); /* use SSL if scheme is https */ sess->use_ssl = !strcmp(scheme, "https"); /* set the hostname/port */ 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; /* If the hostname parses as an IP address, don't * enable SNI by default. */ 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); /* Set flags which default to on: */ 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); /* Create URI for session to pass off to libproxy */ memset(&uri, 0, sizeof uri); ne_fill_server_uri(sess, &uri); uri.path = "/"; /* make valid URI structure. */ url = ne_uri_unparse(&uri); uri.path = NULL; /* Get list of pseudo-URIs from libproxy: */ 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; /* Hostname/port required for http/socks schemes. */ if (ptype != PROXY_NONE && !(uri.host && uri.port)) continue; /* Do nothing if libproxy returned only a single "direct://" * entry -- a single "direct" (noop) proxy is equivalent to * having none. */ 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) { /* A "direct" URI requires an attempt to connect directly to * the origin server, so dup the server details. */ set_hostinfo(hi, ptype, sess->server.hostname, sess->server.port); } else { /* SOCKS/HTTP proxy. */ 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; } /* Free up the proxies array: */ 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); } /* Run the close_conn hooks. */ 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; ne_strnzcpy(sess->error, _("Server certificate verification failed: "), sizeof sess->error); 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; } } } /* This doesn't actually implement complete RFC 2818 logic; omits * "f*.example.com" support for simplicity. */ 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; /* Prevent wildcard CN matches against anything which can be * parsed as an IP address (i.e. a CN of "*.1.1.1" should not * be match 8.1.1.1). draft-saintandre-tls-server-id-check * will require some more significant changes to cert ID * verification which will probably obviate this check, but * this is a desirable policy tightening in the mean time. */ 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 /* NE_HAVE_SSL */ 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) /* nullop */; 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); }