From c9d23a0d8b2b1e8231b02784b49eba43d592375c Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Wed, 15 Oct 2014 15:33:37 +0000 Subject: [PATCH] added https proxying without certificate check. this is different from CONNECT. when this feature is used, the proxy establishes a https connection to the origin server --- qse/cmd/http/httpd.c | 18 ++- qse/include/qse/http/httpd.h | 35 ++++- qse/lib/http/httpd-proxy.c | 92 +++++++---- qse/lib/http/httpd-std.c | 286 ++++++++++++++++++++++++++++++----- qse/lib/http/httpd.c | 11 +- qse/lib/http/httpd.h | 4 +- 6 files changed, 358 insertions(+), 88 deletions(-) diff --git a/qse/cmd/http/httpd.c b/qse/cmd/http/httpd.c index a56b22af..d44de8a2 100644 --- a/qse/cmd/http/httpd.c +++ b/qse/cmd/http/httpd.c @@ -199,6 +199,7 @@ struct loccfg_t struct { unsigned int allow_http: 1; + unsigned int allow_https: 1; unsigned int allow_connect: 1; unsigned int allow_intercept: 2; /* 0: no, 1: proxy, 2: local */ unsigned int allow_upgrade: 1; @@ -468,6 +469,7 @@ static int get_server_root ( { qse_http_method_t mth; qse_mchar_t* qpath; + int proto_len; qse_memset (root, 0, QSE_SIZEOF(*root)); mth = qse_htre_getqmethodtype (qinfo->req); @@ -534,13 +536,12 @@ static int get_server_root ( } } -/* TODO: handle https:// .... */ - if (loccfg->proxy.allow_http && - qse_mbszcasecmp (qpath, QSE_MT("http://"), 7) == 0) + if ((loccfg->proxy.allow_http && qse_mbszcasecmp (qpath, QSE_MT("http://"), (proto_len = 7)) == 0) || + (loccfg->proxy.allow_https && qse_mbszcasecmp (qpath, QSE_MT("https://"), (proto_len = 8)) == 0)) { qse_mchar_t* host, * slash; - host = qpath + 7; + host = qpath + proto_len; slash = qse_mbschr (host, QSE_MT('/')); if (slash && slash - host > 0) @@ -561,6 +562,7 @@ static int get_server_root ( host = host - 1; root->u.proxy.host = host; + if (proto_len == 8) root->u.proxy.flags |= QSE_HTTPD_RSRC_PROXY_DST_SECURE; if (qse_mbstonwad (host, &root->u.proxy.dst.nwad) <= -1) { root->u.proxy.flags |= QSE_HTTPD_RSRC_PROXY_DST_STR; @@ -576,7 +578,6 @@ static int get_server_root ( /* TODO: refrain from manipulating the request like this */ qinfo->req->u.q.path = slash; /* TODO: use setqpath or something... */ - goto proxy_ok; } else @@ -1513,6 +1514,11 @@ static int load_loccfg_proxy (qse_httpd_t* httpd, qse_xli_t* xli, qse_xli_list_t if (!pair && default_proxy) pair = qse_xli_findpair (xli, default_proxy, QSE_T("http")); /* server-default.proxy.http */ if (pair) cfg->proxy.allow_http = get_boolean ((qse_xli_str_t*)pair->val); + pair = QSE_NULL; + if (proxy) pair = qse_xli_findpair (xli, proxy, QSE_T("https")); /* server.host[].location[].proxy.https */ + if (!pair && default_proxy) pair = qse_xli_findpair (xli, default_proxy, QSE_T("https")); /* server-default.proxy.https */ + if (pair) cfg->proxy.allow_https = get_boolean ((qse_xli_str_t*)pair->val); + pair = QSE_NULL; if (proxy) pair = qse_xli_findpair (xli, proxy, QSE_T("connect")); if (!pair && default_proxy) pair = qse_xli_findpair (xli, default_proxy, QSE_T("connect")); @@ -2067,6 +2073,7 @@ static int open_config_file (qse_httpd_t* httpd) { QSE_T("server-default.error-foot"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server-default.proxy"), { QSE_XLI_SCM_VALLIST | QSE_XLI_SCM_KEYNODUP, 0, 0 } }, { QSE_T("server-default.proxy.http"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, + { QSE_T("server-default.proxy.https"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server-default.proxy.connect"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server-default.proxy.intercept"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server-default.proxy.upgrade"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, @@ -2125,6 +2132,7 @@ static int open_config_file (qse_httpd_t* httpd) { QSE_T("server.host.location.error-foot"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server.host.location.proxy"), { QSE_XLI_SCM_VALLIST | QSE_XLI_SCM_KEYNODUP, 0, 0 } }, { QSE_T("server.host.location.proxy.http"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, + { QSE_T("server.host.location.proxy.https"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server.host.location.proxy.connect"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server.host.location.proxy.intercept"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, { QSE_T("server.host.location.proxy.upgrade"), { QSE_XLI_SCM_VALSTR | QSE_XLI_SCM_KEYNODUP, 1, 1 } }, diff --git a/qse/include/qse/http/httpd.h b/qse/include/qse/http/httpd.h index 2905190c..6da384f9 100644 --- a/qse/include/qse/http/httpd.h +++ b/qse/include/qse/http/httpd.h @@ -58,7 +58,8 @@ enum qse_httpd_errnum_t QSE_HTTPD_EAGAIN, QSE_HTTPD_ENOSVR, /* no active servers */ - QSE_HTTPD_ECONN, + QSE_HTTPD_ECONN, /* connection failure */ + QSE_HTTPD_ESCONN, /* secure connection failure */ QSE_HTTPD_ENOBUF, /* no buffer available */ QSE_HTTPD_EDISCON, /* client disconnnected */ QSE_HTTPD_EBADREQ, /* bad request */ @@ -146,12 +147,31 @@ struct qse_httpd_stat_t qse_ntime_t mtime; }; +enum qse_httpd_peer_flag_t +{ + QSE_HTTPD_PEER_SECURE = (1 << 0), + + /* ---------------------------------- */ + + /* indicate the underlying socket is connected. internal use only. don't set it */ + QSE_HTTPD_PEER_CONNECTED = (1 << 20), + + /* internal use only */ + QSE_HTTPD_PEER_PENDING = (1 << 21), + + /* all internal enumerators */ + QSE_HTTPD_PEER_ALL_INTERNALS = (QSE_HTTPD_PEER_CONNECTED | QSE_HTTPD_PEER_PENDING) +}; +typedef enum qse_httpd_peer_flag_t qse_httpd_peer_flag_t; + typedef struct qse_httpd_peer_t qse_httpd_peer_t; struct qse_httpd_peer_t { + int flags; /* 0 or bitwised-OR'ed of qse_httpd_peer_flag_t enumerators */ qse_nwad_t nwad; qse_nwad_t local; /* local side address facing the peer */ - qse_httpd_hnd_t handle; + qse_httpd_hnd_t handle; + qse_httpd_hnd_t handle2; }; enum qse_httpd_mux_mask_t @@ -529,7 +549,7 @@ typedef struct qse_httpd_task_trigger_t qse_httpd_task_trigger_t; struct qse_httpd_task_trigger_t { int flags; /**< [IN] bitwise-ORed of #qse_httpd_task_trigger_flag_t enumerators*/ - int cmask; /* client mask - QSE_HTTPD_TASK_TRIGGER_READ | QSE_HTTPD_TASK_TRIGGER_WRITE */ + unsigned int cmask; /* client mask - QSE_HTTPD_TASK_TRIGGER_READ | QSE_HTTPD_TASK_TRIGGER_WRITE */ struct { int mask; /* QSE_HTTPD_TASK_TRIGGER_READ | QSE_HTTPD_TASK_TRIGGER_WRITE */ @@ -772,10 +792,11 @@ enum qse_httpd_rsrc_proxy_flag_t QSE_HTTPD_RSRC_PROXY_ALLOW_UPGRADE = (1 << 2), /* allow protocol upgrade */ QSE_HTTPD_RSRC_PROXY_X_FORWARDED = (1 << 3), /* add x-forwarded-for and x-forwarded-proto */ QSE_HTTPD_RSRC_PROXY_DST_STR = (1 << 4), /* destination is an unresovled string pointed to by dst.str */ - QSE_HTTPD_RSRC_PROXY_ENABLE_DNS = (1 << 5), /* dns service enabled (udp) */ - QSE_HTTPD_RSRC_PROXY_ENABLE_URS = (1 << 6), /* url rewriting enabled (udp) */ - QSE_HTTPD_RSRC_PROXY_DNS_SERVER = (1 << 7), /* dns address specified */ - QSE_HTTPD_RSRC_PROXY_URS_SERVER = (1 << 8), /* urs address specified */ + QSE_HTTPD_RSRC_PROXY_DST_SECURE = (1 << 5), /* use secure connection to destination */ + QSE_HTTPD_RSRC_PROXY_ENABLE_DNS = (1 << 6), /* dns service enabled (udp) */ + QSE_HTTPD_RSRC_PROXY_ENABLE_URS = (1 << 7), /* url rewriting enabled (udp) */ + QSE_HTTPD_RSRC_PROXY_DNS_SERVER = (1 << 8), /* dns address specified */ + QSE_HTTPD_RSRC_PROXY_URS_SERVER = (1 << 9), /* urs address specified */ }; typedef enum qse_httpd_rsrc_proxy_flag_t qse_httpd_rsrc_proxy_flag_t; diff --git a/qse/lib/http/httpd-proxy.c b/qse/lib/http/httpd-proxy.c index 612bf153..923fa328 100644 --- a/qse/lib/http/httpd-proxy.c +++ b/qse/lib/http/httpd-proxy.c @@ -35,31 +35,31 @@ struct task_proxy_arg_t typedef struct task_proxy_t task_proxy_t; struct task_proxy_t { -#define PROXY_INIT_FAILED (1 << 0) -#define PROXY_RAW (1 << 1) -#define PROXY_TRANSPARENT (1 << 2) -#define PROXY_DNS_SERVER (1 << 3) /* dns server address specified */ -#define PROXY_URS_SERVER (1 << 4) /* urs server address specified */ -#define PROXY_OUTBAND_PEER_NAME (1 << 5) /* the peer_name pointer points to +#define PROXY_INIT_FAILED (1u << 0) +#define PROXY_RAW (1u << 1) +#define PROXY_TRANSPARENT (1u << 2) +#define PROXY_DNS_SERVER (1u << 3) /* dns server address specified */ +#define PROXY_URS_SERVER (1u << 4) /* urs server address specified */ +#define PROXY_OUTBAND_PEER_NAME (1u << 5) /* the peer_name pointer points to a separate memory chunk outside the task_proxy_t chunk. explicit deallocatin is required */ -#define PROXY_RESOLVE_PEER_NAME (1 << 6) -#define PROXY_PEER_NAME_RESOLVING (1 << 7) -#define PROXY_PEER_NAME_RESOLVED (1 << 8) -#define PROXY_PEER_NAME_UNRESOLVED (1 << 9) -#define PROXY_REWRITE_URL (1 << 10) -#define PROXY_URL_REWRITING (1 << 11) -#define PROXY_URL_PREREWRITTEN (1 << 12) /* URL has been prerewritten in prerewrite(). */ -#define PROXY_URL_REWRITTEN (1 << 13) -#define PROXY_URL_REDIRECTED (1 << 14) -#define PROXY_X_FORWARDED (1 << 15) /* Add X-Forwarded-For and X-Forwarded-Proto */ -#define PROXY_VIA (1 << 16) /* Via: added to the request */ -#define PROXY_VIA_RETURNING (1 << 17) /* Via: added to the response */ -#define PROXY_ALLOW_UPGRADE (1 << 18) -#define PROXY_UPGRADE_REQUESTED (1 << 19) -#define PROXY_PROTOCOL_SWITCHED (1 << 20) -#define PROXY_GOT_BAD_REQUEST (1 << 21) +#define PROXY_RESOLVE_PEER_NAME (1u << 6) +#define PROXY_PEER_NAME_RESOLVING (1u << 7) +#define PROXY_PEER_NAME_RESOLVED (1u << 8) +#define PROXY_PEER_NAME_UNRESOLVED (1u << 9) +#define PROXY_REWRITE_URL (1u << 10) +#define PROXY_URL_REWRITING (1u << 11) +#define PROXY_URL_PREREWRITTEN (1u << 12) /* URL has been prerewritten in prerewrite(). */ +#define PROXY_URL_REWRITTEN (1u << 13) +#define PROXY_URL_REDIRECTED (1u << 14) +#define PROXY_X_FORWARDED (1u << 15) /* Add X-Forwarded-For and X-Forwarded-Proto */ +#define PROXY_VIA (1u << 16) /* Via: added to the request */ +#define PROXY_VIA_RETURNING (1u << 17) /* Via: added to the response */ +#define PROXY_ALLOW_UPGRADE (1u << 18) +#define PROXY_UPGRADE_REQUESTED (1u << 19) +#define PROXY_PROTOCOL_SWITCHED (1u << 20) +#define PROXY_GOT_BAD_REQUEST (1u << 21) unsigned int flags; qse_httpd_t* httpd; qse_httpd_client_t* client; @@ -899,7 +899,13 @@ static void adjust_peer_name_and_port (task_proxy_t* proxy) else { if (proxy->flags & PROXY_RAW) proxy->peer_port = QSE_HTTPD_DEFAULT_SECURE_PORT; - else proxy->peer_port = QSE_HTTPD_DEFAULT_PORT; + else + { + if (proxy->peer.flags & QSE_HTTPD_PEER_SECURE) + proxy->peer_port = QSE_HTTPD_DEFAULT_SECURE_PORT; + else + proxy->peer_port = QSE_HTTPD_DEFAULT_PORT; + } } } @@ -940,6 +946,8 @@ static int task_init_proxy ( if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_X_FORWARDED) proxy->flags |= PROXY_X_FORWARDED; if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_ALLOW_UPGRADE) proxy->flags |= PROXY_ALLOW_UPGRADE; + if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_DST_SECURE) proxy->peer.flags |= QSE_HTTPD_PEER_SECURE; + proxy->peer.local = arg->rsrc->src.nwad; if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_DST_STR) { @@ -1414,6 +1422,7 @@ static int task_main_proxy_4 ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_proxy_t* proxy = (task_proxy_t*)task->ctx; + qse_ssize_t n; #if 0 printf ("task_main_proxy_4 trigger[0].mask=%d trigger[1].mask=%d trigger.cmask=%d\n", @@ -1425,8 +1434,7 @@ printf ("task_main_proxy_4 trigger[0].mask=%d trigger[1].mask=%d trigger.cmask=% if ((task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_READABLE) && proxy->buflen < QSE_SIZEOF(proxy->buf)) { - qse_ssize_t n; - + reread: /* reading from the peer */ httpd->errnum = QSE_HTTPD_ENOERR; n = httpd->opt.scb.peer.recv ( @@ -1521,7 +1529,6 @@ printf ("task_main_proxy_4 trigger[0].mask=%d trigger[1].mask=%d trigger.cmask=% * side is writable. it should be safe to write whenever * this task function is called. even if it's not writable, * it should still be ok as the client socket is non-blocking. */ - qse_ssize_t n; httpd->errnum = QSE_HTTPD_ENOERR; n = httpd->opt.scb.client.send (httpd, client, proxy->buf, proxy->buflen); @@ -1542,6 +1549,24 @@ printf ("task_main_proxy_4 trigger[0].mask=%d trigger[1].mask=%d trigger.cmask=% } } + if (proxy->peer.flags & QSE_HTTPD_PEER_PENDING) + { + /* this QSE_HTTPD_CLIENT_PENDING thing is a dirty hack for SSL. + * In SSL, data is transmitted in a record. a record can be + * as large as 16K bytes since its length field is 2 bytes. + * If SSL_read() has a record but it's given a smaller buffer + * than the actual record, the next call to select() won't return. + * there is no data to read at the socket layer. SSL_pending() can + * tell you the amount of data in the SSL buffer. I try to consume + * the pending data if the client.recv handler has set QSE_HTTPD_CLIENT_PENDING. */ + + /* BUG BUG BUG. + * it jumps back to read more. If the client-side is not writable, + * unnecessary loop is made between this 'goto' and the target label. + * HOW SHOULD I SOLVE THIS? USE A BIG BUFFER AS LARGE AS 16K? */ + /*if (proxy->buflen < QSE_SIZEOF(proxy->buf))*/ goto reread; + } + return 1; } @@ -2059,17 +2084,20 @@ printf ("XXXXXXXXXXXXXXXXXXXXXXXXXX URL REWRITTEN TO [%s].....\n", new_url); } else { + int proto_len; + QSE_ASSERT (QSE_STR_LEN(proxy->reqfwdbuf) > 0); /* TODO: Host rewriting?? */ /* TODO: Host rewriting - to support it, headers must be made available thru request cloning. * the request may not be valid after task_init_proxy */ - if (qse_mbszcasecmp (new_url, QSE_MT("http://"), 7) == 0) + if (qse_mbszcasecmp (new_url, QSE_MT("http://"), (proto_len = 7)) == 0 || + qse_mbszcasecmp (new_url, QSE_MT("https://"), (proto_len = 8)) == 0) { const qse_mchar_t* host; - host = new_url + 7; + host = new_url + proto_len; if (host[0] != QSE_MT('/') && host[0] != QSE_MT('\0')) { const qse_mchar_t* slash; @@ -2093,6 +2121,12 @@ printf ("XXXXXXXXXXXXXXXXXXXXXXXXXX URL REWRITTEN TO [%s].....\n", new_url); goto fail; } +/* TODO: antything todo when http is rewritten to HTTPS or vice versa */ + if (proto_len == 8) + proxy->peer.flags |= QSE_HTTPD_PEER_SECURE; + else + proxy->peer.flags &= ~QSE_HTTPD_PEER_SECURE; + if (qse_mbstonwad (tmp, &nwad) <= -1) { proxy->flags |= PROXY_RESOLVE_PEER_NAME | PROXY_OUTBAND_PEER_NAME; @@ -2104,7 +2138,7 @@ printf ("XXXXXXXXXXXXXXXXXXXXXXXXXX URL REWRITTEN TO [%s].....\n", new_url); if (qse_getnwadport(&nwad) == 0) { /* i don't care if tmp is X.X.X.X:0 or just X.X.X.X */ - qse_setnwadport (&nwad, qse_hton16(QSE_HTTPD_DEFAULT_PORT)); + qse_setnwadport (&nwad, qse_hton16(proto_len == 8? QSE_HTTPD_DEFAULT_SECURE_PORT: QSE_HTTPD_DEFAULT_PORT)); } proxy->peer.nwad = nwad; diff --git a/qse/lib/http/httpd-std.c b/qse/lib/http/httpd-std.c index 58a7e1fd..943edfef 100644 --- a/qse/lib/http/httpd-std.c +++ b/qse/lib/http/httpd-std.c @@ -577,7 +577,8 @@ typedef struct httpd_xtn_t httpd_xtn_t; struct httpd_xtn_t { #if defined(HAVE_SSL) - SSL_CTX* ssl_ctx; + SSL_CTX* ssl_client_ctx; + SSL_CTX* ssl_peer_ctx; #endif qse_httpd_ecb_t ecb; qse_httpd_dnsstd_t dns; @@ -587,7 +588,8 @@ struct httpd_xtn_t #if defined(HAVE_SSL) static int init_xtn_ssl (qse_httpd_t* httpd, qse_httpd_server_t* server) { - SSL_CTX* ctx; +/* BUG BUG BUG. SSL context for client must exist inside the seerver, i guess */ + SSL_CTX* client_ctx = QSE_NULL; httpd_xtn_t* xtn; server_xtn_t* server_xtn; qse_httpd_serverstd_ssl_t ssl; @@ -597,24 +599,28 @@ static int init_xtn_ssl (qse_httpd_t* httpd, qse_httpd_server_t* server) if (server_xtn->query (httpd, server, QSE_HTTPD_SERVERSTD_SSL, QSE_NULL, &ssl) <= -1) { - return -1; + goto oops; } if (ssl.certfile == QSE_NULL || ssl.keyfile == QSE_NULL) { qse_httpd_seterrnum (httpd, QSE_HTTPD_EINVAL); - return -1; + goto oops; } - ctx = SSL_CTX_new (SSLv23_server_method()); - if (ctx == QSE_NULL) return -1; + client_ctx = SSL_CTX_new (SSLv23_server_method()); + if (!client_ctx) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM); + goto oops; + } /*SSL_CTX_set_info_callback(ctx,ssl_info_callback);*/ - if (SSL_CTX_use_certificate_file (ctx, ssl.certfile, SSL_FILETYPE_PEM) == 0 || - SSL_CTX_use_PrivateKey_file (ctx, ssl.keyfile, SSL_FILETYPE_PEM) == 0 || - SSL_CTX_check_private_key (ctx) == 0 /*|| - SSL_CTX_use_certificate_chain_file (ctx, chainfile) == 0*/) + if (SSL_CTX_use_certificate_file (client_ctx, ssl.certfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_use_PrivateKey_file (client_ctx, ssl.keyfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_check_private_key (client_ctx) == 0 /*|| + SSL_CTX_use_certificate_chain_file (client_ctx, chainfile) == 0*/) { if (httpd->opt.trait & QSE_HTTPD_LOGACT) { @@ -626,25 +632,60 @@ static int init_xtn_ssl (qse_httpd_t* httpd, qse_httpd_server_t* server) httpd->opt.rcb.logact (httpd, &msg); } - SSL_CTX_free (ctx); - return -1; + qse_httpd_seterrnum (httpd, QSE_HTTPD_EINVAL); /* TODO: define a better error code */ + goto oops; } - /* TODO: SSL_CTX_set_verify(); SSL_CTX_set_verify_depth() */ /* TODO: CRYPTO_set_id_callback (); */ /* TODO: CRYPTO_set_locking_callback (); */ + SSL_CTX_set_read_ahead (client_ctx, 0); + + xtn->ssl_client_ctx = client_ctx; - SSL_CTX_set_read_ahead (ctx, 0); - xtn->ssl_ctx = ctx; return 0; + +oops: + if (client_ctx) SSL_CTX_free (client_ctx); + return -1; } static void fini_xtn_ssl (httpd_xtn_t* xtn) { /* TODO: CRYPTO_set_id_callback (QSE_NULL); */ /* TODO: CRYPTO_set_locking_callback (QSE_NULL); */ - SSL_CTX_free (xtn->ssl_ctx); + SSL_CTX_free (xtn->ssl_client_ctx); +} + +static int init_xtn_peer_ssl (qse_httpd_t* httpd) +{ + SSL_CTX* peer_ctx = QSE_NULL; + httpd_xtn_t* xtn; + + xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd); + + peer_ctx = SSL_CTX_new (SSLv23_client_method()); + if (!peer_ctx) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM); + goto oops; + } + + xtn->ssl_peer_ctx = peer_ctx; + +printf ("SSL PEER CTX ============>%p\n", xtn->ssl_peer_ctx); + return 0; + +oops: + if (peer_ctx) SSL_CTX_free (peer_ctx); + return -1; +} + +static void fini_xtn_peer_ssl (httpd_xtn_t* xtn) +{ + /* TODO: CRYPTO_set_id_callback (QSE_NULL); */ + /* TODO: CRYPTO_set_locking_callback (QSE_NULL); */ + SSL_CTX_free (xtn->ssl_peer_ctx); } #endif @@ -656,7 +697,8 @@ static void cleanup_standard_httpd (qse_httpd_t* httpd) xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd); #if defined(HAVE_SSL) - if (xtn->ssl_ctx) fini_xtn_ssl (xtn); + if (xtn->ssl_peer_ctx) fini_xtn_peer_ssl (xtn); + if (xtn->ssl_client_ctx) fini_xtn_ssl (xtn); #endif #if defined(USE_LTDL) @@ -1069,6 +1111,10 @@ static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) int connaddrsize, bindaddrsize; int connected = 1; qse_sck_hnd_t fd = QSE_INVALID_SCKHND; + SSL* ssl = QSE_NULL; + httpd_xtn_t* xtn; + + xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd); #if defined(_WIN32) unsigned long cmd; @@ -1079,6 +1125,9 @@ static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) #else int flag; #endif + + /* turn off internally used bits */ + peer->flags &= ~QSE_HTTPD_PEER_ALL_INTERNALS; connaddrsize = qse_nwadtoskad (&peer->nwad, &connaddr); if (connaddrsize <= -1) @@ -1117,6 +1166,33 @@ static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) if (set_socket_nonblock (httpd, fd, 1) <= -1) goto oops; + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + #if defined(HAVE_SSL) + if (!xtn->ssl_peer_ctx) + { + /* TODO: peer ssl initialization doesn't have to be delayed... */ + if (init_xtn_peer_ssl (httpd) <= -1) goto oops; + } + + ssl = SSL_new (xtn->ssl_peer_ctx); + if (ssl == QSE_NULL) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); /* TODO: better error code */ + goto oops; + } + + if (SSL_set_fd (ssl, fd) == 0) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); /* TODO: better error code */ + goto oops; + } + #else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + goto oops; + #endif + } + #if defined(_WIN32) if (connect (fd, (struct sockaddr*)&connaddr, connaddrsize) <= -1) { @@ -1154,13 +1230,48 @@ static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) } #endif - /*if (set_socket_nonblock (httpd, fd, 0) <= -1) goto oops;*/ + if ((peer->flags & QSE_HTTPD_PEER_SECURE) && connected) + { + #if defined(HAVE_SSL) + int ret = SSL_connect (ssl); + if (ret <= 0) + { + int err = SSL_get_error(ssl, ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + /* handshaking isn't complete. */ + peer->flags |= QSE_HTTPD_PEER_CONNECTED; + connected = 0; /* not fully connected yet */ + } + else + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESCONN); + goto oops; + } + } + else + { + peer->flags |= QSE_HTTPD_PEER_CONNECTED; + /* socket connected + ssl connected */ + } + #endif + } + /* take note the socket handle is in the non-blocking mode here */ peer->handle = fd; + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + #if defined(HAVE_SSL) + peer->handle2 = SSL_TO_HANDLE(ssl); + #endif + } return connected; oops: qse_httpd_seterrnum (httpd, SKERR_TO_ERRNUM()); +#if defined(HAVE_SSL) + if (ssl) SSL_free (ssl); +#endif if (qse_isvalidsckhnd(fd)) qse_closesckhnd (fd); return -1; @@ -1170,10 +1281,16 @@ oops: static void peer_close (qse_httpd_t* httpd, qse_httpd_peer_t* peer) { + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + #if defined(HAVE_SSL) + SSL_free (HANDLE_TO_SSL(peer->handle2)); + #endif + } qse_closesckhnd (peer->handle); } -static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +static int is_peer_socket_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) { #if defined(_WIN32) int len; @@ -1245,32 +1362,122 @@ static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) #endif } +static int is_peer_connected_securely (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +{ + int ret = SSL_connect (HANDLE_TO_SSL(peer->handle2)); + if (ret <= 0) + { + int err = SSL_get_error(HANDLE_TO_SSL(peer->handle2), ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + /* handshaking isn't complete. */ + return 0; /* not connected */ + } + else + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESCONN); + return -1; + } + } + return 1; +} + +static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +{ + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + if (peer->flags & QSE_HTTPD_PEER_CONNECTED) + { + return is_peer_connected_securely (httpd, peer); + } + else + { + int ret = is_peer_socket_connected (httpd, peer); + if (ret <= 0) return ret; + peer->flags |= QSE_HTTPD_PEER_CONNECTED; + return 0; + } + } + else + { + return is_peer_socket_connected (httpd, peer); + } +} + static qse_ssize_t peer_recv ( qse_httpd_t* httpd, qse_httpd_peer_t* peer, qse_mchar_t* buf, qse_size_t bufsize) { -#if defined(__DOS__) - qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); - return -1; -#else - qse_ssize_t ret = recv (peer->handle, buf, bufsize, 0); - if (ret <= -1) qse_httpd_seterrnum (httpd, SKERR_TO_ERRNUM()); - return ret; -#endif + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + #if defined(HAVE_SSL) + int ret = SSL_read (HANDLE_TO_SSL(peer->handle2), buf, bufsize); + if (ret <= -1) + { + int err = SSL_get_error(HANDLE_TO_SSL(peer->handle2),ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN); + else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); + } + + if (SSL_pending (HANDLE_TO_SSL(peer->handle2)) > 0) + peer->flags |= QSE_HTTPD_PEER_PENDING; + else + peer->flags &= ~QSE_HTTPD_PEER_PENDING; + + return ret; + #else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + #endif + } + else + { + #if defined(__DOS__) + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + #else + qse_ssize_t ret = recv (peer->handle, buf, bufsize, 0); + if (ret <= -1) qse_httpd_seterrnum (httpd, SKERR_TO_ERRNUM()); + return ret; + #endif + } } static qse_ssize_t peer_send ( qse_httpd_t* httpd, qse_httpd_peer_t* peer, const qse_mchar_t* buf, qse_size_t bufsize) { -#if defined(__DOS__) - qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); - return -1; -#else - qse_ssize_t ret = send (peer->handle, buf, bufsize, 0); - if (ret <= -1) qse_httpd_seterrnum (httpd, SKERR_TO_ERRNUM()); - return ret; -#endif + if (peer->flags & QSE_HTTPD_PEER_SECURE) + { + #if defined(HAVE_SSL) + int ret = SSL_write (HANDLE_TO_SSL(peer->handle2), buf, bufsize); + if (ret <= -1) + { + int err = SSL_get_error(HANDLE_TO_SSL(peer->handle2),ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN); + else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); + } + return ret; + #else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + #endif + } + else + { + #if defined(__DOS__) + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + #else + qse_ssize_t ret = send (peer->handle, buf, bufsize, 0); + if (ret <= -1) qse_httpd_seterrnum (httpd, SKERR_TO_ERRNUM()); + return ret; + #endif + } } /* ------------------------------------------------------------------- */ @@ -2008,7 +2215,7 @@ static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client) httpd_xtn_t* xtn; xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd); - if (!xtn->ssl_ctx) + if (!xtn->ssl_client_ctx) { /* delayed initialization of ssl */ if (init_xtn_ssl (httpd, client->server) <= -1) @@ -2017,7 +2224,7 @@ static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client) } } - QSE_ASSERT (xtn->ssl_ctx != QSE_NULL); + QSE_ASSERT (xtn->ssl_client_ctx != QSE_NULL); QSE_ASSERT (QSE_SIZEOF(client->handle2) >= QSE_SIZEOF(ssl)); if (HANDLE_TO_SSL(client->handle2)) @@ -2026,7 +2233,7 @@ static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client) } else { - ssl = SSL_new (xtn->ssl_ctx); + ssl = SSL_new (xtn->ssl_client_ctx); if (ssl == QSE_NULL) return -1; client->handle2 = SSL_TO_HANDLE(ssl); @@ -2058,7 +2265,8 @@ static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client) httpd->opt.rcb.logact (httpd, &msg); } - /* SSL_free (ssl); */ + /* client_closed() free this. no SSL_free() here. + SSL_free (ssl); */ return -1; } diff --git a/qse/lib/http/httpd.c b/qse/lib/http/httpd.c index af1a35bb..0be0912f 100644 --- a/qse/lib/http/httpd.c +++ b/qse/lib/http/httpd.c @@ -1103,12 +1103,11 @@ qse_printf (QSE_T("!!!!!FEEDING OK OK OK OK %d from %d\n"), (int)m, (int)client- /* this QSE_HTTPD_CLIENT_PENDING thing is a dirty hack for SSL. * In SSL, data is transmitted in a record. a record can be * as large as 16K bytes since its length field is 2 bytes. - * If SSL_read() has record a record but it's given a - * smaller buffer than the actuaal record, the next call - * to select() won't return. there is no data to read - * at the socket layer. SSL_pending() can tell you the - * amount of data in the SSL buffer. I try to consume - * the pending data if the client.recv handler set QSE_HTTPD_CLIENT_PENDING. + * If SSL_read() has a record but it's given a smaller buffer + * than the actual record, the next call to select() won't return. + * there is no data to read at the socket layer. SSL_pending() can + * tell you the amount of data in the SSL buffer. I try to consume + * the pending data if the client.recv handler has set QSE_HTTPD_CLIENT_PENDING. * * TODO: Investigate if there is any starvation issues. * What if a single client never stops sending? diff --git a/qse/lib/http/httpd.h b/qse/lib/http/httpd.h index a98ab754..90affdba 100644 --- a/qse/lib/http/httpd.h +++ b/qse/lib/http/httpd.h @@ -100,8 +100,8 @@ struct qse_httpd_real_task_t qse_httpd_real_task_t* next; }; -#define MAX_SEND_SIZE 4096 -#define MAX_RECV_SIZE 4096 +#define MAX_SEND_SIZE (4096 * 4) +#define MAX_RECV_SIZE (4096 * 2) #define MAX_NWAD_TEXT_SIZE 96