conn.c
来自「mms client」· C语言 代码 · 共 1,478 行 · 第 1/3 页
C
1,478 行
/* conn.c - implement Connection type * * This file implements the interface defined in conn.h. * * Richard Braakman * * SSL client implementation contributed by * Jarkko Kovala <jarkko.kovala@iki.fi> * * SSL server implementation contributed by * Stipe Tolj <tolj@wapme-systems.de> for Wapme Systems AG *//* TODO: unlocked_close() on error *//* TODO: have I/O functions check if connection is open *//* TODO: have conn_open_tcp do a non-blocking connect() */#include <signal.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <string.h>#include "gwlib/gwlib.h"#ifdef HAVE_LIBSSL#include <openssl/ssl.h>#include <openssl/err.h>SSL_CTX *global_ssl_context = NULL;SSL_CTX *global_server_ssl_context = NULL;X509 *ssl_public_cert;RSA *ssl_private_key;#endif /* HAVE_LIBSSL */typedef unsigned long (*CRYPTO_CALLBACK_PTR)(void);/* * This used to be 4096. It is now 0 so that callers don't have to * deal with the complexities of buffering (i.e. deciding when to * flush) unless they want to. * FIXME: Figure out how to combine buffering sensibly with use of * conn_register. */#define DEFAULT_OUTPUT_BUFFERING 0#define SSL_CONN_TIMEOUT 30struct Connection{ /* We use separate locks for input and ouput fields, so that * read and write activities don't have to get in each other's * way. If you need both, then acquire the outlock first. */ Mutex *inlock; Mutex *outlock; volatile sig_atomic_t claimed;#ifndef NO_GWASSERT long claiming_thread;#endif /* fd value is read-only and is not locked */ int fd; /* socket state */ enum {yes,no} connected; /* Protected by outlock */ Octstr *outbuf; long outbufpos; /* start of unwritten data in outbuf */ /* Try to buffer writes until there are this many octets to send. * Set it to 0 to get an unbuffered connection. */ unsigned int output_buffering; /* Protected by inlock */ Octstr *inbuf; long inbufpos; /* start of unread data in inbuf */ int read_eof; /* we encountered eof on read */ int read_error; /* we encountered error on read */ /* Protected by both locks when updating, so you need only one * of the locks when reading. */ FDSet *registered; conn_callback_t *callback; void *callback_data; /* Protected by inlock */ int listening_pollin; /* Protected by outlock */ int listening_pollout;#ifdef HAVE_LIBSSL SSL *ssl; X509 *peer_certificate; Mutex *ssl_mutex;#endif /* HAVE_LIBSSL */};static void unlocked_register_pollin(Connection *conn, int onoff);static void unlocked_register_pollout(Connection *conn, int onoff);/* There are a number of functions that play with POLLIN and POLLOUT flags. * The general rule is that we always want to poll for POLLIN except when * we have detected eof (which may be reported as eternal POLLIN), and * we want to poll for POLLOUT only if there's data waiting in the * output buffer. If output buffering is set, we may not want to poll for * POLLOUT if there's not enough data waiting, which is why we have * unlocked_try_write. *//* Macros to get more information for debugging purposes */#define unlock_in(conn) unlock_in_real(conn, __FILE__, __LINE__, __func__)#define unlock_out(conn) unlock_out_real(conn, __FILE__, __LINE__, __func__)/* Lock a Connection's read direction, if the Connection is unclaimed */static void lock_in(Connection *conn){ gw_assert(conn != NULL); if (conn->claimed) gw_assert(gwthread_self() == conn->claiming_thread); else mutex_lock(conn->inlock);}/* Unlock a Connection's read direction, if the Connection is unclaimed */static void unlock_in_real(Connection *conn, char *file, int line, const char *func){ int ret; gw_assert(conn != NULL); if (!conn->claimed) { if ((ret = mutex_unlock(conn->inlock)) != 0) { panic(0, "%s:%ld: %s: Mutex unlock failed. " \ "(Called from %s:%ld:%s.)", \ __FILE__, (long) __LINE__, __func__, \ file, (long) line, func); } }}/* Lock a Connection's write direction, if the Connection is unclaimed */static void lock_out(Connection *conn){ gw_assert(conn != NULL); if (conn->claimed) gw_assert(gwthread_self() == conn->claiming_thread); else mutex_lock(conn->outlock);}/* Unlock a Connection's write direction, if the Connection is unclaimed */static void unlock_out_real(Connection *conn, char *file, int line, const char *func){ int ret; gw_assert(conn != NULL); if (!conn->claimed) { if ((ret = mutex_unlock(conn->outlock)) != 0) { panic(0, "%s:%ld: %s: Mutex unlock failed. " \ "(Called from %s:%ld:%s.)", \ __FILE__, (long) __LINE__, __func__, \ file, (long) line, func); } }}/* Return the number of bytes in the Connection's output buffer */static long unlocked_outbuf_len(Connection *conn){ return octstr_len(conn->outbuf) - conn->outbufpos;}/* Return the number of bytes in the Connection's input buffer */static long unlocked_inbuf_len(Connection *conn){ return octstr_len(conn->inbuf) - conn->inbufpos;}/* Send as much data as can be sent without blocking. Return the number * of bytes written, or -1 in case of error. */static long unlocked_write(Connection *conn){ long ret;#ifdef HAVE_LIBSSL if (conn->ssl != NULL) { mutex_lock(conn->ssl_mutex); ret = SSL_write(conn->ssl, octstr_get_cstr(conn->outbuf) + conn->outbufpos, octstr_len(conn->outbuf) - conn->outbufpos); if (ret < 0) { int SSL_error = SSL_get_error(conn->ssl, ret); if (SSL_error == SSL_ERROR_WANT_READ) { SSL_read(conn->ssl, NULL, 0); mutex_unlock(conn->ssl_mutex); return 0; } else if (SSL_error == SSL_ERROR_WANT_WRITE) { mutex_unlock(conn->ssl_mutex); return 0; } else { error(0, "SSL write failed: OpenSSL error %d: %s", SSL_error, ERR_error_string(SSL_error, NULL)); mutex_unlock(conn->ssl_mutex); return -1; } } mutex_unlock(conn->ssl_mutex); } else#endif /* HAVE_LIBSSL */ ret = octstr_write_data(conn->outbuf, conn->fd, conn->outbufpos); if (ret < 0) return -1; conn->outbufpos += ret; /* Heuristic: Discard the already-written data if it's more than * half of the total. This should keep the buffer size small * without wasting too many cycles on moving data around. */ if (conn->outbufpos > octstr_len(conn->outbuf) / 2) { octstr_delete(conn->outbuf, 0, conn->outbufpos); conn->outbufpos = 0; } if (conn->registered) unlocked_register_pollout(conn, unlocked_outbuf_len(conn) > 0); return ret;}/* Try to empty the output buffer without blocking. Return 0 for success, * 1 if there is still data left in the buffer, and -1 for errors. */static int unlocked_try_write(Connection *conn){ long len; len = unlocked_outbuf_len(conn); if (len == 0) return 0; if (len < (long) conn->output_buffering) return 1; if (unlocked_write(conn) < 0) return -1; if (unlocked_outbuf_len(conn) > 0) return 1; return 0;}/* Read whatever data is currently available, up to an internal maximum. */static void unlocked_read(Connection *conn){ unsigned char buf[4096]; long len; if (conn->inbufpos > 0) { octstr_delete(conn->inbuf, 0, conn->inbufpos); conn->inbufpos = 0; }#ifdef HAVE_LIBSSL if (conn->ssl != NULL) { mutex_lock(conn->ssl_mutex); len = SSL_read(conn->ssl, buf, sizeof(buf)); if (len < 0) { int SSL_error = SSL_get_error(conn->ssl, len); if (SSL_error == SSL_ERROR_WANT_WRITE) { len = SSL_write(conn->ssl, NULL, 0); mutex_unlock(conn->ssl_mutex); return; } else if (SSL_error == SSL_ERROR_WANT_READ) { mutex_unlock(conn->ssl_mutex); return; } else error(0, "SSL read failed: OpenSSL error %d: %s", SSL_error, ERR_error_string(SSL_error, NULL)); } mutex_unlock(conn->ssl_mutex); } else#endif /* HAVE_LIBSSL */ len = read(conn->fd, buf, sizeof(buf)); if (len < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return; error(errno, "Error reading from fd %d:", conn->fd); conn->read_error = 1; if (conn->registered) unlocked_register_pollin(conn, 0); return; } else if (len == 0) { conn->read_eof = 1; if (conn->registered) unlocked_register_pollin(conn, 0); } else { octstr_append_data(conn->inbuf, buf, len); }}/* Cut "length" octets from the input buffer and return them as an Octstr */static Octstr *unlocked_get(Connection *conn, long length){ Octstr *result = NULL; gw_assert(unlocked_inbuf_len(conn) >= length); result = octstr_copy(conn->inbuf, conn->inbufpos, length); conn->inbufpos += length; return result;}/* Tell the fdset whether we are interested in POLLIN events, but only * if the status changed. (Calling fdset_listen can be expensive if * it requires synchronization with the polling thread.) * We must already have the inlock. */static void unlocked_register_pollin(Connection *conn, int onoff){ gw_assert(conn->registered); if (onoff == 1 && !conn->listening_pollin) { /* Turn it on */ conn->listening_pollin = 1; fdset_listen(conn->registered, conn->fd, POLLIN, POLLIN); } else if (onoff == 0 && conn->listening_pollin) { /* Turn it off */ conn->listening_pollin = 0; fdset_listen(conn->registered, conn->fd, POLLIN, 0); }}/* Tell the fdset whether we are interested in POLLOUT events, but only * if the status changed. (Calling fdset_listen can be expensive if * it requires synchronization with the polling thread.) * We must already have the outlock. */static void unlocked_register_pollout(Connection *conn, int onoff){ gw_assert(conn->registered); if (onoff == 1 && !conn->listening_pollout) { /* Turn it on */ conn->listening_pollout = 1; fdset_listen(conn->registered, conn->fd, POLLOUT, POLLOUT); } else if (onoff == 0 && conn->listening_pollout) { /* Turn it off */ conn->listening_pollout = 0; fdset_listen(conn->registered, conn->fd, POLLOUT, 0); }}#ifdef HAVE_LIBSSLConnection *conn_open_ssl(Octstr *host, int port, Octstr *certkeyfile, Octstr *our_host){ Connection *ret; int SSL_ret = 0; int connected = 0; time_t timeout; /* open the TCP connection */ if (!(ret = conn_open_tcp(host, port, our_host))) { return NULL; } ret->ssl = SSL_new(global_ssl_context); ret->ssl_mutex = mutex_create(); SSL_set_connect_state(ret->ssl); if (certkeyfile != NULL) { SSL_use_certificate_file(ret->ssl, octstr_get_cstr(certkeyfile), SSL_FILETYPE_PEM); SSL_use_PrivateKey_file(ret->ssl, octstr_get_cstr(certkeyfile), SSL_FILETYPE_PEM); if (SSL_check_private_key(ret->ssl) != 1) { error(0, "conn_open_ssl: private key isn't consistent with the " "certificate from file %s (or failed reading the file)", octstr_get_cstr(certkeyfile)); goto error; } } SSL_set_fd(ret->ssl, ret->fd); /* * The current thread's error queue must be empty before * the TLS/SSL I/O operation is attempted, or SSL_get_error() * will not work reliably. */ ERR_clear_error(); /* * make the socket is non-blocking while we do SSL_connect */ if (socket_set_blocking(ret->fd, 0) < 0) { goto error; } /* record current time */ timeout = time(NULL); while(!connected && (timeout + SSL_CONN_TIMEOUT > time(NULL))) { /* Attempt to connect as long as the timeout hasn't run down */ SSL_ret = SSL_connect(ret->ssl); switch(SSL_get_error(ret->ssl,SSL_ret)) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: /* non-blocking socket wants more time to read or write */ gwthread_sleep(0.01F); continue; default: /* we're connected to the server successfuly */ connected++; } } if (!connected) { /* connection timed out - this probably means that something is terrible wrong */ int SSL_error = SSL_get_error (ret->ssl, SSL_ret); error(0,"SSL connection timeout: OpenSSL error %d: %s", SSL_error, ERR_error_string(SSL_error, NULL)); goto error; } /* * XXX - restore the non-blocking state * we don't need this since we use non-blocking operations * anyway before doing SSL_connect(), right?! */ /* if (socket_set_blocking(ret->fd, 0) < 0) { goto error; } */ if (SSL_ret != 1) { int SSL_error = SSL_get_error (ret->ssl, SSL_ret); error(0, "SSL connect failed: OpenSSL error %d: %s", SSL_error, ERR_error_string(SSL_error, NULL)); goto error; } return ret;error: conn_destroy(ret); return NULL;}#endif /* HAVE_LIBSSL */Connection *conn_open_tcp(Octstr *host, int port, Octstr *our_host){ return conn_open_tcp_with_port(host, port, 0, our_host);}Connection *conn_open_tcp_nb(Octstr *host, int port, Octstr *our_host){ return conn_open_tcp_nb_with_port(host, port, 0, our_host);}Connection *conn_open_tcp_nb_with_port(Octstr *host, int port, int our_port, Octstr *our_host){ int sockfd; int done = -1; Connection *c; sockfd = tcpip_connect_nb_to_server_with_port(octstr_get_cstr(host), port, our_port, our_host == NULL ? NULL : octstr_get_cstr(our_host), &done); if (sockfd < 0) return NULL; c = conn_wrap_fd(sockfd, 0); if (done != 0) { c->connected = no; } return c;}int conn_is_connected(Connection *conn) { if (conn->connected == yes) return 0; return -1;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?