📄 sip_transport_tls_ossl.c
字号:
rc = SSL_CTX_set_cipher_list(ctx, opt->ciphers.ptr);
if (rc != 1) {
ssl_report_error(lis_name, 4, PJ_SUCCESS,
"Error setting cipher list '%.*s'",
(int)opt->ciphers.slen,
opt->ciphers.ptr);
SSL_CTX_free(ctx);
return PJSIP_TLS_ECIPHER;
}
PJ_LOG(5,(lis_name, "TLS ciphers set to '%.*s'",
(int)opt->ciphers.slen,
opt->ciphers.ptr));
}
/* Done! */
*p_ctx = ctx;
return PJ_SUCCESS;
}
/* Destroy SSL context */
static void destroy_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
/* Potentially shutdown OpenSSL library if this is the last
* context exists.
*/
shutdown_openssl();
}
/*
* Perform SSL_connect upon completion of socket connect()
*/
static pj_status_t ssl_connect(struct tls_transport *tls)
{
SSL *ssl = tls->ssl;
int status;
if (SSL_is_init_finished (ssl))
return PJ_SUCCESS;
if (SSL_get_fd(ssl) < 0)
SSL_set_fd(ssl, (int)tls->sock);
if (!SSL_in_connect_init(ssl))
SSL_set_connect_state(ssl);
PJ_LOG(5,(tls->base.obj_name, "Starting SSL_connect() negotiation"));
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;
PJ_LOG(5,(tls->base.obj_name,
"SSL_connect() negotiation completes successfully"));
break;
case SSL_ERROR_WANT_WRITE:
/* Wait for more activity */
PJ_FD_SET(tls->sock, &wr_set);
status = 1;
break;
case SSL_ERROR_WANT_READ:
/* Wait for more activity */
PJ_FD_SET(tls->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."));
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
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(tls->sock, &wr_set);
else if (SSL_want_read (ssl))
PJ_FD_SET(tls->sock, &rd_set);
else
status = -1; /* Doesn't want anything - bail out */
}
else {
status = -1;
}
break;
default:
ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS,
"SSL_connect() error");
status = -1;
break;
}
if (status == 1) {
pj_time_val timeout, *p_timeout;
/* 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);
/* This will block the whole stack!!! */
PJ_TODO(SUPPORT_SSL_ASYNCHRONOUS_CONNECT);
if (tls->listener->setting.timeout.sec == 0 &&
tls->listener->setting.timeout.msec == 0)
{
p_timeout = NULL;
} else {
timeout = tls->listener->setting.timeout;
p_timeout = &timeout;
}
/* Block indefinitely if timeout pointer is zero. */
status = pj_sock_select(tls->sock+1, &rd_set, &wr_set,
NULL, p_timeout);
/* 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 if (status == 0)
return PJSIP_TLS_ETIMEDOUT;
else
status = -1;
}
} while (status == 1 && !SSL_is_init_finished (ssl));
return (status == -1 ? PJSIP_TLS_ECONNECT : PJ_SUCCESS);
}
/*
* Perform SSL_accept() on the newly established incoming TLS connection.
*/
static pj_status_t ssl_accept(struct tls_transport *tls)
{
SSL *ssl = tls->ssl;
int rc;
if (SSL_is_init_finished (ssl))
return PJ_SUCCESS;
if (!SSL_in_accept_init(ssl))
SSL_set_accept_state(ssl);
PJ_LOG(5,(tls->base.obj_name, "Starting SSL_accept() negotiation"));
/* Repeat retrying SSL_accept() procedure until it completes either
* successfully or with failure.
*/
do {
/* These handle sets are used to set up for whatever SSL_accept 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);
rc = SSL_accept (ssl);
switch (SSL_get_error (ssl, rc)) {
case SSL_ERROR_NONE:
/* Success! */
rc = 0;
PJ_LOG(5,(tls->base.obj_name,
"SSL_accept() negotiation completes successfully"));
break;
case SSL_ERROR_WANT_WRITE:
PJ_FD_SET(tls->sock, &wr_set);
rc = 1; /* Wait for more activity */
break;
case SSL_ERROR_WANT_READ:
PJ_FD_SET(tls->sock, &rd_set);
rc = 1; /* Need to read more data */
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,(tls->base.obj_name,
"Incoming SSL connection closed prematurely by client"));
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
case SSL_ERROR_SYSCALL:
/* Explicitly check for EWOULDBLOCK since it doesn't get converted
* to an SSL_ERROR_WANT_{READ,WRITE} on some platforms.
* If SSL_accept 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)
&& rc==-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.
*/
rc = 1; /* Wait for more activity */
if (SSL_want_write(ssl))
PJ_FD_SET(tls->sock, &wr_set);
else if (SSL_want_read(ssl))
PJ_FD_SET(tls->sock, &rd_set);
else {
/* Doesn't want anything - bail out */
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
}
}
else {
return PJSIP_TLS_EUNKNOWN;
}
break;
default:
ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS,
"Error calling SSL_accept()");
return pj_get_netos_error() ? pj_get_netos_error() :
PJSIP_TLS_EUNKNOWN;
}
if (rc == 1) {
pj_time_val timeout, *p_timeout;
/* 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);
if (tls->listener->setting.timeout.sec == 0 &&
tls->listener->setting.timeout.msec == 0)
{
p_timeout = NULL;
} else {
timeout = tls->listener->setting.timeout;
p_timeout = &timeout;
}
rc = pj_sock_select(tls->sock+1, &rd_set, &wr_set, NULL,
p_timeout);
if (rc >= 1)
rc = 1;
else if (rc == 0)
return PJSIP_TLS_ETIMEDOUT;
else
return pj_get_netos_error();
}
} while (rc == 1 && !SSL_is_init_finished(ssl));
return (rc == -1 ? PJSIP_TLS_EUNKNOWN : PJ_SUCCESS);
}
/* Send outgoing data with SSL connection */
static pj_status_t ssl_write(struct tls_transport *tls,
pjsip_tx_data *tdata)
{
int size = tdata->buf.cur - tdata->buf.start;
int sent = 0;
do {
const int fragment_sent = SSL_write(tls->ssl,
tdata->buf.start + sent,
size - sent);
switch( SSL_get_error(tls->ssl, fragment_sent)) {
case SSL_ERROR_NONE:
sent += fragment_sent;
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
/* For now, we can't handle situation where WANT_READ/WANT_WRITE
* is raised after some data has been sent, since we don't have
* mechanism to keep track of how many bytes have been sent
* inside the individual tdata.
*/
pj_assert(sent == 0);
PJ_TODO(PARTIAL_SSL_SENT);
return PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK);
case SSL_ERROR_ZERO_RETURN:
/* The peer has notified us that it is shutting down via the SSL
* "close_notify" message. Tell the transport manager that it
* shouldn't use this transport any more and return ENOTCONN
* to caller.
*/
/* It is safe to call this multiple times. */
pjsip_transport_shutdown(&tls->base);
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
case SSL_ERROR_SYSCALL:
if (fragment_sent == 0) {
/* An EOF occured but the SSL "close_notify" message was not
* sent. Shutdown the transport and return ENOTCONN.
*/
/* It is safe to call this multiple times. */
pjsip_transport_shutdown(&tls->base);
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
}
/* Other error */
return pj_get_netos_error();
default:
ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS,
"Error sending %s with SSL_write()",
pjsip_tx_data_get_info(tdata));
return pj_get_netos_error() ? pj_get_netos_error()
: PJSIP_TLS_ESEND;
}
} while (sent < size);
return PJ_SUCCESS;
}
/* Read data from SSL connection */
static pj_status_t ssl_read(struct tls_transport *tls)
{
pjsip_rx_data *rdata = &tls->rdata;
int bytes_read, max_size;
max_size = sizeof(rdata->pkt_info.packet) - rdata->pkt_info.len;
bytes_read = SSL_read(tls->ssl,
rdata->pkt_info.packet+rdata->pkt_info.len,
max_size);
switch (SSL_get_error(tls->ssl, bytes_read)) {
case SSL_ERROR_NONE:
/* Data successfully read */
rdata->pkt_info.len += bytes_read;
return PJ_SUCCESS;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK);
case SSL_ERROR_ZERO_RETURN:
/* The peer has notified us that it is shutting down via the SSL
* "close_notify" message.
*/
pjsip_transport_shutdown(&tls->base);
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
case SSL_ERROR_SYSCALL:
if (bytes_read == 0) {
/* An EOF occured but the SSL "close_notify" message was not
* sent.
*/
pjsip_transport_shutdown(&tls->base);
return PJ_STATUS_FROM_OS(OSERR_ENOTCONN);
}
/* Other error */
return pj_get_netos_error();
default:
ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS,
"Error reading data with SSL_read()");
return pj_get_netos_error() ? pj_get_netos_error()
: PJSIP_TLS_EREAD;
}
/* Should not reach here */
}
/****************************************************************************
* The TLS listener/transport factory.
*/
/*
* This is the public API to create, initialize, register, and start the
* TLS listener.
*/
PJ_DEF(pj_status_t) pjsip_tls_transport_start( pjsip_endpoint *endpt,
const pjsip_tls_setting *opt,
const pj_sockaddr_in *local,
const pjsip_host_port *a_name,
unsigned async_cnt,
pjsip_tpfactory **p_factory)
{
pj_pool_t *pool;
struct tls_listener *listener;
pj_ioqueue_callback listener_cb;
pj_sockaddr_in *listener_addr;
int addr_len;
unsigned i;
pj_status_t status;
/* Sanity check */
PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
/* Verify that address given in a_name (if any) is valid */
if (a_name && a_name->host.slen) {
pj_sockaddr_in tmp;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -