⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sip_transport_tls_ossl.c

📁 一个开源SIP协议栈
💻 C
📖 第 1 页 / 共 5 页
字号:
	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 + -