From 4391d0f3b1d03097f8e4fffd24857e54f8ea1eec Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Wed, 14 Mar 2001 19:53:12 +0000 Subject: Added 2001-03-14 Jeffrey Stedfast * camel-tcp-stream-openssl.[c,h]: Added * Makefile.am: Added camel-tcp-stream-openssl.[c,h] to the build. * camel-types.h: Added CamelTcpStreamSSL and CamelTcpStreamOpenSSL svn path=/trunk/; revision=8710 --- camel/camel-tcp-stream-openssl.c | 531 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 camel/camel-tcp-stream-openssl.c (limited to 'camel/camel-tcp-stream-openssl.c') diff --git a/camel/camel-tcp-stream-openssl.c b/camel/camel-tcp-stream-openssl.c new file mode 100644 index 0000000000..d0a6d3f95b --- /dev/null +++ b/camel/camel-tcp-stream-openssl.c @@ -0,0 +1,531 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2001 Ximian, Inc. (www.ximian.com) + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#include + +#ifdef HAVE_OPENSSL +#include "camel-tcp-stream-openssl.h" +#include "camel-operation.h" +#include +#include +#include +#include +#include +#include +#include + +static CamelTcpStreamClass *parent_class = NULL; + +/* Returns the class for a CamelTcpStreamOpenSSL */ +#define CTSR_CLASS(so) CAMEL_TCP_STREAM_OPENSSL_CLASS (CAMEL_OBJECT_GET_CLASS (so)) + +static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n); +static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n); +static int stream_flush (CamelStream *stream); +static int stream_close (CamelStream *stream); + +static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port); +static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); +static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); + +static void +camel_tcp_stream_openssl_class_init (CamelTcpStreamOpenSSLClass *camel_tcp_stream_openssl_class) +{ + CamelTcpStreamClass *camel_tcp_stream_class = + CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_openssl_class); + CamelStreamClass *camel_stream_class = + CAMEL_STREAM_CLASS (camel_tcp_stream_openssl_class); + + parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ())); + + /* virtual method overload */ + camel_stream_class->read = stream_read; + camel_stream_class->write = stream_write; + camel_stream_class->flush = stream_flush; + camel_stream_class->close = stream_close; + + camel_tcp_stream_class->connect = stream_connect; + camel_tcp_stream_class->getsockopt = stream_getsockopt; + camel_tcp_stream_class->setsockopt = stream_setsockopt; +} + +static void +camel_tcp_stream_openssl_init (gpointer object, gpointer klass) +{ + CamelTcpStreamOpenSSL *stream = CAMEL_TCP_STREAM_OPENSSL (object); + + stream->sockfd = -1; + stream->ssl = NULL; +} + +static void +camel_tcp_stream_openssl_finalize (CamelObject *object) +{ + CamelTcpStreamOpenSSL *stream = CAMEL_TCP_STREAM_OPENSSL (object); + + if (stream->ssl) { + SSL_shutdown (stream->ssl); + + if (stream->ssl->ctx) + SSL_CTX_free (stream->ssl->ctx); + + SSL_free (stream->ssl); + } + + if (stream->sockfd != -1) + close (stream->sockfd); +} + + +CamelType +camel_tcp_stream_openssl_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register (camel_tcp_stream_get_type (), + "CamelTcpStreamOpenSSL", + sizeof (CamelTcpStreamOpenSSL), + sizeof (CamelTcpStreamOpenSSLClass), + (CamelObjectClassInitFunc) camel_tcp_stream_openssl_class_init, + NULL, + (CamelObjectInitFunc) camel_tcp_stream_openssl_init, + (CamelObjectFinalizeFunc) camel_tcp_stream_openssl_finalize); + } + + return type; +} + + +/** + * camel_tcp_stream_openssl_new: + * + * Return value: a tcp stream + **/ +CamelStream * +camel_tcp_stream_openssl_new () +{ + CamelTcpStreamOpenSSL *stream; + + stream = CAMEL_TCP_STREAM_OPENSSL (camel_object_new (camel_tcp_stream_openssl_get_type ())); + + return CAMEL_STREAM (stream); +} + +static ssize_t +stream_read (CamelStream *stream, char *buffer, size_t n) +{ + CamelTcpStreamOpenSSL *tcp_stream_openssl = CAMEL_TCP_STREAM_OPENSSL (stream); + ssize_t nread; + int cancel_fd; + + if (camel_operation_cancel_check (NULL)) { + errno = EINTR; + return -1; + } + + cancel_fd = camel_operation_cancel_fd (NULL); + if (cancel_fd == -1) { + do { + nread = SSL_read (tcp_stream_openssl->ssl, buffer, n); + } while (nread == -1 && errno == EINTR); + } else { + int flags, fdmax; + fd_set rdset; + + flags = fcntl (tcp_stream_openssl->sockfd, F_GETFL); + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags | O_NONBLOCK); + + FD_ZERO (&rdset); + FD_SET (tcp_stream_openssl->sockfd, &rdset); + FD_SET (cancel_fd, &rdset); + fdmax = MAX (tcp_stream_openssl->sockfd, cancel_fd) + 1; + + select (fdmax, &rdset, 0, 0, NULL); + if (FD_ISSET (cancel_fd, &rdset)) { + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags); + errno = EINTR; + return -1; + } + + nread = SSL_read (tcp_stream_openssl->ssl, buffer, n); + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags); + } + + return nread; +} + +static ssize_t +stream_write (CamelStream *stream, const char *buffer, size_t n) +{ + CamelTcpStreamOpenSSL *tcp_stream_openssl = CAMEL_TCP_STREAM_OPENSSL (stream); + ssize_t w, written = 0; + int cancel_fd; + + if (camel_operation_cancel_check (NULL)) { + errno = EINTR; + return -1; + } + + cancel_fd = camel_operation_cancel_fd (NULL); + if (cancel_fd == -1) { + do { + written = SSL_write (tcp_stream_openssl->ssl, buffer, n); + } while (written == -1 && errno == EINTR); + } else { + fd_set rdset, wrset; + int flags, fdmax; + + flags = fcntl (tcp_stream_openssl->sockfd, F_GETFL); + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags | O_NONBLOCK); + + fdmax = MAX (tcp_stream_openssl->sockfd, cancel_fd) + 1; + do { + FD_ZERO (&rdset); + FD_ZERO (&wrset); + FD_SET (tcp_stream_openssl->sockfd, &wrset); + FD_SET (cancel_fd, &rdset); + + select (fdmax, &rdset, &wrset, 0, NULL); + if (FD_ISSET (cancel_fd, &rdset)) { + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags); + errno = EINTR; + return -1; + } + + w = SSL_write (tcp_stream_openssl->ssl, buffer + written, n - written); + if (w > 0) + written += w; + } while (w != -1 && written < n); + + fcntl (tcp_stream_openssl->sockfd, F_SETFL, flags); + } + + return written; +} + +static int +stream_flush (CamelStream *stream) +{ + return fsync (((CamelTcpStreamOpenSSL *)stream)->sockfd); +} + + +static void +close_ssl_connection (SSL *ssl) +{ + if (ssl) { + SSL_shutdown (ssl); + + if (ssl->ctx) + SSL_CTX_free (ssl->ctx); + + SSL_free (ssl); + } +} + +static int +stream_close (CamelStream *stream) +{ + close_ssl_connection (((CamelTcpStreamOpenSSL *)stream)->ssl); + ((CamelTcpStreamOpenSSL *)stream)->ssl = NULL; + + if (close (((CamelTcpStreamOpenSSL *)stream)->sockfd) == -1) + return -1; + + ((CamelTcpStreamOpenSSL *)stream)->sockfd = -1; + return 0; +} + +/* this is a 'cancellable' connect, cancellable from camel_operation_cancel etc */ +/* returns -1 & errno == EINTR if the connection was cancelled */ +static int +socket_connect (struct hostent *h, int port) +{ + struct sockaddr_in sin; + int fd; + int ret; + socklen_t len; + struct timeval tv; + int cancel_fd; + + /* see if we're cancelled yet */ + if (camel_operation_cancel_check (NULL)) { + errno = EINTR; + return -1; + } + + /* setup connect, we do it using a nonblocking socket so we can poll it */ + sin.sin_port = htons (port); + sin.sin_family = h->h_addrtype; + memcpy (&sin.sin_addr, h->h_addr, sizeof (sin.sin_addr)); + + fd = socket (h->h_addrtype, SOCK_STREAM, 0); + + cancel_fd = camel_operation_cancel_fd (NULL); + if (cancel_fd == -1) { + ret = connect (fd, (struct sockaddr *)&sin, sizeof (sin)); + if (ret == -1) { + close (fd); + return -1; + } + + return fd; + } else { + fd_set rdset, wrset; + int flags, fdmax; + + flags = fcntl (fd, F_GETFL); + fcntl (fd, F_SETFL, flags | O_NONBLOCK); + + ret = connect (fd, (struct sockaddr *)&sin, sizeof (sin)); + if (ret == 0) { + fcntl (fd, F_SETFL, flags); + return fd; + } + + if (errno != EINPROGRESS) { + close (fd); + return -1; + } + + FD_ZERO (&rdset); + FD_ZERO (&wrset); + FD_SET (fd, &wrset); + FD_SET (cancel_fd, &rdset); + fdmax = MAX (fd, cancel_fd) + 1; + tv.tv_usec = 0; + tv.tv_sec = 60 * 4; + + if (select (fdmax, &rdset, &wrset, 0, &tv) == 0) { + close (fd); + errno = ETIMEDOUT; + return -1; + } + + if (cancel_fd != -1 && FD_ISSET (cancel_fd, &rdset)) { + close (fd); + errno = EINTR; + return -1; + } else { + len = sizeof (int); + + if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &ret, &len) == -1) { + close (fd); + return -1; + } + + if (ret != 0) { + close (fd); + errno = ret; + return -1; + } + } + + fcntl (fd, F_SETFL, flags); + } + + return fd; +} + +static int +verify_callback (int ok, X509_STORE_CTX *ctx) +{ + char *str, buf[256]; + X509 *cert; + int err; + + cert = X509_STORE_CTX_get_current_cert (ctx); + err = X509_STORE_CTX_get_error (ctx); + + str = X509_NAME_oneline (X509_get_subject_name (cert), buf, 256); + if (str) { + if (ok) + d(fprintf (stderr, "CamelTcpStreamSSL: depth=%d %s\n", ctx->error_depth, buf)); + else + d(fprintf (stderr, "CamelTcpStreamSSL: depth=%d error=%d %s\n", + ctx->error_depth, err, buf)); + } + + if (!ok) { + switch (err) { + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /* FIXME: get user's response */ + ok = 1; + } + } + + return ok; +} + +static SSL * +open_ssl_connection (int sockfd) +{ + SSL_CTX *ssl_ctx = NULL; + SSL *ssl = NULL; + int n; + + /* SSLv23_client_method will negotiate with SSL v2, v3, or TLS v1 */ + ssl_ctx = SSL_CTX_new (SSLv23_client_method ()); + SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_PEER, &verify_cb); + ssl = SSL_new (ssl_ctx); + SSL_set_fd (ssl, sockfd); + + n = SSL_connect (ssl); + if (n != 1) { + SSL_shutdown (ssl); + + if (ssl->ctx) + SSL_CTX_free (ssl->ctx); + + SSL_free (ssl); + ssl = NULL; + } + + return ssl; +} + +static int +stream_connect (CamelTcpStream *stream, struct hostent *host, int port) +{ + CamelTcpStreamOpenSSL *openssl = CAMEL_TCP_STREAM_OPENSSL (stream); + SSL *ssl; + int fd; + + g_return_val_if_fail (host != NULL, -1); + + fd = socket_connect (host, port); + if (fd == -1) + return -1; + + ssl = open_ssl_connection (sockfd); + if (!ssl) + return -1; + + openssl->sockfd = fd; + openssl->ssl = ssl; + + return 0; +} + + +static int +get_sockopt_level (const CamelSockOptData *data) +{ + switch (data->option) { + case CAMEL_SOCKOPT_MAXSEGMENT: + case CAMEL_SOCKOPT_NODELAY: + return IPPROTO_TCP; + default: + return SOL_SOCKET; + } +} + +static int +get_sockopt_optname (const CamelSockOptData *data) +{ + switch (data->option) { + case CAMEL_SOCKOPT_MAXSEGMENT: + return TCP_MAXSEG; + case CAMEL_SOCKOPT_NODELAY: + return TCP_NODELAY; + case CAMEL_SOCKOPT_BROADCAST: + return SO_BROADCAST; + case CAMEL_SOCKOPT_KEEPALIVE: + return SO_KEEPALIVE; + case CAMEL_SOCKOPT_LINGER: + return SO_LINGER; + case CAMEL_SOCKOPT_RECVBUFFERSIZE: + return SO_RCVBUF; + case CAMEL_SOCKOPT_SENDBUFFERSIZE: + return SO_SNDBUF; + case CAMEL_SOCKOPT_REUSEADDR: + return SO_REUSEADDR; + case CAMEL_SOCKOPT_IPTYPEOFSERVICE: + return SO_TYPE; + default: + return -1; + } +} + +static int +stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) +{ + int optname, optlen; + + if ((optname = get_sockopt_optname (data)) == -1) + return -1; + + if (data->option == CAMEL_SOCKOPT_NONBLOCKING) { + int flags; + + flags = fcntl (((CamelTcpStreamOpenSSL *)stream)->sockfd, F_GETFL); + if (flags == -1) + return -1; + + data->value.non_blocking = flags & O_NONBLOCK; + + return 0; + } + + return getsockopt (((CamelTcpStreamOpenSSL *)stream)->sockfd, + get_sockopt_level (data), + optname, + (void *) &data->value, + &optlen); +} + +static int +stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) +{ + int optname; + + if ((optname = get_sockopt_optname (data)) == -1) + return -1; + + if (data->option == CAMEL_SOCKOPT_NONBLOCKING) { + int flags, set; + + flags = fcntl (((CamelTcpStreamOpenSSL *)stream)->sockfd, F_GETFL); + if (flags == -1) + return -1; + + set = data->value.non_blocking ? 1 : 0; + flags = (flags & ~O_NONBLOCK) | (set & O_NONBLOCK); + + if (fcntl (((CamelTcpStreamOpenSSL *)stream)->sockfd, F_SETFL, flags) == -1) + return -1; + + return 0; + } + + return setsockopt (((CamelTcpStreamOpenSSL *)stream)->sockfd, + get_sockopt_level (data), + optname, + (void *) &data->value, + sizeof (data->value)); +} + +#endif /* HAVE_OPENSSL */ -- cgit v1.2.3