diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-12-24 04:34:50 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-12-24 04:34:50 +0000 |
commit | f9111f673cec890c90d1d77f8a1bc64965661941 (patch) | |
tree | e315c47f5ccd442774969aa342c9aba3750f6947 /pjsip | |
parent | 0929253bcaeadfc922042f7b3c89c39062bb7334 (diff) |
More TLS fixes (ticket #3), it should work now in blocking mode
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@858 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/src/pjsip/sip_transport_tls_ossl.c | 340 |
1 files changed, 239 insertions, 101 deletions
diff --git a/pjsip/src/pjsip/sip_transport_tls_ossl.c b/pjsip/src/pjsip/sip_transport_tls_ossl.c index d2f86dde..0e69ed79 100644 --- a/pjsip/src/pjsip/sip_transport_tls_ossl.c +++ b/pjsip/src/pjsip/sip_transport_tls_ossl.c @@ -25,7 +25,9 @@ #include <pj/log.h> #include <pj/os.h> #include <pj/pool.h> +#include <pj/sock_select.h> #include <pj/string.h> +#include <pj/compat/socket.h> /* Only build when PJSIP_HAS_TLS_TRANSPORT is enabled */ @@ -60,6 +62,11 @@ #define PJSIP_TLS_EKEYFILE PJ_EUNKNOWN /** * @hideinitializer + * Error creating SSL context. + */ +#define PJSIP_TLS_ECTX PJ_EUNKNOWN +/** + * @hideinitializer * Unable to list SSL CA list. */ #define PJSIP_TLS_ECALIST PJ_EUNKNOWN @@ -103,7 +110,6 @@ struct tls_transport pj_sock_t sock; SSL *ssl; - BIO *bio; pjsip_rx_data rdata; pj_bool_t quitting; @@ -145,67 +151,31 @@ static pj_status_t tls_tp_destroy(pjsip_transport *transport); */ static int tls_init_count; -/* ssl_perror() */ -#if 0 -#define ssl_perror(level,obj,title) \ -{ \ - unsigned long ssl_err = ERR_get_error(); \ - char errmsg[200]; \ - ERR_error_string_n(ssl_err, errmsg, sizeof(errmsg)); \ - PJ_LOG(level,(obj, "%s: %s", title, errmsg)); \ -} -#elif 1 -struct err_data +/* ssl_report_error() */ +static void ssl_report_error(int level, const char *sender, + const char *format, ...) { - int lvl; - const char *snd; - const char *ttl; -}; - -static int ssl_print_err_count; -static int ssl_print_err_cb(const char *str, size_t len, void *u) -{ - struct err_data *e = (struct err_data *)u; - switch (e->lvl) { - case 1: - PJ_LOG(1,(e->snd, "%s: %.*s", e->ttl, len-1, str)); - break; - case 2: - PJ_LOG(2,(e->snd, "%s: %.*s", e->ttl, len-1, str)); - break; - case 3: - PJ_LOG(3,(e->snd, "%s: %.*s", e->ttl, len-1, str)); - break; - default: - PJ_LOG(4,(e->snd, "%s: %.*s", e->ttl, len-1, str)); - break; - } - ++ssl_print_err_count; - return len; -} + va_list arg; + unsigned long ssl_err; -static void ssl_perror(int level, const char *sender, const char *title) -{ - struct err_data e; - int count = ssl_print_err_count; - e.lvl = level; e.snd = sender; e.ttl = title; - ERR_print_errors_cb(&ssl_print_err_cb, &e); + va_start(arg, format); + ssl_err = ERR_get_error(); - if (count==ssl_print_err_count) - ssl_print_err_cb(" ", 1, &e); -} -#else -static void ssl_perror(int level, const char *sender, const char *title) -{ - static BIO *bio_err; + if (ssl_err == 0) { + pj_log(sender, level, format, arg); + } else { + char err_format[512]; + int len; - if (!bio_err) { - bio_err = BIO_new_fp(stderr,BIO_NOCLOSE); + len = pj_ansi_snprintf(err_format, sizeof(err_format), + "%s: ", format); + ERR_error_string(ssl_err, err_format+len); + + pj_log(sender, level, err_format, arg); } - ERR_print_errors(bio_err); -} -#endif + va_end(arg); +} /* Initialize OpenSSL */ @@ -254,34 +224,47 @@ static pj_status_t initialize_ctx(struct tls_listener *lis, SSL_METHOD *meth; SSL_CTX *ctx; + *p_ctx = NULL; + /* Create SSL context*/ meth = SSLv23_method(); ctx = SSL_CTX_new(meth); + if (ctx == NULL) + return PJSIP_TLS_ECTX; + + /* Load the CAs we trust*/ + if (ca_list_file && *ca_list_file) { + if(!(SSL_CTX_load_verify_locations(ctx, ca_list_file, 0))) { + ssl_report_error(2, lis->base.obj_name, + "Error loading/verifying CA list file '%s'", + ca_list_file); + SSL_CTX_free(ctx); + return PJSIP_TLS_ECALIST; + } + } + /* Load our keys and certificates */ - if(!(SSL_CTX_use_certificate_chain_file(ctx, keyfile))) { - ssl_perror(2, lis->base.obj_name, - "Error loading keys and certificate file"); - SSL_CTX_free(ctx); - return PJSIP_TLS_EKEYFILE; - } + if (keyfile && *keyfile) { + if(!(SSL_CTX_use_certificate_chain_file(ctx, keyfile))) { + ssl_report_error(2, lis->base.obj_name, + "Error loading keys and certificate file '%s'", + keyfile); + SSL_CTX_free(ctx); + return PJSIP_TLS_EKEYFILE; + } - /* Set password callback */ - SSL_CTX_set_default_passwd_cb(ctx, password_cb); - SSL_CTX_set_default_passwd_cb_userdata(ctx, lis); + /* Set password callback */ + SSL_CTX_set_default_passwd_cb(ctx, password_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, lis); - if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))) { - ssl_perror(2, lis->base.obj_name, "Error loading private key file"); - SSL_CTX_free(ctx); - return PJSIP_TLS_EKEYFILE; - } - - /* Load the CAs we trust*/ - if(!(SSL_CTX_load_verify_locations(ctx, ca_list_file, 0))) { - ssl_perror(2, lis->base.obj_name, - "Error loading/verifying CA list file"); - SSL_CTX_free(ctx); - return PJSIP_TLS_ECALIST; + if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))) { + ssl_report_error(2, lis->base.obj_name, + "Error loading private key file '%s'", + keyfile); + SSL_CTX_free(ctx); + return PJSIP_TLS_EKEYFILE; + } } #if (OPENSSL_VERSION_NUMBER < 0x00905100L) @@ -497,9 +480,21 @@ static int PJ_THREAD_FUNC tls_worker_thread(void *arg) pjsip_rx_data *rdata = &tls_tp->rdata; while (!tls_tp->quitting) { + pj_fd_set_t rd_set; + pj_time_val timeout; int len; pj_size_t size_eaten; + PJ_FD_ZERO(&rd_set); + PJ_FD_SET(tls_tp->sock, &rd_set); + + timeout.sec = 1; + timeout.msec = 0; + + len = pj_sock_select(tls_tp->sock, &rd_set, NULL, NULL, &timeout); + if (len < 1) + continue; + /* Start blocking read to SSL socket */ len = SSL_read(tls_tp->ssl, rdata->pkt_info.packet + rdata->pkt_info.len, @@ -534,20 +529,22 @@ static int PJ_THREAD_FUNC tls_worker_thread(void *arg) case SSL_ERROR_ZERO_RETURN: PJ_LOG(4,(tls_tp->base.obj_name, "SSL transport shutdodwn by remote")); - pjsip_transport_shutdown(&tls_tp->base); + if (!tls_tp->quitting) + pjsip_transport_shutdown(&tls_tp->base); goto done; case SSL_ERROR_SYSCALL: PJ_LOG(2,(tls_tp->base.obj_name, "SSL Error: Premature close")); - pjsip_transport_shutdown(&tls_tp->base); + if (!tls_tp->quitting) + pjsip_transport_shutdown(&tls_tp->base); goto done; default: PJ_LOG(2,(tls_tp->base.obj_name, "SSL read problem")); - pjsip_transport_shutdown(&tls_tp->base); + if (!tls_tp->quitting) + pjsip_transport_shutdown(&tls_tp->base); goto done; } - } done: @@ -555,6 +552,123 @@ done: } +PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp); + +/* + * Perform SSL_connect upon completion of socket connect() + */ +static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) +{ + int status; + + if (SSL_is_init_finished (ssl)) + return PJ_SUCCESS; + + SSL_set_fd(ssl, (int)sock); + + if (!SSL_in_connect_init (ssl)) + SSL_set_connect_state (ssl); + + do { + /* These handle sets are used to set up for whatever SSL_connect + * says it wants next. They're reset on each pass around the loop. + */ + pj_fd_set_t rd_set; + pj_fd_set_t wr_set; + + PJ_FD_ZERO(&rd_set); + PJ_FD_ZERO(&wr_set); + + status = SSL_connect (ssl); + switch (SSL_get_error (ssl, status)) { + case SSL_ERROR_NONE: + /* Success */ + status = 0; + break; + + case SSL_ERROR_WANT_WRITE: + /* Wait for more activity */ + PJ_FD_SET(sock, &wr_set); + status = 1; + break; + + case SSL_ERROR_WANT_READ: + /* Wait for more activity */ + PJ_FD_SET(sock, &rd_set); + status = 1; + break; + + case SSL_ERROR_ZERO_RETURN: + /* The peer has notified us that it is shutting down via + * the SSL "close_notify" message so we need to + * shutdown, too. + */ + PJ_LOG(4,(THIS_FILE, "SSL connect() failed, remote has" + "shutdown connection.")); + status = -1; + break; + + case SSL_ERROR_SYSCALL: + /* On some platforms (e.g. MS Windows) OpenSSL does not + * store the last error in errno so explicitly do so. + * + * Explicitly check for EWOULDBLOCK since it doesn't get + * converted to an SSL_ERROR_WANT_{READ,WRITE} on some + * platforms. If SSL_connect failed outright, though, don't + * bother checking more. This can happen if the socket gets + * closed during the handshake. + */ + if (pj_get_netos_error() == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + status == -1) + { + /* Although the SSL_ERROR_WANT_READ/WRITE isn't getting + * set correctly, the read/write state should be valid. + * Use that to decide what to do. + */ + status = 1; /* Wait for more activity */ + if (SSL_want_write (ssl)) + PJ_FD_SET(sock, &wr_set); + else if (SSL_want_read (ssl)) + PJ_FD_SET(sock, &rd_set); + else + status = -1; /* Doesn't want anything - bail out */ + } + else { + status = -1; + } + break; + + default: + ssl_report_error(4, THIS_FILE, "SSL_connect() error"); + status = -1; + break; + } + + if (status == 1) { + /* Must have at least one handle to wait for at this point. */ + pj_assert(PJ_FD_COUNT(&rd_set) == 1 || + PJ_FD_COUNT(&wr_set) == 1); + + /* Block indefinitely if timeout pointer is zero. */ + status = pj_sock_select(FD_SETSIZE, &rd_set, &wr_set, + NULL, NULL); + + /* 0 is timeout, so we're done. + * -1 is error, so we're done. + * Could be both handles set (same handle in both masks) so set to 1. + */ + if (status >= 1) + status = 1; + else /* Timeout or socket failure */ + status = -1; + } + + } while (status == 1 && !SSL_is_init_finished (ssl)); + + return (status == -1 ? PJSIP_TLS_ECONNECT : PJ_SUCCESS); +} + + /* * Create a new TLS transport. The TLS role can be a server or a client, * depending on whether socket is valid. @@ -657,15 +771,12 @@ static pj_status_t tls_create_transport(struct tls_listener *lis, /* Create SSL object and BIO */ tls_tp->ssl = SSL_new(lis->ctx); - tls_tp->bio = BIO_new_socket(sock, BIO_NOCLOSE); - SSL_set_bio(tls_tp->ssl, tls_tp->bio, tls_tp->bio); + SSL_set_verify (tls_tp->ssl, 0, 0); /* Connect SSL */ - if (SSL_connect(tls_tp->ssl) <= 0) { - ssl_perror(4, tls_tp->base.obj_name, "SSL_connect() error"); - status = PJSIP_TLS_ECONNECT; + status = perform_ssl_connect(tls_tp->ssl, sock); + if (status != PJ_SUCCESS) goto on_error; - } /* TODO: check server cert. */ PJ_TODO(TLS_CHECK_SERVER_CERT); @@ -839,28 +950,56 @@ static pj_status_t tls_tp_send_msg(pjsip_transport *transport, pj_ssize_t sent_bytes)) { struct tls_transport *tls_tp = (struct tls_transport*) transport; + int bytes_sent; /* This is a connection oriented protocol, so rem_addr is not used */ PJ_UNUSED_ARG(rem_addr); PJ_UNUSED_ARG(addr_len); - /* Write to TLS */ - if (BIO_write(tls_tp->bio, tdata->buf.start , - tdata->buf.cur - tdata->buf.start) <= 0) - { - if(! BIO_should_retry(tls_tp->bio)) { - ssl_perror(4, transport->obj_name, "SSL send error"); - return PJSIP_TLS_ESEND; - } - - /* Do something to handle the retry */ - } - /* Data written immediately, no need to call callback */ PJ_UNUSED_ARG(callback); PJ_UNUSED_ARG(token); - return PJ_SUCCESS; + /* Write to TLS */ + bytes_sent = SSL_write (tls_tp->ssl, tdata->buf.start, + tdata->buf.cur - tdata->buf.start); + + switch (SSL_get_error (tls_tp->ssl, bytes_sent)) { + case SSL_ERROR_NONE: + pj_assert(bytes_sent == tdata->buf.cur - tdata->buf.start); + return PJ_SUCCESS; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return PJ_RETURN_OS_ERROR(OSERR_EWOULDBLOCK); + + case SSL_ERROR_ZERO_RETURN: + /* The peer has notified us that it is shutting down via the SSL + * "close_notify" message so we need to shutdown, too. + */ + pj_assert(bytes_sent == tdata->buf.cur - tdata->buf.start); + SSL_shutdown (tls_tp->ssl); + pjsip_transport_shutdown(transport); + return PJ_SUCCESS; + + case SSL_ERROR_SYSCALL: + if (bytes_sent == 0) { + /* An EOF occured but the SSL "close_notify" message was not + * sent. This is a protocol error, but we ignore it. + */ + pjsip_transport_shutdown(transport); + return 0; + } + return pj_get_netos_error(); + + default: + /* Reset errno to prevent previous values (e.g. EWOULDBLOCK) + * from being associated with fatal SSL errors. + */ + pj_set_netos_error(0); + ssl_report_error(4, transport->obj_name, "SSL_write error"); + return PJSIP_TLS_ESEND; + } } @@ -905,7 +1044,6 @@ static pj_status_t tls_tp_destroy(pjsip_transport *transport) SSL_free(tls_tp->ssl); tls_tp->ssl = NULL; - tls_tp->bio = NULL; tls_tp->sock = PJ_INVALID_SOCKET; } else if (tls_tp->sock != PJ_INVALID_SOCKET) { |