📄 sip_transport_tls_ossl.c
字号:
/* $Id: sip_transport_tls_ossl.c 974 2007-02-19 01:13:53Z bennylp $ *//* * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include <pjsip/sip_transport_tls.h>#include <pjsip/sip_endpoint.h>#include <pjsip/sip_errno.h>#include <pj/compat/socket.h>#include <pj/addr_resolv.h>#include <pj/assert.h>#include <pj/ioqueue.h>#include <pj/lock.h>#include <pj/log.h>#include <pj/os.h>#include <pj/pool.h>#include <pj/compat/socket.h>#include <pj/sock_select.h>#include <pj/string.h>/* Only build when PJSIP_HAS_TLS_TRANSPORT is enabled */#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0/* * Include OpenSSL headers */#include <openssl/bio.h>#include <openssl/ssl.h>#include <openssl/err.h>/* * With VisualC++, it's not possible to dynamically link against some * libraries when some macros are defined (unlike "make" based build * system where this can be easily manipulated). * * So for VisualC++ IDE, include OpenSSL libraries in the linking by * using the #pragma lib construct below. */#ifdef _MSC_VER# ifdef _DEBUG# pragma comment( lib, "libeay32MTd")# pragma comment( lib, "ssleay32MTd")#else# pragma comment( lib, "libeay32MT")# pragma comment( lib, "ssleay32MT")# endif#endif#define THIS_FILE "transport_tls_ossl.c"#define MAX_ASYNC_CNT 16#define POOL_LIS_INIT 4000#define POOL_LIS_INC 4001#define POOL_TP_INIT 4000#define POOL_TP_INC 4002/** * Get the number of descriptors in the set. This is defined in sock_select.c * This function will only return the number of sockets set from PJ_FD_SET * operation. When the set is modified by other means (such as by select()), * the count will not be reflected here. * * That's why don't export this function in the header file, to avoid * misunderstanding. * * @param fdsetp The descriptor set. * * @return Number of descriptors in the set. */PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp);struct tls_listener;struct tls_transport;/* * This structure is "descendant" of pj_ioqueue_op_key_t, and it is used to * track pending/asynchronous accept() operation. TLS transport may have * more than one pending accept() operations, depending on the value of * async_cnt. */struct pending_accept{ pj_ioqueue_op_key_t op_key; struct tls_listener *listener; unsigned index; pj_pool_t *pool; pj_sock_t new_sock; int addr_len; pj_sockaddr_in local_addr; pj_sockaddr_in remote_addr;};/* * This is the TLS listener, which is a "descendant" of pjsip_tpfactory (the * SIP transport factory). */struct tls_listener{ pjsip_tpfactory factory; pj_bool_t is_registered; pjsip_tls_setting setting; pjsip_endpoint *endpt; pjsip_tpmgr *tpmgr; pj_sock_t sock; pj_ioqueue_key_t *key; unsigned async_cnt; struct pending_accept *accept_op[MAX_ASYNC_CNT]; SSL_CTX *ctx;};/* * This structure is used to keep delayed transmit operation in a list. * A delayed transmission occurs when application sends tx_data when * the TLS connect/establishment is still in progress. These delayed * transmission will be "flushed" once the socket is connected (either * successfully or with errors). */struct delayed_tdata{ PJ_DECL_LIST_MEMBER(struct delayed_tdata); pjsip_tx_data_op_key *tdata_op_key;};/* * This structure describes the TLS transport, and it's descendant of * pjsip_transport. */struct tls_transport{ pjsip_transport base; pj_bool_t is_server; struct tls_listener *listener; pj_bool_t is_registered; pj_bool_t is_closing; pj_status_t close_reason; pj_sock_t sock; pj_ioqueue_key_t *key; pj_bool_t has_pending_connect; /* SSL connection */ SSL *ssl; pj_bool_t ssl_shutdown_called; /* TLS transport can only have one rdata! * Otherwise chunks of incoming PDU may be received on different * buffer. */ pjsip_rx_data rdata; /* Pending transmission list. */ struct delayed_tdata delayed_list;};/**************************************************************************** * PROTOTYPES *//* This callback is called when pending accept() operation completes. */static void on_accept_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, pj_status_t status);/* This callback is called by transport manager to destroy listener */static pj_status_t lis_destroy(pjsip_tpfactory *factory);/* This callback is called by transport manager to create transport */static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, int addr_len, pjsip_transport **transport);/* Common function to create and initialize transport */static pj_status_t tls_create(struct tls_listener *listener, pj_pool_t *pool, pj_sock_t sock, pj_bool_t is_server, const pj_sockaddr_in *local, const pj_sockaddr_in *remote, struct tls_transport **p_tls);/**************************************************************************** * SSL FUNCTIONS *//* ssl_report_error() */static void ssl_report_error(const char *sender, int level, pj_status_t status, const char *format, ...){ va_list marker; va_start(marker, format); if (status != PJ_SUCCESS) { char err_format[PJ_ERR_MSG_SIZE + 512]; int len; len = pj_ansi_snprintf(err_format, sizeof(err_format), "%s: ", format); pj_strerror(status, err_format+len, sizeof(err_format)-len); pj_log(sender, level, err_format, marker); } else { unsigned long ssl_err; ssl_err = ERR_get_error(); if (ssl_err == 0) { pj_log(sender, level, format, marker); } else { char err_format[512]; int len; 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, marker); } } va_end(marker);}static void sockaddr_to_host_port( pj_pool_t *pool, pjsip_host_port *host_port, const pj_sockaddr_in *addr ){ enum { M = 48 }; host_port->host.ptr = pj_pool_alloc(pool, M); host_port->host.slen = pj_ansi_snprintf( host_port->host.ptr, M, "%s", pj_inet_ntoa(addr->sin_addr)); host_port->port = pj_ntohs(addr->sin_port);}/* SSL password callback. */static int password_cb(char *buf, int num, int rwflag, void *user_data){ struct tls_listener *lis = user_data; PJ_UNUSED_ARG(rwflag); if(num < lis->setting.password.slen+1) return 0; pj_memcpy(buf, lis->setting.password.ptr, lis->setting.password.slen); return lis->setting.password.slen;}/* OpenSSL library initialization counter */static int openssl_init_count;/* Initialize OpenSSL */static pj_status_t init_openssl(void){ if (++openssl_init_count != 1) return PJ_SUCCESS; SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); return PJ_SUCCESS;}/* Shutdown OpenSSL */static void shutdown_openssl(void){ if (--openssl_init_count != 0) return;}/* Create and initialize new SSL context */static pj_status_t create_ctx( struct tls_listener *lis, SSL_CTX **p_ctx){ struct pjsip_tls_setting *opt = &lis->setting; char *lis_name = lis->factory.obj_name; SSL_METHOD *ssl_method; SSL_CTX *ctx; int mode, rc; *p_ctx = NULL; /* Make sure OpenSSL library has been initialized */ init_openssl(); /* Determine SSL method to use */ switch (opt->method) { case PJSIP_SSL_DEFAULT_METHOD: case PJSIP_SSLV23_METHOD: ssl_method = SSLv23_method(); break; case PJSIP_TLSV1_METHOD: ssl_method = TLSv1_method(); break; case PJSIP_SSLV2_METHOD: ssl_method = SSLv2_method(); break; case PJSIP_SSLV3_METHOD: ssl_method = SSLv3_method(); break; default: ssl_report_error(lis_name, 4, PJSIP_TLS_EINVMETHOD, "Error creating SSL context"); return PJSIP_TLS_EINVMETHOD; } /* Create SSL context for the listener */ ctx = SSL_CTX_new(ssl_method); if (ctx == NULL) { ssl_report_error(lis_name, 4, PJ_SUCCESS, "Error creating SSL context"); return PJSIP_TLS_ECTX; } /* Load CA list if one is specified. */ if (opt->ca_list_file.slen) { /* Can only take a NULL terminated filename in the setting */ pj_assert(opt->ca_list_file.ptr[opt->ca_list_file.slen] == '\0'); rc = SSL_CTX_load_verify_locations(ctx, opt->ca_list_file.ptr, NULL); if (rc != 1) { ssl_report_error(lis_name, 4, PJ_SUCCESS, "Error loading/verifying CA list file '%.*s'", (int)opt->ca_list_file.slen, opt->ca_list_file.ptr); SSL_CTX_free(ctx); return PJSIP_TLS_ECACERT; } PJ_LOG(5,(lis_name, "TLS CA file successfully loaded from '%.*s'", (int)opt->ca_list_file.slen, opt->ca_list_file.ptr)); } /* Set password callback */ SSL_CTX_set_default_passwd_cb(ctx, password_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, lis); /* Load certificate if one is specified */ if (opt->cert_file.slen) { /* Can only take a NULL terminated filename in the setting */ pj_assert(opt->cert_file.ptr[opt->cert_file.slen] == '\0'); /* Load certificate chain from file into ctx */ rc = SSL_CTX_use_certificate_chain_file(ctx, opt->cert_file.ptr); if(rc != 1) { ssl_report_error(lis_name, 4, PJ_SUCCESS, "Error loading certificate chain file '%.*s'", (int)opt->cert_file.slen, opt->cert_file.ptr); SSL_CTX_free(ctx); return PJSIP_TLS_ECERTFILE; } PJ_LOG(5,(lis_name, "TLS certificate successfully loaded from '%.*s'", (int)opt->cert_file.slen, opt->cert_file.ptr)); } /* Load private key if one is specified */ if (opt->privkey_file.slen) { /* Can only take a NULL terminated filename in the setting */ pj_assert(opt->privkey_file.ptr[opt->privkey_file.slen] == '\0'); /* Adds the first private key found in file to ctx */ rc = SSL_CTX_use_PrivateKey_file(ctx, opt->privkey_file.ptr, SSL_FILETYPE_PEM); if(rc != 1) { ssl_report_error(lis_name, 4, PJ_SUCCESS, "Error adding private key from '%.*s'", (int)opt->privkey_file.slen, opt->privkey_file.ptr); SSL_CTX_free(ctx); return PJSIP_TLS_EKEYFILE; } PJ_LOG(5,(lis_name, "TLS private key successfully loaded from '%.*s'", (int)opt->privkey_file.slen, opt->privkey_file.ptr)); } /* SSL verification options */ if (lis->setting.verify_client || lis->setting.verify_server) { mode = SSL_VERIFY_PEER; if (lis->setting.require_client_cert) mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } else { mode = SSL_VERIFY_NONE; } SSL_CTX_set_verify(ctx, mode, NULL); PJ_LOG(5,(lis_name, "TLS verification mode set to %d", mode)); /* Optionally set cipher list if one is specified */ if (opt->ciphers.slen) { /* Can only take a NULL terminated cipher list in the setting */ pj_assert(opt->cert_file.ptr[opt->cert_file.slen] == '\0'); 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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -