long ssl_io_data_cb(BIO *bio, int cmd, const char *argp, int argi, long argl, long rc) { SSL *ssl; conn_rec *c; server_rec *s; if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL) return rc; if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL) return rc; s = c->server; if ( cmd == (BIO_CB_WRITE|BIO_CB_RETURN) || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) { if (rc >= 0) { ssl_log(s, SSL_LOG_DEBUG, "%s: %s %ld/%d bytes %s BIO#%08lX [mem: %08lX] %s", SSL_LIBRARY_NAME, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"), rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"), (long)bio, (long)argp, (argp != NULL ? "(BIO dump follows)" : "(Ops, no memory buffer?)")); if (argp != NULL) ssl_io_data_dump(s, argp, rc); } else { ssl_log(s, SSL_LOG_DEBUG, "%s: I/O error, %d bytes expected to %s on BIO#%08lX [mem: %08lX]", SSL_LIBRARY_NAME, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"), (long)bio, (long)argp); } } return rc; }
void ssl_scache_shmht_init(server_rec *s, pool *p) { SSLModConfigRec *mc = myModConfig(); AP_MM *mm; table_t *ta; int ta_errno; int avail; int n; /* * Create shared memory segment */ if (mc->szSessionCacheDataFile == NULL) { ssl_log(s, SSL_LOG_ERROR, "SSLSessionCache required"); ssl_die(); } if ((mm = ap_mm_create(mc->nSessionCacheDataSize, mc->szSessionCacheDataFile)) == NULL) { ssl_log(s, SSL_LOG_ERROR, "Cannot allocate shared memory: %s", ap_mm_error()); ssl_die(); } mc->pSessionCacheDataMM = mm; /* * Make sure the childs have access to the underlaying files */ ap_mm_permission(mm, SSL_MM_FILE_MODE, ap_user_id, -1); /* * Create hash table in shared memory segment */ avail = ap_mm_available(mm); n = (avail/2) / 1024; n = n < 10 ? 10 : n; if ((ta = table_alloc(n, &ta_errno, ssl_scache_shmht_malloc, ssl_scache_shmht_calloc, ssl_scache_shmht_realloc, ssl_scache_shmht_free )) == NULL) { ssl_log(s, SSL_LOG_ERROR, "Cannot allocate hash table in shared memory: %s", table_strerror(ta_errno)); ssl_die(); } table_attr(ta, TABLE_FLAG_AUTO_ADJUST|TABLE_FLAG_ADJUST_DOWN); table_set_data_alignment(ta, sizeof(char *)); table_clear(ta); mc->tSessionCacheDataTable = ta; /* * Log the done work */ ssl_log(s, SSL_LOG_INFO, "Init: Created hash-table (%d buckets) " "in shared memory (%d bytes) for SSL session cache", n, avail); return; }
/* * Open the SSL logfile */ void ssl_log_open(server_rec *s_main, server_rec *s, pool *p) { char *szLogFile; SSLSrvConfigRec *sc_main = mySrvConfig(s_main); SSLSrvConfigRec *sc = mySrvConfig(s); piped_log *pl; char *cp; /* * Short-circuit for inherited logfiles in order to save * filedescriptors in mass-vhost situation. Be careful, this works * fine because the close happens implicitly by the pool facility. */ if ( s != s_main && sc_main->fileLogFile != NULL && ( (sc->szLogFile == NULL) || ( sc->szLogFile != NULL && sc_main->szLogFile != NULL && strEQ(sc->szLogFile, sc_main->szLogFile)))) { sc->fileLogFile = sc_main->fileLogFile; } else if (sc->szLogFile != NULL) { if (strEQ(sc->szLogFile, "/dev/null")) return; else if (sc->szLogFile[0] == '|') { cp = sc->szLogFile+1; while (*cp == ' ' || *cp == '\t') cp++; szLogFile = ssl_util_server_root_relative(p, "log", cp); if ((pl = ap_open_piped_log(p, szLogFile)) == NULL) { ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO, "Cannot open reliable pipe to SSL logfile filter %s", szLogFile); ssl_die(); } sc->fileLogFile = ap_pfdopen(p, ap_piped_log_write_fd(pl), "a"); setbuf(sc->fileLogFile, NULL); } else { szLogFile = ssl_util_server_root_relative(p, "log", sc->szLogFile); if ((sc->fileLogFile = ap_pfopen(p, szLogFile, "a")) == NULL) { ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO, "Cannot open SSL logfile %s", szLogFile); ssl_die(); } setbuf(sc->fileLogFile, NULL); } } return; }
static int ssl_io_hook_writev(BUFF *fb, const struct iovec *iov, int iovcnt) { SSL *ssl; conn_rec *c; int rc; if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL) { rc = SSL_writev(ssl, iov, iovcnt); /* * Simulate an EINTR in case OpenSSL wants to write more. */ if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_WANT_WRITE) errno = EINTR; /* * Log SSL errors */ if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_SSL) { c = (conn_rec *)SSL_get_app_data(ssl); ssl_log(c->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, "SSL error on writing data"); } /* * writev(2) returns only the generic error number -1 */ if (rc < 0) rc = -1; } else rc = writev(fb->fd, iov, iovcnt); return rc; }
static int ssl_io_hook_read(BUFF *fb, char *buf, int len) { SSL *ssl; conn_rec *c; int rc; if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL) { rc = SSL_read(ssl, buf, len); /* * Simulate an EINTR in case OpenSSL wants to read more. * (This is usually the case when the client forces an SSL * renegotation which is handled implicitly by OpenSSL.) */ if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ) errno = EINTR; /* * Log SSL errors */ if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_SSL) { c = (conn_rec *)SSL_get_app_data(ssl); ssl_log(c->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, "SSL error on reading data"); } /* * read(2) returns only the generic error number -1 */ if (rc < 0) rc = -1; } else rc = read(fb->fd_in, buf, len); return rc; }
/* the SSL_read replacement routine which knows about the suck buffer */ static int ssl_io_suck_read(SSL *ssl, char *buf, int len) { ap_ctx *actx; struct ssl_io_suck_st *ss; request_rec *r = NULL; int rv; actx = (ap_ctx *)SSL_get_app_data2(ssl); if (actx != NULL) r = (request_rec *)ap_ctx_get(actx, "ssl::request_rec"); rv = -1; if (r != NULL && r->ctx != NULL) { ss = ap_ctx_get(r->ctx, "ssl::io::suck"); if (ss != NULL) { if (ss->active && ss->pendlen > 0) { /* ok, there is pre-sucked data */ len = (ss->pendlen > len ? len : ss->pendlen); memcpy(buf, ss->pendptr, len); ss->pendptr += len; ss->pendlen -= len; ssl_log(r->server, SSL_LOG_TRACE, "I/O: injecting %d bytes of pre-sucked data " "into Apache I/O layer", len); rv = len; } } } if (rv == -1) rv = SSL_read(ssl, buf, len); return rv; }
static void ssl_io_data_dump(server_rec *srvr, const char *s, long len) { char buf[256]; char tmp[64]; int i, j, rows, trunc; unsigned char ch; trunc = 0; for(; (len > 0) && ((s[len-1] == ' ') || (s[len-1] == '\0')); len--) trunc++; rows = (len / DUMP_WIDTH); if ((rows * DUMP_WIDTH) < len) rows++; ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "+-------------------------------------------------------------------------+"); for (i = 0 ; i< rows; i++) { ap_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH); ap_cpystrn(buf, tmp, sizeof(buf)); for (j = 0; j < DUMP_WIDTH; j++) { if (((i * DUMP_WIDTH) + j) >= len) ap_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); else { ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff; ap_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' '); ap_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf)); } } ap_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); for (j = 0; j < DUMP_WIDTH; j++) { if (((i * DUMP_WIDTH) + j) >= len) ap_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); else { ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff; ap_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.'); ap_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf)); } } ap_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf)); ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "%s", buf); } if (trunc > 0) ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "| %04lx - <SPACES/NULS>", len + trunc); ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "+-------------------------------------------------------------------------+"); return; }
void ssl_scache_shmht_expire(server_rec *s) { SSLModConfigRec *mc = myModConfig(); SSLSrvConfigRec *sc = mySrvConfig(s); static time_t tLast = 0; table_linear_t iterator; time_t tExpiresAt; void *vpKey; void *vpKeyThis; void *vpData; int nKey; int nKeyThis; int nData; int nElements = 0; int nDeleted = 0; int bDelete; int rc; time_t tNow; /* * make sure the expiration for still not-accessed session * cache entries is done only from time to time */ tNow = time(NULL); if (tNow < tLast+sc->nSessionCacheTimeout) return; tLast = tNow; ssl_mutex_on(s); if (table_first_r(mc->tSessionCacheDataTable, &iterator, &vpKey, &nKey, &vpData, &nData) == TABLE_ERROR_NONE) { do { bDelete = FALSE; nElements++; if (nData < sizeof(time_t) || vpData == NULL) bDelete = TRUE; else { memcpy(&tExpiresAt, vpData, sizeof(time_t)); if (tExpiresAt <= tNow) bDelete = TRUE; } vpKeyThis = vpKey; nKeyThis = nKey; rc = table_next_r(mc->tSessionCacheDataTable, &iterator, &vpKey, &nKey, &vpData, &nData); if (bDelete) { table_delete(mc->tSessionCacheDataTable, vpKeyThis, nKeyThis, NULL, NULL); nDeleted++; } } while (rc == TABLE_ERROR_NONE); } ssl_mutex_off(s); ssl_log(s, SSL_LOG_TRACE, "Inter-Process Session Cache (SHMHT) Expiry: " "old: %d, new: %d, removed: %d", nElements, nElements-nDeleted, nDeleted); return; }
static void ssl_log_flush(pn_transport_t* transport) { char buf[128]; // see "man ERR_error_string_n()" unsigned long err = ERR_get_error(); while (err) { ERR_error_string_n(err, buf, sizeof(buf)); ssl_log(transport, "%s", buf); err = ERR_get_error(); } }
void pn_ssl_free(pn_transport_t *transport) { pni_ssl_t *ssl = transport->ssl; if (!ssl) return; ssl_log(transport, "SSL socket freed." ); release_ssl_socket( ssl ); if (ssl->domain) pn_ssl_domain_free(ssl->domain); if (ssl->session_id) free((void *)ssl->session_id); if (ssl->peer_hostname) free((void *)ssl->peer_hostname); if (ssl->inbuf) free((void *)ssl->inbuf); if (ssl->outbuf) free((void *)ssl->outbuf); if (ssl->subject) free(ssl->subject); free(ssl); }
static int start_ssl_shutdown(pn_transport_t *transport) { pni_ssl_t *ssl = transport->ssl; if (!ssl->ssl_shutdown) { ssl_log(transport, "Shutting down SSL connection..."); if (ssl->session_id) { // save the negotiated credentials before we close the connection pn_ssl_session_t *ssn = (pn_ssl_session_t *)calloc( 1, sizeof(pn_ssl_session_t)); if (ssn) { ssn->id = pn_strdup( ssl->session_id ); ssn->session = SSL_get1_session( ssl->ssl ); if (ssn->session) { ssl_log(transport, "Saving SSL session as %s", ssl->session_id ); LL_ADD( ssl->domain, ssn_cache, ssn ); } else { ssl_session_free( ssn ); } } } ssl->ssl_shutdown = true; BIO_ssl_shutdown( ssl->bio_ssl ); } return 0; }
void ssl_io_suck(request_rec *r, SSL *ssl) { int rc; int len; char *buf; int buflen; char c; int sucked; if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) == OK) { if (ap_should_client_block(r)) { /* read client request block through Apache API */ buflen = HUGE_STRING_LEN; buf = ap_palloc(r->pool, buflen); ap_hard_timeout("SSL I/O request body pre-sucking", r); sucked = 0; ssl_io_suck_start(r); while ((len = ap_get_client_block(r, buf, buflen)) > 0) { ssl_io_suck_record(r, buf, len); sucked += len; ap_reset_timeout(r); } ssl_io_suck_end(r); ap_kill_timeout(r); /* suck trailing data (usually CR LF) which is still in the Apache BUFF layer */ ap_hard_timeout("SSL I/O request trailing data pre-sucking", r); while (ap_bpeekc(r->connection->client) != EOF) { c = ap_bgetc(r->connection->client); ssl_io_suck_record(r, &c, 1); sucked++; } ap_kill_timeout(r); ssl_log(r->server, SSL_LOG_TRACE, "I/O: sucked %d bytes of input data from SSL/TLS I/O layer " "for delayed injection into Apache I/O layer", sucked); } } return; }
// take data from the network, and pass it into SSL. Attempt to read decrypted data from // SSL socket and pass it to the application. static ssize_t process_input_ssl( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t available) { pni_ssl_t *ssl = transport->ssl; if (ssl->ssl == NULL && init_ssl_socket(transport, ssl)) return PN_EOS; ssl_log( transport, "process_input_ssl( data size=%d )",available ); ssize_t consumed = 0; bool work_pending; bool shutdown_input = (available == 0); // caller is closed do { work_pending = false; // Write to network bio as much as possible, consuming bytes/available if (available > 0) { int written = BIO_write( ssl->bio_net_io, input_data, available ); if (written > 0) { input_data += written; available -= written; consumed += written; ssl->read_blocked = false; work_pending = (available > 0); ssl_log( transport, "Wrote %d bytes to BIO Layer, %d left over", written, available ); } } else if (shutdown_input) { // lower layer (caller) has closed. Close the WRITE side of the BIO. This will cause // an EOF to be passed to SSL once all pending inbound data has been consumed. ssl_log( transport, "Lower layer closed - shutting down BIO write side"); (void)BIO_shutdown_wr( ssl->bio_net_io ); shutdown_input = false; } // Read all available data from the SSL socket if (!ssl->ssl_closed && ssl->in_count < ssl->in_size) { int read = BIO_read( ssl->bio_ssl, &ssl->inbuf[ssl->in_count], ssl->in_size - ssl->in_count ); if (read > 0) { ssl_log( transport, "Read %d bytes from SSL socket for app", read ); ssl_log_clear_data(transport, &ssl->inbuf[ssl->in_count], read ); ssl->in_count += read; work_pending = true; } else { if (!BIO_should_retry(ssl->bio_ssl)) { int reason = SSL_get_error( ssl->ssl, read ); switch (reason) { case SSL_ERROR_ZERO_RETURN: // SSL closed cleanly ssl_log(transport, "SSL connection has closed"); start_ssl_shutdown(transport); // KAG: not sure - this may not be necessary ssl->ssl_closed = true; break; default: // unexpected error return (ssize_t)ssl_failed(transport); } } else { if (BIO_should_write( ssl->bio_ssl )) { ssl->write_blocked = true; ssl_log(transport, "Detected write-blocked"); } if (BIO_should_read( ssl->bio_ssl )) { ssl->read_blocked = true; ssl_log(transport, "Detected read-blocked"); } } } } // write incoming data to app layer if (!ssl->app_input_closed) { if (ssl->in_count > 0 || ssl->ssl_closed) { /* if ssl_closed, send 0 count */ ssize_t consumed = transport->io_layers[layer+1]->process_input(transport, layer+1, ssl->inbuf, ssl->in_count); if (consumed > 0) { ssl->in_count -= consumed; if (ssl->in_count) memmove( ssl->inbuf, ssl->inbuf + consumed, ssl->in_count ); work_pending = true; ssl_log( transport, "Application consumed %d bytes from peer", (int) consumed ); } else if (consumed < 0) { ssl_log(transport, "Application layer closed its input, error=%d (discarding %d bytes)", (int) consumed, (int)ssl->in_count); ssl->in_count = 0; // discard any pending input ssl->app_input_closed = consumed; if (ssl->app_output_closed && ssl->out_count == 0) { // both sides of app closed, and no more app output pending: start_ssl_shutdown(transport); } } else { // app did not consume any bytes, must be waiting for a full frame if (ssl->in_count == ssl->in_size) { // but the buffer is full, not enough room for a full frame. // can we grow the buffer? uint32_t max_frame = pn_transport_get_max_frame(transport); if (!max_frame) max_frame = ssl->in_size * 2; // no limit if (ssl->in_size < max_frame) { // no max frame limit - grow it. size_t newsize = pn_min(max_frame, ssl->in_size * 2); char *newbuf = (char *)realloc( ssl->inbuf, newsize ); if (newbuf) { ssl->in_size = newsize; ssl->inbuf = newbuf; work_pending = true; // can we get more input? } } else { // can't gather any more input, but app needs more? // This is a bug - since SSL can buffer up to max-frame, // the application _must_ have enough data to process. If // this is an oversized frame, the app _must_ handle it // by returning an error code to SSL. pn_transport_log(transport, "Error: application unable to consume input."); } } } } } } while (work_pending); //_log(ssl, "ssl_closed=%d in_count=%d app_input_closed=%d app_output_closed=%d", // ssl->ssl_closed, ssl->in_count, ssl->app_input_closed, ssl->app_output_closed ); // PROTON-82: Instead, close the input side as soon as we've completed enough of the SSL // shutdown handshake to send the close_notify. We're not requiring the response, as // some implementations never reply. // --- // tell transport our input side is closed if the SSL socket cannot be read from any // longer, AND any pending input has been written up to the application (or the // application is closed) //if (ssl->ssl_closed && ssl->app_input_closed) { // consumed = ssl->app_input_closed; //} if (ssl->app_input_closed && (SSL_get_shutdown(ssl->ssl) & SSL_SENT_SHUTDOWN) ) { consumed = ssl->app_input_closed; if (transport->io_layers[layer]==&ssl_output_closed_layer) { transport->io_layers[layer] = &ssl_closed_layer; } else { transport->io_layers[layer] = &ssl_input_closed_layer; } } ssl_log(transport, "process_input_ssl() returning %d", (int) consumed); return consumed; }
// Certificate chain verification callback: return 1 if verified, // 0 if remote cannot be verified (fail handshake). // static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { if (!preverify_ok || X509_STORE_CTX_get_error_depth(ctx) != 0) // already failed, or not at peer cert in chain return preverify_ok; X509 *cert = X509_STORE_CTX_get_current_cert(ctx); SSL *ssn = (SSL *) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); if (!ssn) { pn_transport_logf(NULL, "Error: unexpected error - SSL session info not available for peer verify!"); return 0; // fail connection } pn_transport_t *transport = (pn_transport_t *)SSL_get_ex_data(ssn, ssl_ex_data_index); if (!transport) { pn_transport_logf(NULL, "Error: unexpected error - SSL context info not available for peer verify!"); return 0; // fail connection } pni_ssl_t *ssl = transport->ssl; if (ssl->domain->verify_mode != PN_SSL_VERIFY_PEER_NAME) return preverify_ok; if (!ssl->peer_hostname) { pn_transport_logf(transport, "Error: configuration error: PN_SSL_VERIFY_PEER_NAME configured, but no peer hostname set!"); return 0; // fail connection } ssl_log(transport, "Checking identifying name in peer cert against '%s'", ssl->peer_hostname); bool matched = false; /* first check any SubjectAltName entries, as per RFC2818 */ GENERAL_NAMES *sans = (GENERAL_NAMES *) X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (sans) { int name_ct = sk_GENERAL_NAME_num( sans ); int i; for (i = 0; !matched && i < name_ct; ++i) { GENERAL_NAME *name = sk_GENERAL_NAME_value( sans, i ); if (name->type == GEN_DNS) { ASN1_STRING *asn1 = name->d.dNSName; if (asn1 && asn1->data && asn1->length) { unsigned char *str; int len = ASN1_STRING_to_UTF8( &str, asn1 ); if (len >= 0) { ssl_log(transport, "SubjectAltName (dns) from peer cert = '%.*s'", len, str ); matched = match_dns_pattern( ssl->peer_hostname, (const char *)str, len ); OPENSSL_free( str ); } } } } GENERAL_NAMES_free( sans ); } /* if no general names match, try the CommonName from the subject */ X509_NAME *name = X509_get_subject_name(cert); int i = -1; while (!matched && (i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) { X509_NAME_ENTRY *ne = X509_NAME_get_entry(name, i); ASN1_STRING *name_asn1 = X509_NAME_ENTRY_get_data(ne); if (name_asn1) { unsigned char *str; int len = ASN1_STRING_to_UTF8( &str, name_asn1); if (len >= 0) { ssl_log(transport, "commonName from peer cert = '%.*s'", len, str); matched = match_dns_pattern( ssl->peer_hostname, (const char *)str, len ); OPENSSL_free(str); } } } if (!matched) { ssl_log(transport, "Error: no name matching %s found in peer cert - rejecting handshake.", ssl->peer_hostname); preverify_ok = 0; #ifdef X509_V_ERR_APPLICATION_VERIFICATION X509_STORE_CTX_set_error( ctx, X509_V_ERR_APPLICATION_VERIFICATION ); #endif } else { ssl_log(transport, "Name from peer cert matched - peer is valid."); } return preverify_ok; }
static int init_ssl_socket(pn_transport_t* transport, pni_ssl_t *ssl) { if (ssl->ssl) return 0; if (!ssl->domain) return -1; ssl->ssl = SSL_new(ssl->domain->ctx); if (!ssl->ssl) { pn_transport_logf(transport, "SSL socket setup failure." ); return -1; } // store backpointer to pn_transport_t in SSL object: SSL_set_ex_data(ssl->ssl, ssl_ex_data_index, transport); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (ssl->peer_hostname && ssl->domain->mode == PN_SSL_MODE_CLIENT) { SSL_set_tlsext_host_name(ssl->ssl, ssl->peer_hostname); } #endif // restore session, if available if (ssl->session_id) { pn_ssl_session_t *ssn = ssn_cache_find( ssl->domain, ssl->session_id ); if (ssn) { ssl_log( transport, "Restoring previous session id=%s", ssn->id ); int rc = SSL_set_session( ssl->ssl, ssn->session ); if (rc != 1) { ssl_log( transport, "Session restore failed, id=%s", ssn->id ); } LL_REMOVE( ssl->domain, ssn_cache, ssn ); ssl_session_free( ssn ); } } // now layer a BIO over the SSL socket ssl->bio_ssl = BIO_new(BIO_f_ssl()); if (!ssl->bio_ssl) { pn_transport_log(transport, "BIO setup failure." ); return -1; } (void)BIO_set_ssl(ssl->bio_ssl, ssl->ssl, BIO_NOCLOSE); // create the "lower" BIO "pipe", and attach it below the SSL layer if (!BIO_new_bio_pair(&ssl->bio_ssl_io, 0, &ssl->bio_net_io, 0)) { pn_transport_log(transport, "BIO setup failure." ); return -1; } SSL_set_bio(ssl->ssl, ssl->bio_ssl_io, ssl->bio_ssl_io); if (ssl->domain->mode == PN_SSL_MODE_SERVER) { SSL_set_accept_state(ssl->ssl); BIO_set_ssl_mode(ssl->bio_ssl, 0); // server mode ssl_log( transport, "Server SSL socket created." ); } else { // client mode SSL_set_connect_state(ssl->ssl); BIO_set_ssl_mode(ssl->bio_ssl, 1); // client mode ssl_log( transport, "Client SSL socket created." ); } ssl->subject = NULL; return 0; }
static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_len) { pni_ssl_t *ssl = transport->ssl; if (!ssl) return PN_EOS; if (ssl->ssl == NULL && init_ssl_socket(transport, ssl)) return PN_EOS; ssize_t written = 0; bool work_pending; do { work_pending = false; // first, get any pending application output, if possible if (!ssl->app_output_closed && ssl->out_count < ssl->out_size) { ssize_t app_bytes = transport->io_layers[layer+1]->process_output(transport, layer+1, &ssl->outbuf[ssl->out_count], ssl->out_size - ssl->out_count); if (app_bytes > 0) { ssl->out_count += app_bytes; work_pending = true; ssl_log(transport, "Gathered %d bytes from app to send to peer", app_bytes ); } else { if (app_bytes < 0) { ssl_log(transport, "Application layer closed its output, error=%d (%d bytes pending send)", (int) app_bytes, (int) ssl->out_count); ssl->app_output_closed = app_bytes; } } } // now push any pending app data into the socket if (!ssl->ssl_closed) { char *data = ssl->outbuf; if (ssl->out_count > 0) { int wrote = BIO_write( ssl->bio_ssl, data, ssl->out_count ); if (wrote > 0) { data += wrote; ssl->out_count -= wrote; work_pending = true; ssl_log( transport, "Wrote %d bytes from app to socket", wrote ); } else { if (!BIO_should_retry(ssl->bio_ssl)) { int reason = SSL_get_error( ssl->ssl, wrote ); switch (reason) { case SSL_ERROR_ZERO_RETURN: // SSL closed cleanly ssl_log(transport, "SSL connection has closed"); start_ssl_shutdown(transport); // KAG: not sure - this may not be necessary ssl->out_count = 0; // can no longer write to socket, so erase app output data ssl->ssl_closed = true; break; default: // unexpected error return (ssize_t)ssl_failed(transport); } } else { if (BIO_should_read( ssl->bio_ssl )) { ssl->read_blocked = true; ssl_log(transport, "Detected read-blocked"); } if (BIO_should_write( ssl->bio_ssl )) { ssl->write_blocked = true; ssl_log(transport, "Detected write-blocked"); } } } } if (ssl->out_count == 0) { if (ssl->app_input_closed && ssl->app_output_closed) { // application is done sending/receiving data, and all buffered output data has // been written to the SSL socket start_ssl_shutdown(transport); } } else if (data != ssl->outbuf) { memmove( ssl->outbuf, data, ssl->out_count ); } } // read from the network bio as much as possible, filling the buffer if (max_len) { int available = BIO_read( ssl->bio_net_io, buffer, max_len ); if (available > 0) { max_len -= available; buffer += available; written += available; ssl->write_blocked = false; work_pending = work_pending || max_len > 0; ssl_log(transport, "Read %d bytes from BIO Layer", available ); } } } while (work_pending); //_log(ssl, "written=%d ssl_closed=%d in_count=%d app_input_closed=%d app_output_closed=%d bio_pend=%d", // written, ssl->ssl_closed, ssl->in_count, ssl->app_input_closed, ssl->app_output_closed, BIO_pending(ssl->bio_net_io) ); // PROTON-82: close the output side as soon as we've sent the SSL close_notify. // We're not requiring the response, as some implementations never reply. // ---- // Once no more data is available "below" the SSL socket, tell the transport we are // done. //if (written == 0 && ssl->ssl_closed && BIO_pending(ssl->bio_net_io) == 0) { // written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS; //} if (written == 0 && (SSL_get_shutdown(ssl->ssl) & SSL_SENT_SHUTDOWN) && BIO_pending(ssl->bio_net_io) == 0) { written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS; if (transport->io_layers[layer]==&ssl_input_closed_layer) { transport->io_layers[layer] = &ssl_closed_layer; } else { transport->io_layers[layer] = &ssl_output_closed_layer; } } ssl_log(transport, "process_output_ssl() returning %d", (int) written); return written; }