This patch is generated from the ssl-2_5 branch of s2_5 in squid Tue May 16 00:15:53 2006 GMT See http://devel.squid-cache.org/ Index: squid/configure.in diff -u squid/configure.in:1.42.2.83 squid/configure.in:1.42.2.30.2.18 --- squid/configure.in:1.42.2.83 Fri May 12 12:52:15 2006 +++ squid/configure.in Mon May 15 15:36:21 2006 @@ -1323,6 +1323,7 @@ openssl/err.h \ openssl/md5.h \ openssl/ssl.h \ + openssl/engine.h \ poll.h \ pwd.h \ regex.h \ Index: squid/src/HttpHeader.c diff -u squid/src/HttpHeader.c:1.10.6.26 squid/src/HttpHeader.c:1.10.6.4.2.15 --- squid/src/HttpHeader.c:1.10.6.26 Fri May 12 09:51:33 2006 +++ squid/src/HttpHeader.c Mon May 15 15:36:22 2006 @@ -130,6 +130,7 @@ #if X_ACCELERATOR_VARY {"X-Accelerator-Vary", HDR_X_ACCELERATOR_VARY, ftStr}, #endif + {"Front-End-Https", HDR_FRONT_END_HTTPS, ftStr}, {"Other:", HDR_OTHER, ftStr} /* ':' will not allow matches */ }; static HttpHeaderFieldInfo *Headers = NULL; Index: squid/src/access_log.c diff -u squid/src/access_log.c:1.15.6.8 squid/src/access_log.c:1.15.6.2.2.6 --- squid/src/access_log.c:1.15.6.8 Tue Mar 29 18:17:46 2005 +++ squid/src/access_log.c Thu Apr 14 18:13:49 2005 @@ -238,13 +238,20 @@ accessLogSquid(AccessLogEntry * al) { const char *client = NULL; - char *user = NULL; + const char *user = NULL; if (Config.onoff.log_fqdn) client = fqdncache_gethostbyaddr(al->cache.caddr, FQDN_LOOKUP_IF_MISS); if (client == NULL) client = inet_ntoa(al->cache.caddr); - user = accessLogFormatName(al->cache.authuser ? - al->cache.authuser : al->cache.rfc931); + user = accessLogFormatName(al->cache.authuser); + if (!user) + user = accessLogFormatName(al->cache.rfc931); +#if USE_SSL + if (!user) + user = accessLogFormatName(al->cache.ssluser); +#endif + if (user && !*user) + safe_free(user); logfilePrintf(logfile, "%9ld.%03d %6d %s %s/%03d %" PRINTF_OFF_T " %s %s %s %s%s/%s %s", (long int) current_time.tv_sec, (int) current_time.tv_usec / 1000, @@ -255,7 +262,7 @@ al->cache.size, al->private.method_str, al->url, - user && *user ? user : dash_str, + user ? user : dash_str, al->hier.ping.timedout ? "TIMEOUT_" : "", hier_strings[al->hier.code], al->hier.host, Index: squid/src/acl.c diff -u squid/src/acl.c:1.43.2.35 squid/src/acl.c:1.43.2.11.2.18 --- squid/src/acl.c:1.43.2.35 Fri Mar 10 19:16:30 2006 +++ squid/src/acl.c Tue Apr 18 08:57:52 2006 @@ -103,6 +103,14 @@ static SPLAYCMP aclArpCompare; static SPLAYWALKEE aclDumpArpListWalkee; #endif +#if USE_SSL +static void aclParseCertList(void *curlist); +static int aclMatchUserCert(void *data, aclCheck_t *); +static int aclMatchCACert(void *data, aclCheck_t *); +static wordlist *aclDumpCertList(void *data); +static void aclDestroyCertList(void *data); +#endif + static int aclCacheMatchAcl(dlink_list * cache, squid_acl acltype, void *data, char *MatchParam); static squid_acl @@ -186,6 +194,12 @@ return ACL_EXTERNAL; if (!strcmp(s, "urllogin")) return ACL_URLLOGIN; +#if USE_SSL + if (!strcmp(s, "user_cert")) + return ACL_USER_CERT; + if (!strcmp(s, "ca_cert")) + return ACL_CA_CERT; +#endif return ACL_NONE; } @@ -266,6 +280,12 @@ return "external"; if (type == ACL_URLLOGIN) return "urllogin"; +#if USE_SSL + if (type == ACL_USER_CERT) + return "user_cert"; + if (type == ACL_CA_CERT) + return "ca_cert"; +#endif return "ERROR"; } @@ -777,6 +797,91 @@ } } +#if USE_SSL +static void +aclParseCertList(void *curlist) +{ + acl_cert_data **datap = curlist; + splayNode **Top; + char *t; + char *attribute = strtokFile(); + if (!attribute) + self_destruct(); + if (*datap) { + if (strcasecmp((*datap)->attribute, attribute) != 0) + self_destruct(); + } else { + *datap = memAllocate(MEM_ACL_CERT_DATA); + (*datap)->attribute = xstrdup(attribute); + } + Top = &(*datap)->values; + while ((t = strtokFile())) { + *Top = splay_insert(xstrdup(t), *Top, aclDomainCompare); + } +} + +static int +aclMatchUserCert(void *data, aclCheck_t * checklist) +{ + acl_cert_data *cert_data = data; + const char *value; + SSL *ssl = fd_table[checklist->conn->fd].ssl; + + if (!ssl) + return 0; + value = sslGetUserAttribute(ssl, cert_data->attribute); + if (!value) + return 0; + cert_data->values = splay_splay(value, cert_data->values, (SPLAYCMP *) strcmp); + return !splayLastResult; +} + +static int +aclMatchCACert(void *data, aclCheck_t * checklist) +{ + acl_cert_data *cert_data = data; + const char *value; + SSL *ssl = fd_table[checklist->conn->fd].ssl; + + if (!ssl) + return 0; + value = sslGetCAAttribute(ssl, cert_data->attribute); + if (!value) + return 0; + cert_data->values = splay_splay(value, cert_data->values, (SPLAYCMP *) strcmp); + return !splayLastResult; +} + +static void +aclDestroyCertList(void *curlist) +{ + acl_cert_data **datap = curlist; + if (!*datap) + return; + splay_destroy((*datap)->values, xfree); + memFree(*datap, MEM_ACL_CERT_DATA); + *datap = NULL; +} + +static void +aclDumpCertListWalkee(void *node_data, void *outlist) +{ + /* outlist is really a wordlist ** */ + wordlistAdd(outlist, node_data); +} + +static wordlist * +aclDumpCertList(void *curlist) +{ + acl_cert_data *data = curlist; + wordlist *wl = NULL; + wordlistAdd(&wl, data->attribute); + if (data->values) + splay_walk(data->values, aclDumpCertListWalkee, &wl); + return wl; +} +#endif + void aclParseAclLine(acl ** head) { @@ -923,6 +1028,12 @@ case ACL_EXTERNAL: aclParseExternal(&A->data); break; +#if USE_SSL + case ACL_USER_CERT: + case ACL_CA_CERT: + aclParseCertList(&A->data); + break; +#endif case ACL_NONE: case ACL_ENUM_MAX: fatal("Bad ACL type"); @@ -1824,6 +1935,14 @@ case ACL_EXTERNAL: return aclMatchExternal(ae->data, checklist); /* NOTREACHED */ +#if USE_SSL + case ACL_USER_CERT: + return aclMatchUserCert(ae->data, checklist); + /* NOTREACHED */ + case ACL_CA_CERT: + return aclMatchCACert(ae->data, checklist); + /* NOTREACHED */ +#endif case ACL_NONE: case ACL_ENUM_MAX: break; @@ -2315,6 +2434,12 @@ case ACL_EXTERNAL: aclDestroyExternal(&a->data); break; +#if USE_SSL + case ACL_USER_CERT: + case ACL_CA_CERT: + aclDestroyCertList(&a->data); + break; +#endif case ACL_NONE: case ACL_ENUM_MAX: debug(28, 1) ("aclDestroyAcls: no case for ACL type %d\n", a->type); @@ -2732,6 +2857,11 @@ #endif case ACL_EXTERNAL: return aclDumpExternal(a->data); +#if USE_SSL + case ACL_USER_CERT: + case ACL_CA_CERT: + return aclDumpCertList(a->data); +#endif case ACL_NONE: case ACL_ENUM_MAX: break; Index: squid/src/cache_cf.c diff -u squid/src/cache_cf.c:1.38.6.29 squid/src/cache_cf.c:1.38.6.9.2.22 --- squid/src/cache_cf.c:1.38.6.29 Wed Oct 26 19:13:24 2005 +++ squid/src/cache_cf.c Wed Nov 2 14:42:33 2005 @@ -497,6 +497,9 @@ #endif if (Config.Store.maxInMemObjSize > 8 * 1024 * 1024) debug(22, 0) ("WARNING: Very large maximum_object_size_in_memory settings can have negative impact on performance\n"); +#if USE_SSL + Config.ssl_client.sslContext = sslCreateClientContext(Config.ssl_client.cert, Config.ssl_client.key, Config.ssl_client.version, Config.ssl_client.cipher, Config.ssl_client.options, Config.ssl_client.flags, Config.ssl_client.cafile, Config.ssl_client.capath, Config.ssl_client.crlfile); +#endif } /* Parse a time specification from the config file. Store the @@ -1572,6 +1575,45 @@ p->options.allow_miss = 1; } else if (!strncasecmp(token, "max-conn=", 9)) { p->max_conn = atoi(token + 9); +#if USE_SSL + } else if (strcmp(token, "ssl") == 0) { + p->use_ssl = 1; + } else if (strncmp(token, "sslcert=", 8) == 0) { + safe_free(p->sslcert); + p->sslcert = xstrdup(token + 8); + } else if (strncmp(token, "sslkey=", 7) == 0) { + safe_free(p->sslkey); + p->sslkey = xstrdup(token + 7); + } else if (strncmp(token, "sslversion=", 11) == 0) { + p->sslversion = atoi(token + 11); + } else if (strncmp(token, "ssloptions=", 11) == 0) { + safe_free(p->ssloptions); + p->ssloptions = xstrdup(token + 11); + } else if (strncmp(token, "sslcipher=", 10) == 0) { + safe_free(p->sslcipher); + p->sslcipher = xstrdup(token + 10); + } else if (strncmp(token, "sslcafile=", 10) == 0) { + safe_free(p->sslcafile); + p->sslcafile = xstrdup(token + 10); + } else if (strncmp(token, "sslcapath=", 10) == 0) { + safe_free(p->sslcapath); + p->sslcapath = xstrdup(token + 10); + } else if (strncmp(token, "sslcrlfile=", 11) == 0) { + safe_free(p->sslcrlfile); + p->sslcrlfile = xstrdup(token + 11); + } else if (strncmp(token, "sslflags=", 9) == 0) { + safe_free(p->sslflags); + p->sslflags = xstrdup(token + 9); + } else if (strncmp(token, "ssldomain=", 10) == 0) { + safe_free(p->ssldomain); + p->ssldomain = xstrdup(token + 10); +#endif + } else if (strcmp(token, "front-end-https") == 0) { + p->front_end_https = 1; + } else if (strcmp(token, "front-end-https=on") == 0) { + p->front_end_https = 1; + } else if (strcmp(token, "front-end-https=auto") == 0) { + p->front_end_https = 2; } else { debug(3, 0) ("parse_peer: token='%s'\n", token); self_destruct(); @@ -1599,6 +1641,11 @@ cbdataLock(p->digest); /* so we know when/if digest disappears */ } #endif +#if USE_SSL + if (p->use_ssl) { + p->sslContext = sslCreateClientContext(p->sslcert, p->sslkey, p->sslversion, p->sslcipher, p->ssloptions, p->sslflags, p->sslcafile, p->sslcapath, p->sslcrlfile); + } +#endif while (*head != NULL) head = &(*head)->next; *head = p; @@ -2488,12 +2535,36 @@ } else if (strncmp(token, "cipher=", 7) == 0) { safe_free(s->cipher); s->cipher = xstrdup(token + 7); + } else if (strncmp(token, "clientca=", 9) == 0) { + safe_free(s->clientca); + s->clientca = xstrdup(token + 9); + } else if (strncmp(token, "cafile=", 7) == 0) { + safe_free(s->cafile); + s->cafile = xstrdup(token + 7); + } else if (strncmp(token, "capath=", 7) == 0) { + safe_free(s->capath); + s->capath = xstrdup(token + 7); + } else if (strncmp(token, "crlfile=", 8) == 0) { + safe_free(s->crlfile); + s->crlfile = xstrdup(token + 8); + } else if (strncmp(token, "dhparams=", 9) == 0) { + safe_free(s->dhfile); + s->dhfile = xstrdup(token + 9); + } else if (strncmp(token, "sslflags=", 9) == 0) { + safe_free(s->sslflags); + s->sslflags = xstrdup(token + 9); + } else if (strncmp(token, "sslcontext=", 11) == 0) { + safe_free(s->sslcontext); + s->sslcontext = xstrdup(token + 11); } else { self_destruct(); } } while (*head) head = &(*head)->next; + s->sslContext = sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslcontext); + if (!s->sslContext) + self_destruct(); *head = s; } @@ -2501,18 +2572,30 @@ dump_https_port_list(StoreEntry * e, const char *n, const https_port_list * s) { while (s) { - storeAppendPrintf(e, "%s %s:%d cert=\"%s\" key=\"%s\"", + storeAppendPrintf(e, "%s %s:%d", n, inet_ntoa(s->s.sin_addr), - ntohs(s->s.sin_port), - s->cert, - s->key); + ntohs(s->s.sin_port)); + if (s->cert) + storeAppendPrintf(e, " cert=%s", s->cert); + if (s->key) + storeAppendPrintf(e, " key=%s", s->key); if (s->version) storeAppendPrintf(e, " version=%d", s->version); if (s->options) storeAppendPrintf(e, " options=%s", s->options); if (s->cipher) storeAppendPrintf(e, " cipher=%s", s->cipher); + if (s->cafile) + storeAppendPrintf(e, " cafile=%s", s->cafile); + if (s->capath) + storeAppendPrintf(e, " capath=%s", s->capath); + if (s->crlfile) + storeAppendPrintf(e, " crlfile=%s", s->crlfile); + if (s->dhfile) + storeAppendPrintf(e, " dhparams=%s", s->dhfile); + if (s->sslflags) + storeAppendPrintf(e, " sslflags=%s", s->sslflags); storeAppendPrintf(e, "\n"); s = s->next; } Index: squid/src/cf.data.pre diff -u squid/src/cf.data.pre:1.49.2.86 squid/src/cf.data.pre:1.49.2.27.2.32 --- squid/src/cf.data.pre:1.49.2.86 Sat Feb 25 19:13:57 2006 +++ squid/src/cf.data.pre Tue Apr 18 08:57:52 2006 @@ -129,8 +129,48 @@ NO_SSLv2 Disallow the use of SSLv2 NO_SSLv3 Disallow the use of SSLv3 NO_TLSv1 Disallow the use of TLSv1 - See src/ssl_support.c or OpenSSL documentation - for a more complete list. + SINGLE_DH_USE Always create a new key when using + temporary/ephemeral DH key exchanges + See src/ssl_support.c or OpenSSL SSL_CTX_set_options + documentation for a complete list of options. + + clientca= File containing the list of CAs to use when + requesting a client certificate + + cafile= File containing additional CA certificates to + use when verifying client certificates. If unset + clientca will be used. + + capath= Directory containing additional CA certificates + and CRL lists to use when verifying client certificates + + crlfile= File of additional CRL lists to use when verifying + the client certificate, in addition to CRLs stored in + the capath. Implies VERIFY_CRL flag below. + + dhparams= File containing DH parameters for temporary/ephemeral + DH key exchanges + + sslflags= Various flags modifying the use of SSL: + DELAYED_AUTH + Don't request client certificates + immediately, but wait until acl processing + requires a certificate (not yet implemented) + NO_DEFAULT_CA + Don't use the default CA lists built in + to OpenSSL. + NO_SESSION_REUSE + Don't allow for session reuse. Each connection + will result in a new SSL session. + VERIFY_CRL + Verify CRL lists when accepting client + certificates + VERIFY_CRL_ALL + Verify CRL lists for all certificates in the + client certificate chain + + sslcontext= SSL session ID context identifier. + DOC_END NAME: ssl_unclean_shutdown @@ -143,6 +183,97 @@ messages. DOC_END +NAME: ssl_engine +IFDEF: USE_SSL +TYPE: string +LOC: Config.SSL.ssl_engine +DEFAULT: none +DOC_START + The openssl engine to use. You will need to set this if you + would like to use hardware SSL acceleration for example. +DOC_END + +NAME: sslproxy_client_certificate +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.cert +TYPE: string +DOC_START + Client SSL Certificate to use when proxying https:// URLs +DOC_END + +NAME: sslproxy_client_key +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.key +TYPE: string +DOC_START + Client SSL Key to use when proxying https:// URLs +DOC_END + +NAME: sslproxy_version +IFDEF: USE_SSL +DEFAULT: 1 +LOC: Config.ssl_client.version +TYPE: int +DOC_START + SSL version level to use when proxying https:// URLs +DOC_END + +NAME: sslproxy_options +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.options +TYPE: string +DOC_START + SSL engine options to use when proxying https:// URLs +DOC_END + +NAME: sslproxy_cipher +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.cipher +TYPE: string +DOC_START + SSL cipher list to use when proxying https:// URLs +DOC_END + +NAME: sslproxy_cafile +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.cafile +TYPE: string +DOC_START +DOC_END + +NAME: sslproxy_capath +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.capath +TYPE: string +DOC_START +DOC_END + +NAME: sslproxy_flags +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.ssl_client.flags +TYPE: string +DOC_START +DOC_END + +NAME: sslpassword_program +IFDEF: USE_SSL +DEFAULT: none +LOC: Config.Program.ssl_password +TYPE: string +DOC_START + Specify a program used for entering SSL key passphrases + when using encrypted SSL certificate keys. If not specified + keys must either be unencrypted, or Squid started with the -N + option to allow it to query interactively for the passphrase. +DOC_END + NAME: icp_port udp_port TYPE: ushort DEFAULT: @DEFAULT_ICP_PORT@ @@ -236,7 +367,7 @@ DOC_START To specify other caches in a hierarchy, use the format: - cache_peer hostname type http_port icp_port + cache_peer hostname type http_port icp_port [options] For example, @@ -276,6 +407,13 @@ max-conn htcp carp-load-factor + ssl + sslcert=/path/to/ssl/certificate + sslkey=/path/to/ssl/key + sslversion=1|2|3|4 + sslcipher=... + ssloptions=... + front-end-https[=on|auto] use 'proxy-only' to specify objects fetched from this cache should not be saved locally. @@ -373,7 +511,61 @@ cache as one participating in a CARP array. The 'f' values for all CARP parents must add up to 1.0. - + + use 'ssl' to indicate that connections to this peer should + bs SSL/TLS encrypted. + + use 'sslcert=/path/to/ssl/certificate' to specify a client + SSL certificate to use when connecting to this peer. + + use 'sslkey=/path/to/ssl/key' to specify the private SSL + key corresponding to sslcert above. If 'sslkey' is not + specified then 'sslcert' is assumed to reference a + combined file containing both the certificate and the key. + + use sslversion=1|2|3|4 to specify the SSL version to use + when connecting to this peer + 1 = automatic (default) + 2 = SSL v2 only + 3 = SSL v3 only + 4 = TLS v1 only + + use sslcipher=... to specify the list of valid SSL chipers + to use when connecting to this peer + + use ssloptions=... to specify various SSL engine options: + NO_SSLv2 Disallow the use of SSLv2 + NO_SSLv3 Disallow the use of SSLv3 + NO_TLSv1 Disallow the use of TLSv1 + See src/ssl_support.c or the OpenSSL documentation for + a more complete list. + + use cafile=... to specify a file containing additional + CA certificates to use when verifying the peer certificate + + use capath=... to specify a directory containing additional + CA certificates to use when verifying the peer certificate + + use sslflags=... to specify various flags modifying the + SSL implementation: + DONT_VERIFY_PEER + Accept certificates even if they fail to + verify. + NO_DEFAULT_CA + Don't use the default CA list built in + to OpenSSL. + + use sslname= to specify the peer name as advertised + in it's certificate. Used for verifying the correctness + of the received peer certificate. If not specified the + peer hostname will be used. + + use front-end-https to enable the "Front-End-Https: On" + header needed when using Squid as a SSL frontend infront + of Microsoft OWA. See MS KB document Q307347 for details + on this header. If set to auto then the header will + only be added if the request is forwarded as a https:// + URL. NOTE: non-ICP/HTCP neighbors must be specified as 'parent'. DOC_END @@ -1570,6 +1762,10 @@ %PROTO Requested protocol %PORT Requested port %METHOD Request method + %USER_CERT SSL User certificate in PEM format + %USER_CERTCHAIN SSL User certificate chain in PEM format + %USER_CERT_xx SSL User certificate subject attribute xx + %USER_CA_xx SSL User certificate issuer attribute xx %{Header} HTTP request header %{Hdr:member} HTTP request header list member %{Hdr:;member} @@ -2135,6 +2331,14 @@ # external ACL lookup via a helper class defined by the # external_acl_type directive. + acl aclname user_cert attribute values... + # match against attributes in a user SSL certificate + # attribute is one of DN/C/O/CN/L/ST + + acl aclname ca_cert attribute values... + # match against attributes a users issuing CA SSL certificate + # attribute is one of DN/C/O/CN/L/ST + Examples: acl macaddress arp 09:00:2b:23:45:67 acl myexample dst_as 1241 Index: squid/src/client_side.c diff -u squid/src/client_side.c:1.47.2.76 squid/src/client_side.c:1.47.2.21.2.34 --- squid/src/client_side.c:1.47.2.76 Fri Mar 10 19:16:31 2006 +++ squid/src/client_side.c Mon May 15 04:53:40 2006 @@ -150,6 +150,9 @@ static void clientEatRequestBody(clientHttpRequest *); static BODY_HANDLER clientReadBody; static void clientAbortBody(request_t * req); +#if USE_SSL +static void httpsAcceptSSL(ConnStateData * connState, SSL_CTX * sslContext); +#endif static int checkAccelOnly(clientHttpRequest * http) @@ -891,6 +894,9 @@ packerClean(&p); memBufClean(&mb); } +#if USE_SSL + http->al.cache.ssluser = sslGetUserEmail(fd_table[conn->fd].ssl); +#endif accessLogLog(&http->al); clientUpdateCounters(http); clientdbUpdate(conn->peer.sin_addr, http->log_type, PROTO_HTTP, http->out.size); @@ -2459,7 +2465,15 @@ url); if (r->method == METHOD_CONNECT && !http->redirect.status) { http->log_type = LOG_TCP_MISS; - sslStart(http, &http->out.size, &http->al.http.code); +#if USE_SSL && SSL_CONNECT_INTERCEPT + if (Config.Sockaddr.https) { + static const char ok[] = "HTTP/1.0 200 Established\r\n\r\n"; + write(http->conn->fd, ok, strlen(ok)); + httpsAcceptSSL(http->conn, Config.Sockaddr.https->sslContext); + httpRequestFree(http); + } else +#endif + sslStart(http, &http->out.size, &http->al.http.code); return; } else if (r->method == METHOD_PURGE) { clientPurgeRequest(http); @@ -3568,38 +3582,79 @@ { ConnStateData *conn = data; X509 *client_cert; + SSL *ssl = fd_table[fd].ssl; int ret; - if ((ret = SSL_accept(fd_table[fd].ssl)) <= 0) { - if (BIO_sock_should_retry(ret)) { + errno = 0; + ERR_clear_error(); + if ((ret = SSL_accept(ssl)) <= 0) { + int ssl_error = SSL_get_error(ssl, ret); + switch (ssl_error) { + case SSL_ERROR_WANT_READ: commSetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, conn, 0); return; + case SSL_ERROR_WANT_WRITE: + commSetSelect(fd, COMM_SELECT_WRITE, clientNegotiateSSL, conn, 0); + return; + case SSL_ERROR_SYSCALL: + if (ret == 0) { + debug(83, 2) ("clientNegotiateSSL: Error negotiating SSL connection on FD %d: Aborted by client\n", fd); + comm_close(fd); + return; + } else { + int hard = 1; + if (errno == ECONNRESET) + hard = 0; + debug(83, hard ? 1 : 2) ("clientNegotiateSSL: Error negotiating SSL connection on FD %d: %s (%d)\n", + fd, strerror(errno), errno); + comm_close(fd); + return; + } + case SSL_ERROR_ZERO_RETURN: + debug(83, 1) ("clientNegotiateSSL: Error negotiating SSL connection on FD %d: Closed by client\n", fd); + comm_close(fd); + return; + default: + debug(83, 1) ("clientNegotiateSSL: Error negotiating SSL connection on FD %d: %s (%d/%d)\n", + fd, ERR_error_string(ERR_get_error(), NULL), ssl_error, ret); + comm_close(fd); + return; } - ret = ERR_get_error(); - if (ret) { - debug(83, 1) ("clientNegotiateSSL: Error negotiating SSL connection on FD %d: %s\n", - fd, ERR_error_string(ret, NULL)); + /* NOTREACHED */ + } + fd_table[fd].read_pending = COMM_PENDING_NOW; + if (SSL_session_reused(ssl)) { + debug(83, 2) ("clientNegotiateSSL: Session %p reused on FD %d (%s:%d)\n", SSL_get_session(ssl), fd, fd_table[fd].ipaddr, (int) fd_table[fd].remote_port); + } else { + if (do_debug(83, 4)) { + /* Write out the SSL session details.. actually the call below, but + * OpenSSL headers do strange typecasts confusing GCC.. */ + /* PEM_write_SSL_SESSION(debug_log, SSL_get_session(ssl)); */ +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x0090708FL + PEM_ASN1_write((i2d_of_void *) i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *) SSL_get_session(ssl), NULL, NULL, 0, NULL, NULL); +#else + PEM_ASN1_write(i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *) SSL_get_session(ssl), NULL, NULL, 0, NULL, NULL); +#endif + /* Note: This does not automatically fflush the log file.. */ } - comm_close(fd); - return; + debug(83, 2) ("clientNegotiateSSL: New session %p on FD %d (%s:%d)\n", SSL_get_session(ssl), fd, fd_table[fd].ipaddr, (int) fd_table[fd].remote_port); } - debug(83, 5) ("clientNegotiateSSL: FD %d negotiated cipher %s\n", fd, - SSL_get_cipher(fd_table[fd].ssl)); + debug(83, 3) ("clientNegotiateSSL: FD %d negotiated cipher %s\n", fd, + SSL_get_cipher(ssl)); - client_cert = SSL_get_peer_certificate(fd_table[fd].ssl); + client_cert = SSL_get_peer_certificate(ssl); if (client_cert != NULL) { - debug(83, 5) ("clientNegotiateSSL: FD %d client certificate: subject: %s\n", fd, + debug(83, 3) ("clientNegotiateSSL: FD %d client certificate: subject: %s\n", fd, X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0)); - debug(83, 5) ("clientNegotiateSSL: FD %d client certificate: issuer: %s\n", fd, + debug(83, 3) ("clientNegotiateSSL: FD %d client certificate: issuer: %s\n", fd, X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0)); X509_free(client_cert); } else { debug(83, 5) ("clientNegotiateSSL: FD %d has no certificate.\n", fd); } - - commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0); + clientReadRequest(fd, conn); } struct _https_port_data { @@ -3608,26 +3663,52 @@ typedef struct _https_port_data https_port_data; CBDATA_TYPE(https_port_data); +static void +httpsAcceptSSL(ConnStateData * connState, SSL_CTX * sslContext) +{ + SSL *ssl; + fde *F; + int fd = connState->fd; + if ((ssl = SSL_new(sslContext)) == NULL) { + int ssl_error = ERR_get_error(); + debug(83, 1) ("httpsAccept: Error allocating handle: %s\n", + ERR_error_string(ssl_error, NULL)); + comm_close(fd); + return; + } + SSL_set_fd(ssl, fd); +#ifdef CAVIUM_SSL + if (Config.SSL.cavium_nonblocking) + cav_set_nb_mode(ssl, 1); +#endif + F = &fd_table[fd]; + F->ssl = ssl; + F->read_method = &ssl_read_method; + F->write_method = &ssl_write_method; + debug(50, 5) ("httpsAcceptSSL: FD %d: starting SSL negotiation.\n", fd); + fd_note(fd, "client https connect"); + + commSetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); + commSetDefer(fd, clientReadDefer, connState); +} + /* handle a new HTTPS connection */ static void httpsAccept(int sock, void *data) { int *N = &incoming_sockets_accepted; https_port_data *https_port = data; - SSL_CTX *sslContext = https_port->sslContext; int fd = -1; - fde *F; ConnStateData *connState = NULL; struct sockaddr_in peer; struct sockaddr_in me; int max = INCOMING_HTTP_MAX; - SSL *ssl; - int ssl_error; #if USE_IDENT static aclCheck_t identChecklist; #endif commSetSelect(sock, COMM_SELECT_READ, httpsAccept, https_port, 0); while (max-- && !httpAcceptDefer(sock, NULL)) { + fde *F; memset(&peer, '\0', sizeof(struct sockaddr_in)); memset(&me, '\0', sizeof(struct sockaddr_in)); if ((fd = comm_accept(sock, &peer, &me)) < 0) { @@ -3636,20 +3717,8 @@ sock, xstrerror()); break; } - if ((ssl = SSL_new(sslContext)) == NULL) { - ssl_error = ERR_get_error(); - debug(83, 1) ("httpsAccept: Error allocating handle: %s\n", - ERR_error_string(ssl_error, NULL)); - break; - } - SSL_set_fd(ssl, fd); F = &fd_table[fd]; - F->ssl = ssl; - F->read_method = &ssl_read_method; - F->write_method = &ssl_write_method; debug(33, 4) ("httpsAccept: FD %d: accepted port %d client %s:%d\n", fd, F->local_port, F->ipaddr, F->remote_port); - debug(50, 5) ("httpsAccept: FD %d: starting SSL negotiation.\n", fd); - connState = cbdataAlloc(ConnStateData); connState->peer = peer; connState->log_addr = peer.sin_addr; @@ -3670,10 +3739,9 @@ if (aclCheckFast(Config.accessList.identLookup, &identChecklist)) identStart(&me, &peer, clientIdentDone, connState); #endif - commSetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); - commSetDefer(fd, clientReadDefer, connState); clientdbEstablished(peer.sin_addr, 1); (*N)++; + httpsAcceptSSL(connState, https_port->sslContext); } } @@ -3864,7 +3932,7 @@ continue; CBDATA_INIT_TYPE(https_port_data); https_port = cbdataAlloc(https_port_data); - https_port->sslContext = sslCreateContext(s->cert, s->key, s->version, s->cipher, s->options); + https_port->sslContext = s->sslContext; comm_listen(fd); commSetSelect(fd, COMM_SELECT_READ, httpsAccept, https_port, 0); commSetDefer(fd, httpAcceptDefer, NULL); Index: squid/src/comm.c diff -u squid/src/comm.c:1.18.6.6 squid/src/comm.c:1.18.6.2.8.9 --- squid/src/comm.c:1.18.6.6 Sat Sep 10 19:13:22 2005 +++ squid/src/comm.c Mon May 15 04:53:41 2006 @@ -588,6 +588,32 @@ comm_close(fd); } +#if USE_SSL +static void +commLingerSSLClose(int fd, void *unused) +{ + int ret; + LOCAL_ARRAY(char, buf, 1024); + + ret = FD_READ_METHOD(fd, buf, 1024); + if (n < 0 && errno != EAGAIN) { + debug(5, 3) ("commLingerSSLClose: FD %d read: %s\n", fd, xstrerror()); + comm_close(fd); + return; + } + ret = ssl_shutdown_method(fd); + if (ret == -1 && errno == EAGAIN) { + commSetSelect(fd, COMM_SELECT_WRITE, commLingerSSLClose, NULL, 0); + return; + } + if (shutdown(fd, 1) < 0) { + comm_close(fd); + return; + } + commSetSelect(fd, COMM_SELECT_READ, commLingerClose, NULL, 0); +} +#endif + static void commLingerTimeout(int fd, void *unused) { @@ -601,16 +627,20 @@ void comm_lingering_close(int fd) { + fd_note(fd, "lingering close"); + commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); + commSetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0); + commSetTimeout(fd, 10, commLingerTimeout, NULL); #if USE_SSL - if (fd_table[fd].ssl) - ssl_shutdown_method(fd); + if (fd_table[fd].ssl) { + commLingerSSLClose(fd, NULL); + return; + } #endif if (shutdown(fd, 1) < 0) { comm_close(fd); return; } - fd_note(fd, "lingering close"); - commSetTimeout(fd, 10, commLingerTimeout, NULL); commSetSelect(fd, COMM_SELECT_READ, commLingerClose, NULL, 0); } #endif @@ -622,23 +652,60 @@ void comm_reset_close(int fd) { + fde *F = &fd_table[fd]; struct linger L; L.l_onoff = 1; L.l_linger = 0; if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &L, sizeof(L)) < 0) debug(50, 0) ("commResetTCPClose: FD %d: %s\n", fd, xstrerror()); + F->flags.close_request = 1; comm_close(fd); } +static inline void +comm_close_finish(int fd) +{ + fd_close(fd); /* update fdstat */ + close(fd); + statCounter.syscalls.sock.closes++; +} + +static inline void +comm_close_ssl_finish(int fd) +{ + fde *F = &fd_table[fd]; + SSL_free(F->ssl); + F->ssl = NULL; + comm_close_finish(fd); +} + +static void +comm_close_ssl(int fd, void *unused) +{ + fde *F = &fd_table[fd]; + int ret = ssl_shutdown_method(fd); + if (ret <= 0 && F->write_pending) { + commSetSelect(fd, COMM_SELECT_WRITE, comm_close_ssl, NULL, 0); + return; + } + comm_close_ssl_finish(fd); +} + +static void +comm_close_ssl_timeout(int fd, void *unused) +{ + debug(50, 1) ("comm_close_ssl: FD %d: timeout\n", fd); + comm_close_ssl_finish(fd); +} + void comm_close(int fd) { - fde *F = NULL; + fde *F = &fd_table[fd]; debug(5, 5) ("comm_close: FD %d\n", fd); assert(fd >= 0); assert(fd < Squid_MaxFD); - F = &fd_table[fd]; if (F->flags.closing) return; @@ -647,23 +714,23 @@ assert(F->flags.open); assert(F->type != FD_FILE); F->flags.closing = 1; -#if USE_SSL - if (F->ssl) - ssl_shutdown_method(fd); -#endif CommWriteStateCallbackAndFree(fd, COMM_ERR_CLOSING); commCallCloseHandlers(fd); if (F->uses) /* assume persistent connect count */ pconnHistCount(1, F->uses); #if USE_SSL if (F->ssl) { - SSL_free(F->ssl); - F->ssl = NULL; + if (!F->flags.close_request) { + F->flags.close_request = 1; + commSetTimeout(fd, 10, comm_close_ssl_timeout, NULL); + comm_close_ssl(fd, NULL); + return; + } + comm_close_ssl_finish(fd); + return; } #endif - fd_close(fd); /* update fdstat */ - close(fd); - statCounter.syscalls.sock.closes++; + comm_close_finish(fd); } /* Send a udp datagram to specified TO_ADDR. */ Index: squid/src/comm_select.c diff -u squid/src/comm_select.c:1.8.6.6 squid/src/comm_select.c:1.8.6.2.2.5 --- squid/src/comm_select.c:1.8.6.6 Sun May 11 19:14:21 2003 +++ squid/src/comm_select.c Wed Nov 19 04:46:11 2003 @@ -312,7 +312,6 @@ #if DELAY_POOLS fd_set slowfds; #endif - PF *hdl = NULL; int fd; unsigned int i; unsigned int maxfd; @@ -349,31 +348,62 @@ events = 0; /* Check each open socket for a handler. */ if (fd_table[i].read_handler) { + int dopoll = 1; switch (commDeferRead(i)) { case 0: - events |= POLLRDNORM; break; case 1: + dopoll = 0; break; #if DELAY_POOLS case -1: - events |= POLLRDNORM; FD_SET(i, &slowfds); break; #endif default: fatalf("bad return value from commDeferRead(FD %d)\n", i); + /* NOTREACHED */ + } + if (dopoll) { + switch (fd_table[i].read_pending) { + case COMM_PENDING_NORMAL: + events |= POLLRDNORM; + break; + case COMM_PENDING_WANTS_WRITE: + events |= POLLWRNORM; + break; + case COMM_PENDING_WANTS_READ: + events |= POLLRDNORM; + break; + case COMM_PENDING_NOW: + events |= POLLRDNORM; + npending++; + break; + } + } + } + if (fd_table[i].write_handler) { + switch (fd_table[i].write_pending) { + case COMM_PENDING_NORMAL: + events |= POLLWRNORM; + break; + case COMM_PENDING_WANTS_WRITE: + events |= POLLWRNORM; + break; + case COMM_PENDING_WANTS_READ: + events |= POLLRDNORM; + break; + case COMM_PENDING_NOW: + events |= POLLWRNORM; + npending++; + break; } } - if (fd_table[i].write_handler) - events |= POLLWRNORM; if (events) { pfds[nfds].fd = i; pfds[nfds].events = events; pfds[nfds].revents = 0; nfds++; - if ((events & POLLRDNORM) && fd_table[i].flags.read_pending) - npending++; } } if (nfds == 0) { @@ -384,14 +414,10 @@ msec = 0; if (msec > MAX_POLL_TIME) msec = MAX_POLL_TIME; - for (;;) { - statCounter.syscalls.polls++; - num = poll(pfds, nfds, msec); - statCounter.select_loops++; - if (num >= 0 || npending > 0) - break; - if (ignoreErrno(errno)) - continue; + statCounter.syscalls.polls++; + num = poll(pfds, nfds, msec); + statCounter.select_loops++; + if (num < 0 && !ignoreErrno(errno)) { debug(5, 0) ("comm_poll: poll failure: %s\n", xstrerror()); assert(errno != EINVAL); return COMM_ERROR; @@ -415,8 +441,30 @@ fd = pfds[i].fd; if (fd == -1) continue; - if (fd_table[fd].flags.read_pending) + switch (fd_table[fd].read_pending) { + case COMM_PENDING_NORMAL: + case COMM_PENDING_WANTS_READ: + break; + case COMM_PENDING_WANTS_WRITE: + if (pfds[i].revents & (POLLOUT | POLLWRNORM)) + revents |= POLLIN; + break; + case COMM_PENDING_NOW: revents |= POLLIN; + break; + } + switch (fd_table[fd].write_pending) { + case COMM_PENDING_NORMAL: + case COMM_PENDING_WANTS_WRITE: + break; + case COMM_PENDING_WANTS_READ: + if (pfds[i].revents & (POLLIN | POLLRDNORM)) + revents |= POLLOUT; + break; + case COMM_PENDING_NOW: + revents |= POLLOUT; + break; + } if (revents == 0) continue; if (fdIsIcp(fd)) { @@ -433,15 +481,17 @@ } F = &fd_table[fd]; if (revents & (POLLRDNORM | POLLIN | POLLHUP | POLLERR)) { + PF *hdl = F->read_handler; debug(5, 6) ("comm_poll: FD %d ready for reading\n", fd); - if (NULL == (hdl = F->read_handler)) - (void) 0; + if (hdl == NULL) + (void) 0; /* Nothing to do */ #if DELAY_POOLS else if (FD_ISSET(fd, &slowfds)) commAddSlowFd(fd); #endif else { F->read_handler = NULL; + F->read_pending = COMM_PENDING_NORMAL; hdl(fd, F->read_data); statCounter.select_fds++; if (commCheckICPIncoming) @@ -453,9 +503,11 @@ } } if (revents & (POLLWRNORM | POLLOUT | POLLHUP | POLLERR)) { + PF *hdl = F->write_handler; debug(5, 5) ("comm_poll: FD %d ready for writing\n", fd); - if ((hdl = F->write_handler)) { + if (hdl != NULL) { F->write_handler = NULL; + F->write_pending = COMM_PENDING_NORMAL; hdl(fd, F->write_data); statCounter.select_fds++; if (commCheckICPIncoming) @@ -500,9 +552,11 @@ #if DELAY_POOLS while ((fd = commGetSlowFd()) != -1) { fde *F = &fd_table[fd]; + PF *hdl = F->read_handler; debug(5, 6) ("comm_select: slow FD %d selected for reading\n", fd); - if ((hdl = F->read_handler)) { + if (hdl != NULL) { F->read_handler = NULL; + F->read_pending = COMM_PENDING_NORMAL; hdl(fd, F->read_data); statCounter.select_fds++; if (commCheckICPIncoming) Index: squid/src/defines.h diff -u squid/src/defines.h:1.15.6.4 squid/src/defines.h:1.15.6.3.2.2 --- squid/src/defines.h:1.15.6.4 Fri Mar 25 19:15:56 2005 +++ squid/src/defines.h Thu Apr 14 18:13:55 2005 @@ -75,8 +75,10 @@ #define COMM_NOCLOEXEC 0x02 #define COMM_REUSEADDR 0x04 +#define do_debug(SECTION, LEVEL) \ + ((_db_level = (LEVEL)) <= debugLevels[SECTION]) #define debug(SECTION, LEVEL) \ - ((_db_level = (LEVEL)) > debugLevels[SECTION]) ? (void) 0 : _db_print + !do_debug(SECTION, LEVEL) ? (void) 0 : _db_print #define safe_free(x) if (x) { xxfree(x); x = NULL; } Index: squid/src/enums.h diff -u squid/src/enums.h:1.29.2.18 squid/src/enums.h:1.29.2.7.2.11 --- squid/src/enums.h:1.29.2.18 Fri Nov 11 19:13:48 2005 +++ squid/src/enums.h Tue Apr 18 08:57:53 2006 @@ -140,6 +140,10 @@ ACL_MAX_USER_IP, ACL_EXTERNAL, ACL_URLLOGIN, +#if USE_SSL + ACL_USER_CERT, + ACL_CA_CERT, +#endif ACL_ENUM_MAX } squid_acl; @@ -245,6 +249,7 @@ #if X_ACCELERATOR_VARY HDR_X_ACCELERATOR_VARY, #endif + HDR_FRONT_END_HTTPS, HDR_OTHER, HDR_ENUM_END } http_hdr_type; @@ -291,7 +296,7 @@ typedef enum { HIER_NONE, - DIRECT, + HIER_DIRECT, SIBLING_HIT, PARENT_HIT, DEFAULT_PARENT, @@ -610,6 +615,9 @@ MEM_TLV, MEM_SWAP_LOG_DATA, MEM_CLIENT_REQ_BUF, +#if USE_SSL + MEM_ACL_CERT_DATA, +#endif MEM_MAX } mem_type; @@ -742,4 +750,15 @@ #endif +/* + * Special case pending filedescriptors. Set in fd_table[fd].read/write_pending + */ +typedef enum { + COMM_PENDING_NORMAL, /* No special processing required */ + COMM_PENDING_WANTS_READ, /* need to read, no matter what commSetSelect indicates */ + COMM_PENDING_WANTS_WRITE, /* need to write, no matter what commSetSelect indicates */ + COMM_PENDING_NOW /* needs to be called again, without needing to wait for readiness + * for example when data is already buffered etc */ +} comm_pending; + #endif /* SQUID_ENUMS_H */ Index: squid/src/external_acl.c diff -u squid/src/external_acl.c:1.2.4.28 squid/src/external_acl.c:1.2.4.11.2.16 --- squid/src/external_acl.c:1.2.4.28 Wed Mar 30 18:22:59 2005 +++ squid/src/external_acl.c Mon May 15 16:07:40 2006 @@ -97,7 +97,8 @@ struct _external_acl_format { enum { - EXT_ACL_LOGIN = 1, + EXT_ACL_UNKNOWN, + EXT_ACL_LOGIN, #if USE_IDENT EXT_ACL_IDENT, #endif @@ -109,7 +110,14 @@ EXT_ACL_HEADER, EXT_ACL_HEADER_MEMBER, EXT_ACL_HEADER_ID, - EXT_ACL_HEADER_ID_MEMBER + EXT_ACL_HEADER_ID_MEMBER, +#if USE_SSL + EXT_ACL_USER_CERT, + EXT_ACL_CA_CERT, + EXT_ACL_USER_CERT_RAW, + EXT_ACL_USER_CERTCHAIN_RAW, +#endif + EXT_ACL_END } type; external_acl_format *next; char *header; @@ -266,6 +274,19 @@ format->type = EXT_ACL_PORT; else if (strcmp(token, "%METHOD") == 0) format->type = EXT_ACL_METHOD; +#if USE_SSL + else if (strcmp(token, "%USER_CERT") == 0) + format->type = EXT_ACL_USER_CERT_RAW; + else if (strcmp(token, "%USER_CERTCHAIN") == 0) + format->type = EXT_ACL_USER_CERTCHAIN_RAW; + else if (strncmp(token, "%USER_CERT_", 11) == 0) { + format->type = EXT_ACL_USER_CERT; + format->header = xstrdup(token + 11); + } else if (strncmp(token, "%CA_CERT_", 9) == 0) { + format->type = EXT_ACL_CA_CERT; + format->header = xstrdup(token + 9); + } +#endif else { self_destruct(); } @@ -328,6 +349,24 @@ DUMP_EXT_ACL_TYPE(PROTO); DUMP_EXT_ACL_TYPE(PORT); DUMP_EXT_ACL_TYPE(METHOD); +#if USE_SSL + case EXT_ACL_USER_CERT_RAW: + storeAppendPrintf(sentry, " %%USER_CERT"); + break; + case EXT_ACL_USER_CERTCHAIN_RAW: + storeAppendPrintf(sentry, " %%USER_CERTCHAIN"); + break; + case EXT_ACL_USER_CERT: + storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header); + break; + case EXT_ACL_CA_CERT: + storeAppendPrintf(sentry, " %%CA_CERT_%s", format->header); + break; +#endif + case EXT_ACL_UNKNOWN: + case EXT_ACL_END: + fatal("unknown external_acl format error"); + break; } } for (word = node->cmdline; word; word = word->next) @@ -563,6 +602,40 @@ sb = httpHeaderGetListMember(&request->header, format->header_id, format->member, format->separator); str = strBuf(sb); break; +#if USE_SSL + case EXT_ACL_USER_CERT_RAW: + if (cbdataValid(ch->conn)) { + SSL *ssl = fd_table[ch->conn->fd].ssl; + if (ssl) + str = sslGetUserCertificatePEM(ssl); + } + break; + case EXT_ACL_USER_CERTCHAIN_RAW: + if (cbdataValid(ch->conn)) { + SSL *ssl = fd_table[ch->conn->fd].ssl; + if (ssl) + str = sslGetUserCertificateChainPEM(ssl); + } + break; + case EXT_ACL_USER_CERT: + if (cbdataValid(ch->conn)) { + SSL *ssl = fd_table[ch->conn->fd].ssl; + if (ssl) + str = sslGetUserAttribute(ssl, format->header); + } + break; + case EXT_ACL_CA_CERT: + if (cbdataValid(ch->conn)) { + SSL *ssl = fd_table[ch->conn->fd].ssl; + if (ssl) + str = sslGetCAAttribute(ssl, format->header); + } + break; +#endif + case EXT_ACL_UNKNOWN: + case EXT_ACL_END: + fatal("unknown external_acl format error"); + break; } if (str) if (!*str) Index: squid/src/forward.c diff -u squid/src/forward.c:1.13.6.16 squid/src/forward.c:1.13.6.1.8.21 --- squid/src/forward.c:1.13.6.16 Fri Mar 10 19:16:31 2006 +++ squid/src/forward.c Mon May 15 04:53:41 2006 @@ -213,18 +213,118 @@ fwdStateFree(fwdState); } +#if USE_SSL +static void +fwdNegotiateSSL(int fd, void *data) +{ + FwdState *fwdState = data; + FwdServer *fs = fwdState->servers; + SSL *ssl = fd_table[fd].ssl; + int ret; + ErrorState *err; + request_t *request = fwdState->request; + + errno = 0; + ERR_clear_error(); + if ((ret = SSL_connect(ssl)) <= 0) { + int ssl_error = SSL_get_error(ssl, ret); + switch (ssl_error) { + case SSL_ERROR_WANT_READ: + commSetSelect(fd, COMM_SELECT_READ, fwdNegotiateSSL, fwdState, 0); + return; + case SSL_ERROR_WANT_WRITE: + commSetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSL, fwdState, 0); + return; + default: + debug(81, 1) ("fwdNegotiateSSL: Error negotiating SSL connection on FD %d: %s (%d/%d/%d)\n", fd, ERR_error_string(ERR_get_error(), NULL), ssl_error, ret, errno); + err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE); +#ifdef EPROTO + err->xerrno = EPROTO; +#else + err->xerrno = EACCES; +#endif + err->request = requestLink(request); + fwdFail(fwdState, err); + if (fs->peer) { + peerConnectFailed(fs->peer); + fs->peer->stats.conn_open--; + } + comm_close(fd); + return; + } + } + if (fs->peer && !SSL_session_reused(ssl)) { + if (fs->peer->sslSession) + SSL_SESSION_free(fs->peer->sslSession); + fs->peer->sslSession = SSL_get1_session(ssl); + } +#if NOT_YET + if (verify_domain) { + char *host; + STACK_OF(GENERAL_NAME) * altnames; + if (fs->peer) { + if (fs->peer->ssldomain) + host = fs->peer->ssldomain; + else + host = fs->peer->host; + } else { + host = fs->request->host; + } + if (!ssl_verify_domain(host, ssl)) { + debug(17, 1) ("Warning: SSL certificate does not match host name '%s'\n", host); + } + } +#endif + fwdDispatch(fwdState); +} + +static void +fwdInitiateSSL(FwdState * fwdState) +{ + FwdServer *fs = fwdState->servers; + int fd = fwdState->server_fd; + SSL *ssl; + SSL_CTX *sslContext = NULL; + peer *peer = fs->peer; + if (peer) { + assert(peer->use_ssl); + sslContext = peer->sslContext; + } else { + sslContext = Config.ssl_client.sslContext; + } + assert(sslContext); + if ((ssl = SSL_new(sslContext)) == NULL) { + ErrorState *err; + debug(83, 1) ("fwdInitiateSSL: Error allocating handle: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR); + err->xerrno = errno; + err->request = requestLink(fwdState->request); + fwdFail(fwdState, err); + fwdStateFree(fwdState); + return; + } + SSL_set_fd(ssl, fd); + if (peer) { + if (peer->sslSession) + SSL_set_session(ssl, peer->sslSession); + } + fd_table[fd].ssl = ssl; + fd_table[fd].read_method = &ssl_read_method; + fd_table[fd].write_method = &ssl_write_method; + fwdNegotiateSSL(fd, fwdState); +} +#endif + static void fwdConnectDone(int server_fd, int status, void *data) { FwdState *fwdState = data; - static FwdState *current = NULL; FwdServer *fs = fwdState->servers; ErrorState *err; request_t *request = fwdState->request; - assert(current != fwdState); - current = fwdState; assert(fwdState->server_fd == server_fd); - if (Config.onoff.log_ip_on_direct && status != COMM_ERR_DNS && fs->code == DIRECT) + if (Config.onoff.log_ip_on_direct && status != COMM_ERR_DNS && fs->code == HIER_DIRECT) hierarchyNote(&fwdState->request->hier, fs->code, fd_table[server_fd].ipaddr); if (status == COMM_ERR_DNS) { /* @@ -254,9 +354,15 @@ fd_table[server_fd].uses++; if (fs->peer) peerConnectSucceded(fs->peer); +#if USE_SSL + if ((fs->peer && fs->peer->use_ssl) || + (!fs->peer && request->protocol == PROTO_HTTPS)) { + fwdInitiateSSL(fwdState); + return; + } +#endif fwdDispatch(fwdState); } - current = NULL; } static void @@ -268,7 +374,7 @@ ErrorState *err; debug(17, 2) ("fwdConnectTimeout: FD %d: '%s'\n", fd, storeUrl(entry)); assert(fd == fwdState->server_fd); - if (Config.onoff.log_ip_on_direct && fs->code == DIRECT && fd_table[fd].ipaddr[0]) + if (Config.onoff.log_ip_on_direct && fs->code == HIER_DIRECT && fd_table[fd].ipaddr[0]) hierarchyNote(&fwdState->request->hier, fs->code, fd_table[fd].ipaddr); if (entry->mem_obj->inmem_hi == 0) { err = errorCon(ERR_CONNECT_FAIL, HTTP_GATEWAY_TIMEOUT); @@ -380,7 +486,7 @@ if (!fs->peer) fwdState->origin_tries++; comm_add_close_handler(fd, fwdServerClosed, fwdState); - fwdConnectDone(fd, COMM_OK, fwdState); + fwdDispatch(fwdState); return; } else { /* Discard the persistent connection to not cause @@ -470,21 +576,22 @@ request_t *request = fwdState->request; StoreEntry *entry = fwdState->entry; ErrorState *err; + int server_fd = fwdState->server_fd; debug(17, 3) ("fwdDispatch: FD %d: Fetching '%s %s'\n", fwdState->client_fd, RequestMethodStr[request->method], storeUrl(entry)); - /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */ - assert(entry->ping_status != PING_WAITING); - assert(entry->lock_count); - EBIT_SET(entry->flags, ENTRY_DISPATCHED); - netdbPingSite(request->host); /* * Assert that server_fd is set. This is to guarantee that fwdState * is attached to something and will be deallocated when server_fd * is closed. */ - assert(fwdState->server_fd > -1); + assert(server_fd > -1); + /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */ + assert(entry->ping_status != PING_WAITING); + assert(entry->lock_count); + EBIT_SET(entry->flags, ENTRY_DISPATCHED); + netdbPingSite(request->host); if (fwdState->servers && (p = fwdState->servers->peer)) { p->stats.fetches++; fwdState->request->peer_login = p->login; @@ -492,6 +599,11 @@ } else { fwdState->request->peer_login = NULL; switch (request->protocol) { +#if USE_SSL + case PROTO_HTTPS: + httpStart(fwdState); + break; +#endif case PROTO_HTTP: httpStart(fwdState); break; Index: squid/src/http.c diff -u squid/src/http.c:1.17.6.32 squid/src/http.c:1.17.6.3.2.16 --- squid/src/http.c:1.17.6.32 Tue Oct 18 19:13:21 2005 +++ squid/src/http.c Mon May 15 04:53:41 2006 @@ -944,6 +944,10 @@ case HDR_CACHE_CONTROL: /* append these after the loop if needed */ break; + case HDR_FRONT_END_HTTPS: + if (!flags.front_end_https) + httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e)); + break; default: /* pass on all other header fields */ httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e)); @@ -1030,6 +1034,11 @@ httpHeaderPutStr(hdr_out, HDR_CONNECTION, "keep-alive"); } } + /* append Front-End-Https */ + if (flags.front_end_https) { + if (flags.front_end_https == 1 || request->protocol == PROTO_HTTPS) + httpHeaderPutStr(hdr_out, HDR_FRONT_END_HTTPS, "On"); + } /* Now mangle the headers. */ httpHdrMangleList(hdr_out, orig_request); stringClean(&strConnection); @@ -1099,10 +1108,12 @@ httpState->flags.keepalive = 1; else if ((double) p->stats.n_keepalives_recv / (double) p->stats.n_keepalives_sent > 0.50) httpState->flags.keepalive = 1; - if (httpState->peer) + if (httpState->peer) { if (neighborType(httpState->peer, httpState->request) == PEER_SIBLING && !httpState->peer->options.allow_miss) httpState->flags.only_if_cached = 1; + httpState->flags.front_end_https = httpState->peer->front_end_https; + } memBufDefInit(&mb); httpBuildRequestPrefix(req, httpState->orig_request, @@ -1132,7 +1143,7 @@ httpState->peer = fwd->servers->peer; /* might be NULL */ if (httpState->peer) { proxy_req = requestCreate(orig_req->method, - PROTO_NONE, storeUrl(httpState->entry)); + orig_req->protocol, storeUrl(httpState->entry)); xstrncpy(proxy_req->host, httpState->peer->host, SQUIDHOSTNAMELEN); proxy_req->port = httpState->peer->http_port; proxy_req->flags = orig_req->flags; Index: squid/src/mem.c diff -u squid/src/mem.c:1.13 squid/src/mem.c:1.13.24.1 --- squid/src/mem.c:1.13 Fri Sep 7 16:55:49 2001 +++ squid/src/mem.c Fri Nov 29 09:49:20 2002 @@ -199,6 +199,9 @@ memDataInit(MEM_ACL_IP_DATA, "acl_ip_data", sizeof(acl_ip_data), 0); memDataInit(MEM_ACL_LIST, "acl_list", sizeof(acl_list), 0); memDataInit(MEM_ACL_NAME_LIST, "acl_name_list", sizeof(acl_name_list), 0); +#if USE_SSL + memDataInit(MEM_ACL_CERT_DATA, "acl_cert_data", sizeof(acl_cert_data), 0); +#endif memDataInit(MEM_ACL_TIME_DATA, "acl_time_data", sizeof(acl_time_data), 0); memDataInit(MEM_AUTH_USER_T, "auth_user_t", sizeof(auth_user_t), 0); Index: squid/src/peer_select.c diff -u squid/src/peer_select.c:1.12 squid/src/peer_select.c:1.12.26.1 --- squid/src/peer_select.c:1.12 Sat Nov 17 17:15:42 2001 +++ squid/src/peer_select.c Mon Oct 27 08:56:31 2003 @@ -425,9 +425,9 @@ return; if (ps->request->protocol == PROTO_WAIS) /* Its not really DIRECT, now is it? */ - peerAddFwdServer(&ps->servers, Config.Wais.peer, DIRECT); + peerAddFwdServer(&ps->servers, Config.Wais.peer, HIER_DIRECT); else - peerAddFwdServer(&ps->servers, NULL, DIRECT); + peerAddFwdServer(&ps->servers, NULL, HIER_DIRECT); } static void Index: squid/src/ssl_support.c diff -u squid/src/ssl_support.c:1.6.6.3 squid/src/ssl_support.c:1.6.6.1.2.19 --- squid/src/ssl_support.c:1.6.6.3 Thu Dec 11 19:13:43 2003 +++ squid/src/ssl_support.c Mon May 15 04:53:42 2006 @@ -35,19 +35,69 @@ #include "squid.h" -extern int commUnsetNonBlocking(int fd); -extern int commSetNonBlocking(int fd); +static int +ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata) +{ + FILE *in; + int len = 0; + char cmdline[1024]; + snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", Config.Program.ssl_password, (const char *) userdata); + in = popen(cmdline, "r"); + if (fgets(buf, size, in)) + len = strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) + len--; + buf[len] = '\0'; + pclose(in); + return len; +} -void clientNegotiateSSL(int fd, void *data); -void clientReadSSLRequest(int fd, void *data); +static void +ssl_ask_password(SSL_CTX * context, const char *prompt) +{ + if (Config.Program.ssl_password) { + SSL_CTX_set_default_passwd_cb(context, ssl_ask_password_cb); + SSL_CTX_set_default_passwd_cb_userdata(context, (void *) prompt); + } +} static RSA * ssl_temp_rsa_cb(SSL * ssl, int export, int keylen) { - static RSA *rsa = NULL; + static RSA *rsa_512 = NULL; + static RSA *rsa_1024 = NULL; + RSA *rsa = NULL; + int newkey = 0; + + switch (keylen) { + case 512: + if (!rsa_512) { + rsa_512 = RSA_generate_key(512, RSA_F4, NULL, NULL); + newkey = 1; + } + rsa = rsa_512; + break; + case 1024: + if (!rsa_1024) { + rsa_1024 = RSA_generate_key(1024, RSA_F4, NULL, NULL); + newkey = 1; + } + rsa = rsa_1024; + break; + default: + debug(83, 1) ("ssl_temp_rsa_cb: Unexpected key length %d\n", keylen); + return NULL; + } - if (rsa == NULL) - rsa = RSA_generate_key(512, RSA_F4, NULL, NULL); + if (rsa == NULL) { + debug(83, 1) ("ssl_temp_rsa_cb: Failed to generate key %d\n", keylen); + return NULL; + } + if (newkey) { + if (do_debug(83, 5)) + PEM_write_RSAPrivateKey(debug_log, rsa, NULL, NULL, 0, NULL, NULL); + debug(83, 1) ("Generated ephemeral RSA key of length %d\n", keylen); + } return rsa; } @@ -55,12 +105,14 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx) { char buffer[256]; + X509 *peer_cert = ctx->cert; - X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buffer, + X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer, sizeof(buffer)); - if (ok) - debug(83, 5) ("SSL Certificate OK: %s\n", buffer); - else { + + if (ok) { + debug(83, 5) ("SSL Certificate signature OK: %s\n", buffer); + } else { switch (ctx->error) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: debug(83, 5) ("SSL Certficate error: CA not known: %s\n", buffer); @@ -78,7 +130,7 @@ debug(83, 5) ("SSL Certificate has invalid \'not after\' field: %s\n", buffer); break; default: - debug(83, 5) ("SSL unknown certificate error %d in %s\n", + debug(83, 1) ("SSL unknown certificate error %d in %s\n", ctx->error, buffer); break; } @@ -272,23 +324,117 @@ return op; } -SSL_CTX * -sslCreateContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options) +#define SSL_FLAG_NO_DEFAULT_CA (1<<0) +#define SSL_FLAG_DELAYED_AUTH (1<<1) +#define SSL_FLAG_DONT_VERIFY_PEER (1<<2) +#define SSL_FLAG_NO_SESSION_REUSE (1<<3) +#define SSL_FLAG_VERIFY_CRL (1<<4) +#define SSL_FLAG_VERIFY_CRL_ALL (1<<5) + +static long +ssl_parse_flags(const char *flags) +{ + long fl = 0; + char *tmp; + char *flag; + + if (!flags) + return 0; + + tmp = xstrdup(flags); + flag = strtok(tmp, ":,"); + while (flag) { + if (strcmp(flag, "NO_DEFAULT_CA") == 0) + fl |= SSL_FLAG_NO_DEFAULT_CA; + else if (strcmp(flag, "DELAYED_AUTH") == 0) + fl |= SSL_FLAG_DELAYED_AUTH; + else if (strcmp(flag, "DONT_VERIFY_PEER") == 0) + fl |= SSL_FLAG_DONT_VERIFY_PEER; + else if (strcmp(flag, "NO_SESSION_REUSE") == 0) + fl |= SSL_FLAG_NO_SESSION_REUSE; +#ifdef X509_V_FLAG_CRL_CHECK + else if (strcmp(flag, "VERIFY_CRL") == 0) + fl |= SSL_FLAG_VERIFY_CRL; + else if (strcmp(flag, "VERIFY_CRL_ALL") == 0) + fl |= SSL_FLAG_VERIFY_CRL_ALL; +#endif + else + fatalf("Unknown ssl flag '%s'", flag); + flag = strtok(NULL, ":,"); + } + safe_free(tmp); + return fl; +} + + +static void +ssl_initialize(void) { - int ssl_error; - SSL_METHOD *method; - SSL_CTX *sslContext; static int ssl_initialized = 0; if (!ssl_initialized) { ssl_initialized = 1; SSL_load_error_strings(); - SSLeay_add_ssl_algorithms(); + SSL_library_init(); +#ifdef HAVE_OPENSSL_ENGINE_H + if (Config.SSL.ssl_engine) { + ENGINE *e; + if (!(e = ENGINE_by_id(Config.SSL.ssl_engine))) { + fatalf("Unable to find SSL engine '%s'\n", Config.SSL.ssl_engine); + } + if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { + int ssl_error = ERR_get_error(); + fatalf("Failed to initialise SSL engine: %s\n", + ERR_error_string(ssl_error, NULL)); + } + } +#else + if (Config.SSL.ssl_engine) { + fatalf("Your OpenSSL has no SSL engine support\n"); + } +#endif + } +} + +static int +ssl_load_crl(SSL_CTX * sslContext, const char *CRLfile) +{ + X509_STORE *st = SSL_CTX_get_cert_store(sslContext); + X509_CRL *crl; + BIO *in = BIO_new_file(CRLfile, "r"); + int count = 0; + if (!in) { + debug(83, 2) ("WARNING: Failed to open CRL file '%s'\n", CRLfile); + return 0; + } + while ((crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL))) { + if (!X509_STORE_add_crl(st, crl)) + debug(83, 2) ("WARNING: Failed to add CRL from file '%s'\n", CRLfile); + else + count++; + X509_CRL_free(crl); } + BIO_free(in); + return count; +} + +SSL_CTX * +sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhfile, const char *context) +{ + int ssl_error; + SSL_METHOD *method; + SSL_CTX *sslContext; + long fl = ssl_parse_flags(flags); + + ssl_initialize(); + if (!keyfile) keyfile = certfile; if (!certfile) certfile = keyfile; + if (!CAfile) + CAfile = clientCA; + ERR_clear_error(); debug(83, 1) ("Initialising SSL.\n"); switch (version) { case 2: @@ -318,45 +464,229 @@ } SSL_CTX_set_options(sslContext, ssl_parse_options(options)); + if (context && *context) { + SSL_CTX_set_session_id_context(sslContext, (unsigned char *) context, strlen(context)); + } + if (fl & SSL_FLAG_NO_SESSION_REUSE) { + SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF); + } + if (Config.SSL.unclean_shutdown) { + debug(83, 5) ("Enabling quiet SSL shutdowns (RFC violation).\n"); + SSL_CTX_set_quiet_shutdown(sslContext, 1); + } if (cipher) { debug(83, 5) ("Using chiper suite %s.\n", cipher); if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { ssl_error = ERR_get_error(); - fatalf("Failed to set SSL cipher suite: %s\n", - ERR_error_string(ssl_error, NULL)); + fatalf("Failed to set SSL cipher suite '%s': %s\n", + cipher, ERR_error_string(ssl_error, NULL)); } } debug(83, 1) ("Using certificate in %s\n", certfile); - if (!SSL_CTX_use_certificate_file(sslContext, certfile, SSL_FILETYPE_PEM)) { + if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) { ssl_error = ERR_get_error(); - fatalf("Failed to acquire SSL certificate: %s\n", - ERR_error_string(ssl_error, NULL)); + debug(83, 0) ("Failed to acquire SSL certificate '%s': %s\n", + certfile, ERR_error_string(ssl_error, NULL)); + goto error; } debug(83, 1) ("Using private key in %s\n", keyfile); + ssl_ask_password(sslContext, keyfile); if (!SSL_CTX_use_PrivateKey_file(sslContext, keyfile, SSL_FILETYPE_PEM)) { ssl_error = ERR_get_error(); - fatalf("Failed to acquire SSL private key: %s\n", - ERR_error_string(ssl_error, NULL)); + debug(83, 0) ("Failed to acquire SSL private key '%s': %s\n", + keyfile, ERR_error_string(ssl_error, NULL)); + goto error; } debug(83, 5) ("Comparing private and public SSL keys.\n"); - if (!SSL_CTX_check_private_key(sslContext)) - fatal("SSL private key does not match public key: %s\n"); + if (!SSL_CTX_check_private_key(sslContext)) { + ssl_error = ERR_get_error(); + debug(83, 0) ("SSL private key '%s' does not match public key '%s': %s\n", + certfile, keyfile, ERR_error_string(ssl_error, NULL)); + goto error; + } + debug(83, 9) ("Setting RSA key generation callback.\n"); + SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb); + + debug(83, 9) ("Setting CA certificate locations.\n"); + if ((CAfile || CApath) && (!SSL_CTX_load_verify_locations(sslContext, CAfile, CApath))) { + ssl_error = ERR_get_error(); + debug(83, 1) ("Error error setting CA certificate locations: %s\n", + ERR_error_string(ssl_error, NULL)); + debug(83, 1) ("continuing anyway...\n"); + } + if (!(fl & SSL_FLAG_NO_DEFAULT_CA) && + !SSL_CTX_set_default_verify_paths(sslContext)) { + ssl_error = ERR_get_error(); + debug(83, 1) ("Error error setting default CA certificate location: %s\n", + ERR_error_string(ssl_error, NULL)); + debug(83, 1) ("continuing anyway...\n"); + } + if (clientCA) { + STACK_OF(X509_NAME) * cert_names; + debug(83, 9) ("Set client certifying authority list.\n"); + cert_names = SSL_load_client_CA_file(clientCA); + if (cert_names == NULL) { + debug(83, 1) ("Error loading the client CA certificates from '%s\': %s\n", clientCA, ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + ERR_clear_error(); + SSL_CTX_set_client_CA_list(sslContext, cert_names); + if (fl & SSL_FLAG_DELAYED_AUTH) { + debug(83, 9) ("Not requesting client certificates until acl processing requires one\n"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); + } else { + debug(83, 9) ("Requiring client certificates.\n"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb); + } + if (CRLfile) { + ssl_load_crl(sslContext, CRLfile); + fl |= SSL_FLAG_VERIFY_CRL; + } +#ifdef X509_V_FLAG_CRL_CHECK + if (fl & SSL_FLAG_VERIFY_CRL_ALL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + else if (fl & SSL_FLAG_VERIFY_CRL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK); +#endif + } else { + debug(83, 9) ("Not requiring any client certificates\n"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); + } + if (dhfile) { + FILE *in = fopen(dhfile, "r"); + DH *dh = NULL; + int codes; + if (in) { + dh = PEM_read_DHparams(in, NULL, NULL, NULL); + fclose(in); + } + if (!dh) + debug(83, 1) ("WARNING: Failed to read DH parameters '%s'\n", dhfile); + else if (dh && DH_check(dh, &codes) == 0) { + if (codes) { + debug(83, 1) ("WARNING: Failed to verify DH parameters '%s' (%x)\n", dhfile, codes); + DH_free(dh); + dh = NULL; + } + } + if (dh) + SSL_CTX_set_tmp_dh(sslContext, dh); + } + return sslContext; + error: + SSL_CTX_free(sslContext); + return NULL; +} + +SSL_CTX * +sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) +{ + int ssl_error; + SSL_METHOD *method; + SSL_CTX *sslContext; + long fl = ssl_parse_flags(flags); + + ssl_initialize(); + if (!keyfile) + keyfile = certfile; + if (!certfile) + certfile = keyfile; + + ERR_clear_error(); + debug(83, 1) ("Initialising SSL.\n"); + switch (version) { + case 2: + debug(83, 5) ("Using SSLv2.\n"); + method = SSLv2_client_method(); + break; + case 3: + debug(83, 5) ("Using SSLv3.\n"); + method = SSLv3_client_method(); + break; + case 4: + debug(83, 5) ("Using TLSv1.\n"); + method = TLSv1_client_method(); + break; + case 1: + default: + debug(83, 5) ("Using SSLv2/SSLv3.\n"); + method = SSLv23_client_method(); + break; + } + + sslContext = SSL_CTX_new(method); + if (sslContext == NULL) { + ssl_error = ERR_get_error(); + fatalf("Failed to allocate SSL context: %s\n", + ERR_error_string(ssl_error, NULL)); + } + SSL_CTX_set_options(sslContext, ssl_parse_options(options)); + + if (cipher) { + debug(83, 5) ("Using chiper suite %s.\n", cipher); + if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { + ssl_error = ERR_get_error(); + fatalf("Failed to set SSL cipher suite '%s': %s\n", + cipher, ERR_error_string(ssl_error, NULL)); + } + } + if (certfile) { + debug(83, 1) ("Using certificate in %s\n", certfile); + if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) { + ssl_error = ERR_get_error(); + fatalf("Failed to acquire SSL certificate '%s': %s\n", + certfile, ERR_error_string(ssl_error, NULL)); + } + debug(83, 1) ("Using private key in %s\n", keyfile); + ssl_ask_password(sslContext, keyfile); + if (!SSL_CTX_use_PrivateKey_file(sslContext, keyfile, SSL_FILETYPE_PEM)) { + ssl_error = ERR_get_error(); + fatalf("Failed to acquire SSL private key '%s': %s\n", + keyfile, ERR_error_string(ssl_error, NULL)); + } + debug(83, 5) ("Comparing private and public SSL keys.\n"); + if (!SSL_CTX_check_private_key(sslContext)) { + ssl_error = ERR_get_error(); + fatalf("SSL private key '%s' does not match public key '%s': %s\n", + certfile, keyfile, ERR_error_string(ssl_error, NULL)); + } + } debug(83, 9) ("Setting RSA key generation callback.\n"); SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb); - debug(83, 9) ("Setting certificate verification callback.\n"); - SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, ssl_verify_cb); + if (fl & SSL_FLAG_DONT_VERIFY_PEER) { + debug(83, 1) ("NOTICE: Peer certificates are not verified for validity!\n"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); + } else { + debug(83, 9) ("Setting certificate verification callback.\n"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb); + } - debug(83, 9) ("Setting default CA certificate location.\n"); - if (!SSL_CTX_set_default_verify_paths(sslContext)) { + debug(83, 9) ("Setting CA certificate locations.\n"); + if ((CAfile || CApath) && (!SSL_CTX_load_verify_locations(sslContext, CAfile, CApath))) { + ssl_error = ERR_get_error(); + debug(83, 1) ("Error error setting CA certificate locations: %s\n", + ERR_error_string(ssl_error, NULL)); + debug(83, 1) ("continuing anyway...\n"); + } + if (!(fl & SSL_FLAG_NO_DEFAULT_CA) && + !SSL_CTX_set_default_verify_paths(sslContext)) { ssl_error = ERR_get_error(); debug(83, 1) ("Error error setting default CA certificate location: %s\n", ERR_error_string(ssl_error, NULL)); debug(83, 1) ("continuing anyway...\n"); } - debug(83, 9) ("Set client certifying authority list.\n"); - SSL_CTX_set_client_CA_list(sslContext, SSL_load_client_CA_file(certfile)); + if (CRLfile) { + ssl_load_crl(sslContext, CRLfile); + fl |= SSL_FLAG_VERIFY_CRL; + } +#ifdef X509_V_FLAG_CRL_CHECK + if (fl & SSL_FLAG_VERIFY_CRL_ALL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + else if (fl & SSL_FLAG_VERIFY_CRL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK); +#endif return sslContext; } @@ -366,16 +696,53 @@ char *buf; int len; { + SSL *ssl = fd_table[fd].ssl; int i; - i = SSL_read(fd_table[fd].ssl, buf, len); +#if DONT_DO_THIS + if (!SSL_is_init_finished(ssl)) { + errno = ENOTCONN; + return -1; + } +#endif - if (i > 0 && SSL_pending(fd_table[fd].ssl) > 0) { - debug(83, 2) ("SSL fd %d is pending\n", fd); - fd_table[fd].flags.read_pending = 1; - } else - fd_table[fd].flags.read_pending = 0; + errno = 0; + ERR_clear_error(); + i = SSL_read(ssl, buf, len); + + fd_table[fd].read_pending = COMM_PENDING_NOW; + if (i > 0 && SSL_pending(ssl) > 0) { + debug(83, 3) ("SSL fd %d is pending\n", fd); + } else if (i <= 0) { + int err = SSL_get_error(ssl, i); + switch (err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + i = 0; + break; + case SSL_ERROR_WANT_READ: + fd_table[fd].read_pending = COMM_PENDING_WANTS_READ; + i = -1; + errno = EAGAIN; + break; + case SSL_ERROR_WANT_WRITE: + fd_table[fd].read_pending = COMM_PENDING_WANTS_WRITE; + i = -1; + errno = EAGAIN; + break; + case SSL_ERROR_SYSCALL: + if (i == 0) + break; + if (errno == ECONNRESET) + break; + debug(83, 2) ("SSL fd %d read error %s (%d)\n", fd, strerror(errno), errno); + break; + default: + debug(83, 2) ("SSL fd %d read error %s (%d/%d)\n", fd, ERR_error_string(ERR_get_error(), NULL), i, err); + break; + } + } return i; } @@ -385,19 +752,321 @@ const char *buf; int len; { - return (SSL_write(fd_table[fd].ssl, buf, len)); + SSL *ssl = fd_table[fd].ssl; + int i; + + if (!SSL_is_init_finished(ssl)) { + errno = ENOTCONN; + return -1; + } + errno = 0; + ERR_clear_error(); + i = SSL_write(ssl, buf, len); + + if (i <= 0) { + int err = SSL_get_error(ssl, i); + switch (err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + i = 0; + break; + case SSL_ERROR_WANT_READ: + fd_table[fd].write_pending = COMM_PENDING_WANTS_READ; + i = -1; + errno = EAGAIN; + break; + case SSL_ERROR_WANT_WRITE: + fd_table[fd].write_pending = COMM_PENDING_WANTS_WRITE; + i = -1; + errno = EAGAIN; + break; + case SSL_ERROR_SYSCALL: + if (i == 0) + break; + if (errno == ECONNRESET) + break; + debug(83, 2) ("SSL fd %d write error %s (%d)\n", fd, strerror(errno), errno); + break; + + default: + debug(83, 2) ("SSL fd %d write error %s (%d/%d)\n", fd, ERR_error_string(ERR_get_error(), NULL), i, err); + i = -1; + break; + } + } + return i; } -void +int ssl_shutdown_method(fd) { SSL *ssl = fd_table[fd].ssl; - if (!fd_table[fd].ssl_shutdown) { - fd_table[fd].ssl_shutdown = 1; - if (Config.SSL.unclean_shutdown) - SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); - else - SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); + int ret; + + if (!SSL_is_init_finished(ssl)) { + errno = ENOTCONN; + return 0; + } + ERR_clear_error(); + ret = SSL_shutdown(ssl); + if (ret <= 0) { + int err = SSL_get_error(ssl, ret); + switch (err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + return 1; + break; + case SSL_ERROR_WANT_READ: + fd_table[fd].write_pending = COMM_PENDING_WANTS_READ; + errno = EAGAIN; + return -1; + break; + case SSL_ERROR_WANT_WRITE: + fd_table[fd].write_pending = COMM_PENDING_WANTS_WRITE; + errno = EAGAIN; + return -1; + break; + case SSL_ERROR_SYSCALL: + if (errno == EAGAIN || errno == 0) { + errno = EAGAIN; + return -1; + break; + } + default: + debug(83, 2) ("WARNING: Unexpected error on SSL_shutdown '%d' (%d)\n", err, errno); + return -1; + break; + } + } + return ret; +} + +static const char * +ssl_get_attribute(X509_NAME * name, const char *attribute_name) +{ + static char buffer[1024]; + int nid; + + buffer[0] = '\0'; + + if (strcmp(attribute_name, "DN") == 0) { + X509_NAME_oneline(name, buffer, sizeof(buffer)); + goto done; } - SSL_shutdown(ssl); + nid = OBJ_txt2nid((char *) attribute_name); + if (nid == 0) { + debug(83, 1) ("WARNING: Unknown SSL attribute name '%s'\n", attribute_name); + return NULL; + } + X509_NAME_get_text_by_NID(name, nid, buffer, sizeof(buffer)); + done: + return *buffer ? buffer : NULL; } + +const char * +sslGetUserAttribute(SSL * ssl, const char *attribute_name) +{ + X509 *cert; + X509_NAME *name; + const char *ret; + + if (!ssl) + return NULL; + + cert = SSL_get_peer_certificate(ssl); + if (!cert) + return NULL; + + name = X509_get_subject_name(cert); + + ret = ssl_get_attribute(name, attribute_name); + + X509_free(cert); + CRYPTO_free(name); + + return ret; +} + +const char * +sslGetCAAttribute(SSL * ssl, const char *attribute_name) +{ + X509 *cert; + X509_NAME *name; + const char *ret; + + if (!ssl) + return NULL; + + cert = SSL_get_peer_certificate(ssl); + if (!cert) + return NULL; + + name = X509_get_issuer_name(cert); + + ret = ssl_get_attribute(name, attribute_name); + + X509_free(cert); + CRYPTO_free(name); + + return ret; +} + +#if 0 +char * +sslGetUserEmail(SSL * ssl) +{ + X509 *cert; + X509_NAME *name; + + static char email[128]; + + if (!ssl) + return NULL; + cert = SSL_get_peer_certificate(ssl); + if (!cert) + return NULL; + + name = X509_get_subject_name(cert); + + if (X509_NAME_get_text_by_NID(name, NID_pkcs9_emailAddress, email, sizeof(email)) > 0) + return email; + else + return NULL; +} +#endif + +const char * +sslGetUserEmail(SSL * ssl) +{ + return sslGetUserAttribute(ssl, "Email"); +} + +const char * +sslGetUserCertificatePEM(SSL * ssl) +{ + X509 *cert; + BIO *mem; + static char *str = NULL; + char *ptr; + long len; + + safe_free(str); + + if (!ssl) + return NULL; + + cert = SSL_get_peer_certificate(ssl); + + if (!cert) + return NULL; + + mem = BIO_new(BIO_s_mem()); + + PEM_write_bio_X509(mem, cert); + + + len = BIO_get_mem_data(mem, &ptr); + + str = (char *) xmalloc(len + 1); + memcpy(str, ptr, len); + str[len] = '\0'; + + X509_free(cert); + BIO_free(mem); + + return str; +} + +const char * +sslGetUserCertificateChainPEM(SSL * ssl) +{ + STACK_OF(X509) * chain; + BIO *mem; + static char *str = NULL; + char *ptr; + long len; + int i; + + safe_free(str); + + if (!ssl) + return NULL; + + chain = SSL_get_peer_cert_chain(ssl); + + if (!chain) + return sslGetUserCertificatePEM(ssl); + + mem = BIO_new(BIO_s_mem()); + + for (i = 0; i < sk_X509_num(chain); i++) { + X509 *cert = sk_X509_value(chain, i); + PEM_write_bio_X509(mem, cert); + } + + len = BIO_get_mem_data(mem, &ptr); + + str = (char *) xmalloc(len + 1); + memcpy(str, ptr, len); + str[len] = '\0'; + + BIO_free(mem); + + return str; +} + +#if NOT_YET +int +ssl_verify_domain(const char *host, SSL * ssl) +{ + int i; + int found = 0; + char name[1024]; + STACK_OF(GENERAL_NAME) * altnames; + altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); + if (altnames) { + int numalts = sk_GENERAL_NAME_num(altnames); + debug(83, 3) ("Verifying server domain %s to certificate subjectAltName\n", host); + for (i = 0; i < numalts; i++) { + const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i); + if (check->type != GEN_DNS) + continue; + ASN1_STRING *data = check->dNSName; + if (data->length > sizeof(name) - 1) + continue; + memcpy(name, data->data, data->length); + name[data->length] = '\0'; + debug(83, 4) ("Verifying server domain %s to certificate name %s\n", server, name); + if (matchDomainName(server, name[0] == '*' ? name + 1 : name) == 0) { + found = 1; + break; + } else { + found = -1; + } + } + } + if (found == 0) { + X509_NAME *name = X509_get_subject_name(server_cert); + debug(83, 3) ("Verifying server domain %s to certificate cn\n", host); + for (i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) { + ASN1_STRING *data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); + if (data->length > sizeof(name) - 1) + continue; + memcpy(name, data->data, data->length); + name[data->length] = '\0'; + debug(83, 4) ("Verifying server domain %s to certificate cn %s\n", + server, name); + if (matchDomainName(server, name[0] == '*' ? name + 1 : name) == 0) { + found = 1; + break; + } + } + } + if (found) { + return 1; + } else { + debug(83, 2) ("ERROR: Certificate does not match domainname %s\n", host); + return 0; + } +} +#endif Index: squid/src/ssl_support.h diff -u squid/src/ssl_support.h:1.5 squid/src/ssl_support.h:1.5.44.7 --- squid/src/ssl_support.h:1.5 Fri Oct 19 15:48:48 2001 +++ squid/src/ssl_support.h Mon May 15 04:53:42 2006 @@ -42,10 +42,21 @@ #if HAVE_OPENSSL_ERR_H #include #endif +#if HAVE_OPENSSL_ENGINE_H +#include +#endif -SSL_CTX *sslCreateContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options); +SSL_CTX *sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhpath, const char *context); +SSL_CTX *sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile); int ssl_read_method(int, char *, int); int ssl_write_method(int, const char *, int); -void ssl_shutdown_method(int); +int ssl_shutdown_method(int); +int ssl_verify_domain(const char *host, SSL *); + +const char *sslGetUserEmail(SSL * ssl); +const char *sslGetUserAttribute(SSL * ssl, const char *attribute); +const char *sslGetCAAttribute(SSL * ssl, const char *attribute); +const char *sslGetUserCertificatePEM(SSL * ssl); +const char *sslGetUserCertificateChainPEM(SSL * ssl); #endif /* SQUID_SSL_SUPPORT_H */ Index: squid/src/structs.h diff -u squid/src/structs.h:1.48.2.46 squid/src/structs.h:1.48.2.8.2.32 --- squid/src/structs.h:1.48.2.46 Fri Mar 10 19:16:31 2006 +++ squid/src/structs.h Mon May 15 16:41:11 2006 @@ -48,6 +48,14 @@ dlink_node *tail; }; +#if USE_SSL +struct _acl_cert_data { + splayNode *values; + char *attribute; +}; + +#endif + struct _acl_user_data { splayNode *names; struct { @@ -350,6 +358,14 @@ int version; char *cipher; char *options; + char *clientca; + char *cafile; + char *capath; + char *crlfile; + char *dhfile; + char *sslflags; + char *sslcontext; + SSL_CTX *sslContext; }; #endif @@ -496,6 +512,9 @@ char *unlinkd; #endif char *diskd; +#if USE_SSL + char *ssl_password; +#endif } Program; #if USE_DNSSERVERS int dnsChildren; @@ -704,6 +723,7 @@ #if USE_SSL struct { int unclean_shutdown; + char *ssl_engine; } SSL; #endif wordlist *ext_methods; @@ -715,6 +735,20 @@ char *store_dir_select_algorithm; int sleep_after_fork; /* microseconds */ external_acl *externalAclHelperList; +#if USE_SSL + struct { + char *cert; + char *key; + int version; + char *options; + char *cipher; + char *cafile; + char *capath; + char *crlfile; + char *flags; + SSL_CTX *sslContext; + } ssl_client; +#endif }; struct _SquidConfig2 { @@ -784,8 +818,9 @@ unsigned int called_connect:1; unsigned int nodelay:1; unsigned int close_on_exec:1; - unsigned int read_pending:1; } flags; + comm_pending read_pending; + comm_pending write_pending; squid_off_t bytes_read; squid_off_t bytes_written; int uses; /* ie # req's over persistent conn */ @@ -812,7 +847,6 @@ WRITE_HANDLER *write_method; #if USE_SSL SSL *ssl; - int ssl_shutdown:1; #endif }; @@ -981,6 +1015,7 @@ unsigned int keepalive_broken:1; unsigned int abuse_detected:1; unsigned int request_sent:1; + unsigned int front_end_https:2; }; struct _HttpStateData { @@ -1052,6 +1087,9 @@ int msec; const char *rfc931; const char *authuser; +#if USE_SSL + const char *ssluser; +#endif } cache; struct { char *request; @@ -1327,6 +1365,22 @@ char *login; /* Proxy authorization */ time_t connect_timeout; int max_conn; +#if USE_SSL + int use_ssl; + char *sslcert; + char *sslkey; + int sslversion; + char *ssloptions; + char *sslcipher; + char *sslcafile; + char *sslcapath; + char *sslcrlfile; + char *sslflags; + char *ssldomain; + SSL_CTX *sslContext; + SSL_SESSION *sslSession; +#endif + int front_end_https; }; struct _net_db_name { Index: squid/src/typedefs.h diff -u squid/src/typedefs.h:1.25.6.8 squid/src/typedefs.h:1.25.6.1.2.7 --- squid/src/typedefs.h:1.25.6.8 Sat Mar 26 18:16:17 2005 +++ squid/src/typedefs.h Thu Apr 14 18:14:19 2005 @@ -89,6 +89,9 @@ typedef struct _acl_hdr_data acl_hdr_data; typedef struct _authscheme_entry authscheme_entry_t; typedef struct _authScheme authScheme; +#if USE_SSL +typedef struct _acl_cert_data acl_cert_data; +#endif typedef struct _acl_user_data acl_user_data; typedef struct _acl_user_ip_data acl_user_ip_data; typedef struct _acl_arp_data acl_arp_data; @@ -366,7 +369,7 @@ typedef int HttpHeaderPos; /* big mask for http headers */ -typedef char HttpHeaderMask[8]; +typedef char HttpHeaderMask[(HDR_ENUM_END + 7) / 8]; /* a common objPackInto interface; used by debugObj */ typedef void (*ObjPackMethod) (void *obj, Packer * p);