From bdb8a0a993163256b4e3c6d10ea9e03140c474b4 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Wed, 6 Mar 2002 00:33:37 +0000 Subject: Start the ssl stream off in non-ssl mode (useful for STARTTLS). 2002-03-05 Jeffrey Stedfast * camel-tcp-stream-openssl.c (camel_tcp_stream_openssl_new_raw): Start the ssl stream off in non-ssl mode (useful for STARTTLS). (camel_tcp_stream_openssl_enable_ssl): New function to toggle an ssl stream into ssl mode. (open_ssl_connection): Close the sockfd on fail so our caller doesn't have to - this also allows us to save the original errno. (stream_connect): If we want ssl mode, do our ssl stuff. (camel_tcp_stream_openssl_class_init): Init some SSL stuff here instead of in open_ssl_connection since these only ever need to be called once. (stream_read): Only use SSL_read if we are in ssl mode. (stream_write): Only use SSL_write if we are in ssl mode. * providers/smtp/camel-smtp-transport.c (smtp_helo): Check for the STARTTLS extension. (connect_to_server): Try to use STARTTLS whenever possible rather than the old way of doing things. (connect_to_server_wrapper): Wrapper around connect_to_server() to first try STARTTLS and then attempt normal SSL mode if we can't connect via STARTTLS. * camel-tcp-stream-ssl.c (camel_tcp_stream_ssl_enable_ssl): New function to toggle an ssl stream into ssl mode. (camel_tcp_stream_ssl_new_raw): Start the ssl stream off in non-ssl mode (useful for STARTTLS). (stream_connect): Only connect in SSL mode if required. svn path=/trunk/; revision=15937 --- camel/ChangeLog | 29 ++++++ camel/camel-tcp-stream-openssl.c | 154 +++++++++++++++++++++------- camel/camel-tcp-stream-openssl.h | 4 + camel/camel-tcp-stream-ssl.c | 136 +++++++++++++++++++++--- camel/camel-tcp-stream-ssl.h | 4 + camel/providers/smtp/camel-smtp-provider.c | 36 +++---- camel/providers/smtp/camel-smtp-transport.c | 141 +++++++++++++++++++++++-- camel/providers/smtp/camel-smtp-transport.h | 13 ++- 8 files changed, 433 insertions(+), 84 deletions(-) (limited to 'camel') diff --git a/camel/ChangeLog b/camel/ChangeLog index 3820ce8426..23d5f21e78 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,32 @@ +2002-03-05 Jeffrey Stedfast + + * camel-tcp-stream-openssl.c (camel_tcp_stream_openssl_new_raw): + Start the ssl stream off in non-ssl mode (useful for STARTTLS). + (camel_tcp_stream_openssl_enable_ssl): New function to toggle an + ssl stream into ssl mode. + (open_ssl_connection): Close the sockfd on fail so our caller + doesn't have to - this also allows us to save the original errno. + (stream_connect): If we want ssl mode, do our ssl stuff. + (camel_tcp_stream_openssl_class_init): Init some SSL stuff here + instead of in open_ssl_connection since these only ever need to be + called once. + (stream_read): Only use SSL_read if we are in ssl mode. + (stream_write): Only use SSL_write if we are in ssl mode. + + * providers/smtp/camel-smtp-transport.c (smtp_helo): Check for the + STARTTLS extension. + (connect_to_server): Try to use STARTTLS whenever possible rather + than the old way of doing things. + (connect_to_server_wrapper): Wrapper around connect_to_server() to + first try STARTTLS and then attempt normal SSL mode if we can't + connect via STARTTLS. + + * camel-tcp-stream-ssl.c (camel_tcp_stream_ssl_enable_ssl): New + function to toggle an ssl stream into ssl mode. + (camel_tcp_stream_ssl_new_raw): Start the ssl stream off in + non-ssl mode (useful for STARTTLS). + (stream_connect): Only connect in SSL mode if required. + 2002-03-01 Jeffrey Stedfast * camel-vtrash-folder.h: diff --git a/camel/camel-tcp-stream-openssl.c b/camel/camel-tcp-stream-openssl.c index cceb9975b7..a8256bf0ae 100644 --- a/camel/camel-tcp-stream-openssl.c +++ b/camel/camel-tcp-stream-openssl.c @@ -60,12 +60,15 @@ static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); static gpointer stream_get_socket (CamelTcpStream *stream); +static SSL *open_ssl_connection (CamelService *service, int sockfd, CamelTcpStreamOpenSSL *openssl); + struct _CamelTcpStreamOpenSSLPrivate { int sockfd; SSL *ssl; CamelService *service; char *expected_host; + gboolean ssl_mode; }; static void @@ -88,6 +91,10 @@ camel_tcp_stream_openssl_class_init (CamelTcpStreamOpenSSLClass *camel_tcp_strea camel_tcp_stream_class->getsockopt = stream_getsockopt; camel_tcp_stream_class->setsockopt = stream_setsockopt; camel_tcp_stream_class->get_socket = stream_get_socket; + + /* init OpenSSL stuff */ + SSLeay_add_ssl_algorithms (); + SSL_load_error_strings (); } static void @@ -152,7 +159,7 @@ camel_tcp_stream_openssl_get_type (void) * user, a CamelService is needed. @expected_host is needed as a * protection against an MITM attack. * - * Return value: a tcp stream + * Return value: a ssl stream (in ssl mode) **/ CamelStream * camel_tcp_stream_openssl_new (CamelService *service, const char *expected_host) @@ -163,10 +170,38 @@ camel_tcp_stream_openssl_new (CamelService *service, const char *expected_host) stream->priv->service = service; stream->priv->expected_host = g_strdup (expected_host); + stream->priv->ssl_mode = TRUE; + + return CAMEL_STREAM (stream); +} + + +/** + * camel_tcp_stream_openssl_new_raw: + * @service: camel service + * @expected_host: host that the stream is expecting to connect with. + * + * Since the SSL certificate authenticator may need to prompt the + * user, a CamelService is needed. @expected_host is needed as a + * protection against an MITM attack. + * + * Return value: a ssl-capable stream (in non ssl mode) + **/ +CamelStream * +camel_tcp_stream_openssl_new_raw (CamelService *service, const char *expected_host) +{ + CamelTcpStreamOpenSSL *stream; + + stream = CAMEL_TCP_STREAM_OPENSSL (camel_object_new (camel_tcp_stream_openssl_get_type ())); + + stream->priv->service = service; + stream->priv->expected_host = g_strdup (expected_host); + stream->priv->ssl_mode = FALSE; return CAMEL_STREAM (stream); } + static int ssl_errno (SSL *ssl, int ret) { @@ -193,11 +228,41 @@ ssl_errno (SSL *ssl, int ret) } } + +/** + * camel_tcp_stream_openssl_enable_ssl: + * @stream: ssl stream + * + * Toggles an ssl-capable stream into ssl mode (if it isn't already). + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_tcp_stream_openssl_enable_ssl (CamelTcpStreamOpenSSL *stream) +{ + SSL *ssl; + + g_return_val_if_fail (CAMEL_IS_TCP_STREAM_OPENSSL (ssl), -1); + + if (stream->priv->sockfd && !stream->priv->ssl_mode) { + ssl = open_ssl_connection (stream->priv->service, stream->priv->sockfd, stream); + if (ssl == NULL) + return -1; + + ssl->priv->ssl = ssl; + } + + ssl->priv->ssl_mode = TRUE; + + return 0; +} + + static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n) { - CamelTcpStreamOpenSSL *tcp_stream_openssl = CAMEL_TCP_STREAM_OPENSSL (stream); - SSL *ssl = tcp_stream_openssl->priv->ssl; + CamelTcpStreamOpenSSL *openssl = CAMEL_TCP_STREAM_OPENSSL (stream); + SSL *ssl = openssl->priv->ssl; ssize_t nread; int cancel_fd; @@ -209,40 +274,48 @@ stream_read (CamelStream *stream, char *buffer, size_t n) cancel_fd = camel_operation_cancel_fd (NULL); if (cancel_fd == -1) { do { - nread = SSL_read (ssl, buffer, n); - if (nread < 0) - errno = ssl_errno (ssl, nread); - } while (nread < 0 && (errno == EINTR || errno == EAGAIN)); + if (ssl) { + nread = SSL_read (ssl, buffer, n); + if (nread < 0) + errno = ssl_errno (ssl, nread); + } else { + nread = read (openssl->priv->sockfd, buffer, n); + } + } while (nread < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); } else { int error, flags, fdmax; fd_set rdset; - flags = fcntl (tcp_stream_openssl->priv->sockfd, F_GETFL); - fcntl (tcp_stream_openssl->priv->sockfd, F_SETFL, flags | O_NONBLOCK); + flags = fcntl (openssl->priv->sockfd, F_GETFL); + fcntl (openssl->priv->sockfd, F_SETFL, flags | O_NONBLOCK); - fdmax = MAX (tcp_stream_openssl->priv->sockfd, cancel_fd) + 1; + fdmax = MAX (openssl->priv->sockfd, cancel_fd) + 1; do { FD_ZERO (&rdset); - FD_SET (tcp_stream_openssl->priv->sockfd, &rdset); + FD_SET (openssl->priv->sockfd, &rdset); FD_SET (cancel_fd, &rdset); select (fdmax, &rdset, 0, 0, NULL); if (FD_ISSET (cancel_fd, &rdset)) { - fcntl (tcp_stream_openssl->priv->sockfd, F_SETFL, flags); + fcntl (openssl->priv->sockfd, F_SETFL, flags); errno = EINTR; return -1; } do { - nread = SSL_read (ssl, buffer, n); - if (nread < 0) - errno = ssl_errno (ssl, nread); + if (ssl) { + nread = SSL_read (ssl, buffer, n); + if (nread < 0) + errno = ssl_errno (ssl, nread); + } else { + nread = read (openssl->priv->sockfd, buffer, n); + } } while (nread < 0 && errno == EINTR); - } while (nread < 0 && errno == EAGAIN); + } while (nread < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)); error = errno; - fcntl (tcp_stream_openssl->priv->sockfd, F_SETFL, flags); + fcntl (openssl->priv->sockfd, F_SETFL, flags); errno = error; } @@ -252,8 +325,8 @@ stream_read (CamelStream *stream, char *buffer, size_t n) static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n) { - CamelTcpStreamOpenSSL *tcp_stream_openssl = CAMEL_TCP_STREAM_OPENSSL (stream); - SSL *ssl = tcp_stream_openssl->priv->ssl; + CamelTcpStreamOpenSSL *openssl = CAMEL_TCP_STREAM_OPENSSL (stream); + SSL *ssl = openssl->priv->ssl; ssize_t w, written = 0; int cancel_fd; @@ -266,10 +339,14 @@ stream_write (CamelStream *stream, const char *buffer, size_t n) if (cancel_fd == -1) { do { do { - w = SSL_write (ssl, buffer + written, n - written); - if (w < 0) - errno = ssl_errno (ssl, w); - } while (w < 0 && (errno == EINTR || errno == EAGAIN)); + if (ssl) { + w = SSL_write (ssl, buffer + written, n - written); + if (w < 0) + errno = ssl_errno (ssl, w); + } else { + w = write (openssl->priv->sockfd, buffer + written, n - written); + } + } while (w < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); if (w > 0) written += w; @@ -296,13 +373,17 @@ stream_write (CamelStream *stream, const char *buffer, size_t n) } do { - w = SSL_write (ssl, buffer + written, n - written); - if (w < 0) - errno = ssl_errno (ssl, w); + if (ssl) { + w = SSL_write (ssl, buffer + written, n - written); + if (w < 0) + errno = ssl_errno (ssl, w); + } else { + w = write (openssl->priv->sockfd, buffer + written, n - written); + } } while (w < 0 && errno == EINTR); if (w < 0) { - if (errno == EAGAIN) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { w = 0; } else { error = errno; @@ -542,9 +623,6 @@ open_ssl_connection (CamelService *service, int sockfd, CamelTcpStreamOpenSSL *o SSL *ssl = NULL; int n; - SSLeay_add_ssl_algorithms(); - SSL_load_error_strings(); - /* SSLv23_client_method will negotiate with SSL v2, v3, or TLS v1 */ ssl_ctx = SSL_CTX_new (SSLv23_client_method ()); g_return_val_if_fail (ssl_ctx != NULL, NULL); @@ -557,7 +635,7 @@ open_ssl_connection (CamelService *service, int sockfd, CamelTcpStreamOpenSSL *o n = SSL_connect (ssl); if (n != 1) { - errno = ssl_errno (ssl, n); + int errnosave = ssl_errno (ssl, n); SSL_shutdown (ssl); @@ -566,6 +644,10 @@ open_ssl_connection (CamelService *service, int sockfd, CamelTcpStreamOpenSSL *o SSL_free (ssl); ssl = NULL; + + close (sockfd); + + errno = errnosave; } return ssl; @@ -575,7 +657,7 @@ static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port) { CamelTcpStreamOpenSSL *openssl = CAMEL_TCP_STREAM_OPENSSL (stream); - SSL *ssl; + SSL *ssl = NULL; int fd; g_return_val_if_fail (host != NULL, -1); @@ -584,9 +666,11 @@ stream_connect (CamelTcpStream *stream, struct hostent *host, int port) if (fd == -1) return -1; - ssl = open_ssl_connection (openssl->priv->service, fd, openssl); - if (!ssl) - return -1; + if (openssl->priv->ssl_mode) { + ssl = open_ssl_connection (openssl->priv->service, fd, openssl); + if (!ssl) + return -1; + } openssl->priv->sockfd = fd; openssl->priv->ssl = ssl; diff --git a/camel/camel-tcp-stream-openssl.h b/camel/camel-tcp-stream-openssl.h index 2d428c628d..9509eaf343 100644 --- a/camel/camel-tcp-stream-openssl.h +++ b/camel/camel-tcp-stream-openssl.h @@ -57,6 +57,10 @@ CamelType camel_tcp_stream_openssl_get_type (void); /* public methods */ CamelStream *camel_tcp_stream_openssl_new (CamelService *service, const char *expected_host); +CamelStream *camel_tcp_stream_openssl_new_raw (CamelService *service, const char *expected_host); + +int camel_tcp_stream_openssl_enable_ssl (CamelTcpStreamOpenSSL *ssl); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c index 50c4684980..9b467b3463 100644 --- a/camel/camel-tcp-stream-ssl.c +++ b/camel/camel-tcp-stream-ssl.c @@ -60,6 +60,8 @@ 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 PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd); + 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); @@ -70,6 +72,7 @@ struct _CamelTcpStreamSSLPrivate { CamelService *service; char *expected_host; + gboolean ssl_mode; }; static void @@ -135,6 +138,7 @@ camel_tcp_stream_ssl_get_type (void) return type; } + /** * camel_tcp_stream_ssl_new: * @service: camel service @@ -144,7 +148,7 @@ camel_tcp_stream_ssl_get_type (void) * user, a CamelService is needed. @expected_host is needed as a * protection against an MITM attack. * - * Return value: a tcp stream + * Return value: a ssl stream (in ssl mode) **/ CamelStream * camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host) @@ -155,10 +159,38 @@ camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host) stream->priv->service = service; stream->priv->expected_host = g_strdup (expected_host); + stream->priv->ssl_mode = TRUE; return CAMEL_STREAM (stream); } + +/** + * camel_tcp_stream_ssl_new_raw: + * @service: camel service + * @expected_host: host that the stream is expected to connect with. + * + * Since the SSL certificate authenticator may need to prompt the + * user, a CamelService is needed. @expected_host is needed as a + * protection against an MITM attack. + * + * Return value: a ssl-capable stream (in non ssl mode) + **/ +CamelStream * +camel_tcp_stream_ssl_new_raw (CamelService *service, const char *expected_host) +{ + CamelTcpStreamSSL *stream; + + stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ())); + + stream->priv->service = service; + stream->priv->expected_host = g_strdup (expected_host); + stream->priv->ssl_mode = FALSE; + + return CAMEL_STREAM (stream); +} + + static void set_errno (int code) { @@ -181,6 +213,45 @@ set_errno (int code) } } + +/** + * camel_tcp_stream_ssl_enable_ssl: + * @ssl: ssl stream + * + * Toggles an ssl-capable stream into ssl mode (if it isn't already). + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl) +{ + PRFileDesc *fd; + + g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (ssl), -1); + + if (ssl->priv->sockfd && !ssl->priv->ssl_mode) { + fd = enable_ssl (ssl, NULL); + if (fd == NULL) { + int errnosave; + + set_errno (PR_GetError ()); + errnosave = errno; + errno = errnosave; + + return -1; + } + + SSL_ResetHandshake (fd, FALSE); + + ssl->priv->sockfd = fd; + } + + ssl->priv->ssl_mode = TRUE; + + return 0; +} + + static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n) { @@ -486,13 +557,34 @@ ssl_bad_cert (void *data, PRFileDesc *sockfd) return SECFailure; } +static PRFileDesc * +enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd) +{ + PRFileDesc *ssl_fd; + + ssl_fd = SSL_ImportFD (NULL, fd ? fd : ssl->priv->sockfd); + if (!ssl_fd) + return NULL; + + SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE); + SSL_SetURL (ssl_fd, ssl->priv->expected_host); + + /*SSL_GetClientAuthDataHook (sslSocket, ssl_get_client_auth, (void *) certNickname);*/ + /*SSL_AuthCertificateHook (ssl_fd, ssl_auth_cert, (void *) CERT_GetDefaultCertDB ());*/ + SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl); + + ssl->priv->ssl_mode = TRUE; + + return ssl_fd; +} + static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port) { CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream); PRIntervalTime timeout = PR_INTERVAL_MIN; PRNetAddr netaddr; - PRFileDesc *fd, *ssl_fd; + PRFileDesc *fd; g_return_val_if_fail (host != NULL, -1); @@ -505,30 +597,42 @@ stream_connect (CamelTcpStream *stream, struct hostent *host, int port) } fd = PR_OpenTCPSocket (host->h_addrtype); - ssl_fd = SSL_ImportFD (NULL, fd); - - SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE); - SSL_SetURL (ssl_fd, ssl->priv->expected_host); + if (fd == NULL) { + set_errno (PR_GetError ()); + return -1; + } - if (ssl_fd == NULL || PR_Connect (ssl_fd, &netaddr, timeout) == PR_FAILURE) { - if (ssl_fd != NULL) { + if (ssl->priv->ssl_mode) { + PRFileDesc *ssl_fd; + + ssl_fd = enable_ssl (ssl, fd); + if (ssl_fd == NULL) { int errnosave; set_errno (PR_GetError ()); errnosave = errno; - PR_Close (ssl_fd); + PR_Close (fd); errno = errnosave; - } else - errno = EINVAL; + + return -1; + } - return -1; + fd = ssl_fd; } - /*SSL_GetClientAuthDataHook (sslSocket, ssl_get_client_auth, (void *) certNickname);*/ - /*SSL_AuthCertificateHook (ssl_fd, ssl_auth_cert, (void *) CERT_GetDefaultCertDB ());*/ - SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl); + if (PR_Connect (fd, &netaddr, timeout) == PR_FAILURE) { + int errnosave; + + set_errno (PR_GetError ()); + errnosave = errno; + PR_Close (fd); + ssl->priv->sockfd = NULL; + errno = errnosave; + + return -1; + } - ssl->priv->sockfd = ssl_fd; + ssl->priv->sockfd = fd; return 0; } diff --git a/camel/camel-tcp-stream-ssl.h b/camel/camel-tcp-stream-ssl.h index a9becb9816..5db4a0eb70 100644 --- a/camel/camel-tcp-stream-ssl.h +++ b/camel/camel-tcp-stream-ssl.h @@ -57,6 +57,10 @@ CamelType camel_tcp_stream_ssl_get_type (void); /* public methods */ CamelStream *camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host); +CamelStream *camel_tcp_stream_ssl_new_raw (CamelService *service, const char *expected_host); + +int camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/camel/providers/smtp/camel-smtp-provider.c b/camel/providers/smtp/camel-smtp-provider.c index 13f0cb6369..48e9bcc6d1 100644 --- a/camel/providers/smtp/camel-smtp-provider.c +++ b/camel/providers/smtp/camel-smtp-provider.c @@ -1,27 +1,27 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-smtp-provider.c: smtp provider registration code */ - -/* - * Authors : - * Jeffrey Stedfast +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) * - * Copyright (C) 2000 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 free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public - * License as published by the Free Software Foundation. + * 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. * - * 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. * - * 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 */ + #ifdef HAVE_CONFIG_H #include #endif @@ -53,8 +53,8 @@ camel_provider_module_init (CamelSession *session) { smtp_provider.object_types[CAMEL_PROVIDER_TRANSPORT] = camel_smtp_transport_get_type (); - smtp_provider.authtypes = g_list_append(camel_sasl_authtype_list(TRUE), camel_sasl_authtype ("LOGIN")); - smtp_provider.authtypes = g_list_append(smtp_provider.authtypes, camel_sasl_authtype ("POPB4SMTP")); + smtp_provider.authtypes = g_list_append (camel_sasl_authtype_list (TRUE), camel_sasl_authtype ("LOGIN")); + smtp_provider.authtypes = g_list_append (smtp_provider.authtypes, camel_sasl_authtype ("POPB4SMTP")); smtp_provider.service_cache = g_hash_table_new (camel_url_hash, camel_url_equal); smtp_provider.url_hash = camel_url_hash; smtp_provider.url_equal = camel_url_equal; diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c index b06b3cb329..c5bf11971e 100644 --- a/camel/providers/smtp/camel-smtp-transport.c +++ b/camel/providers/smtp/camel-smtp-transport.c @@ -92,6 +92,9 @@ static gboolean smtp_data (CamelSmtpTransport *transport, CamelMedium *message, static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex); static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex); +static void smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf, + const char *message, CamelException *ex); + /* private data members */ static CamelTransportClass *parent_class = NULL; @@ -151,11 +154,16 @@ smtp_construct (CamelService *service, CamelSession *session, CamelException *ex) { CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service); + const char *use_ssl; CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex); - if (camel_url_get_param (url, "use_ssl")) - smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL; + if ((use_ssl = camel_url_get_param (url, "use_ssl"))) { + if (!strcmp (use_ssl, "always")) + smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS; + else if (!strcmp (use_ssl, "when-possible")) + smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE; + } } static const char * @@ -228,11 +236,11 @@ smtp_error_string (int error) } static gboolean -connect_to_server (CamelService *service, CamelException *ex) +connect_to_server (CamelService *service, int try_starttls, CamelException *ex) { CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service); CamelStream *tcp_stream; - gchar *respbuf = NULL; + char *respbuf = NULL; struct hostent *h; guint32 addrlen; int port, ret; @@ -247,20 +255,28 @@ connect_to_server (CamelService *service, CamelException *ex) /* set some smtp transport defaults */ transport->flags &= ~(CAMEL_SMTP_TRANSPORT_IS_ESMTP | CAMEL_SMTP_TRANSPORT_8BITMIME | + CAMEL_SMTP_TRANSPORT_STARTTLS | CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES); transport->authtypes = NULL; port = service->url->port ? service->url->port : SMTP_PORT; -#if defined(HAVE_NSS) || defined(HAVE_OPENSSL) +#if defined (HAVE_NSS) || defined (HAVE_OPENSSL) if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) { - port = service->url->port ? service->url->port : 465; + if (!try_starttls) + port = service->url->port ? service->url->port : 465; #ifdef HAVE_NSS /* use the preferred implementation - NSS */ - tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host); + if (try_starttls) + tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host); + else + tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host); #else - tcp_stream = camel_tcp_stream_openssl_new (service, service->url->host); + if (try_starttls) + tcp_stream = camel_tcp_stream_openssl_new_raw (service, service->url->host); + else + tcp_stream = camel_tcp_stream_openssl_new (service, service->url->host); #endif /* HAVE_NSS */ } else { tcp_stream = camel_tcp_stream_raw_new (); @@ -340,7 +356,107 @@ connect_to_server (CamelService *service, CamelException *ex) smtp_helo (transport, ex); } +#if defined (HAVE_NSS) || defined (HAVE_OPENSSL) + if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) { + /* try_starttls is always TRUE here */ + if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS) + goto starttls; + } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) { + if (try_starttls) { + if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS) { + goto starttls; + } else { + /* server doesn't support STARTTLS, abort */ + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to SMTP server %s in secure mode: %s"), + service->url->host, _("server does not appear to support SSL")); + goto exception_cleanup; + } + } + } +#endif /* HAVE_NSS || HAVE_OPENSSL */ + return TRUE; + +#if defined (HAVE_NSS) || defined (HAVE_OPENSSL) + starttls: + d(fprintf (stderr, "sending : STARTTLS\r\n")); + if (camel_stream_write (tcp_stream, "STARTTLS\r\n", 10) == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("STARTTLS request timed out: %s"), + g_strerror (errno)); + goto exception_cleanup; + } + + respbuf = NULL; + + do { + /* Check for "220 Ready for TLS" */ + g_free (respbuf); + respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream)); + + d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)")); + + if (!respbuf || strncmp (respbuf, "220", 3)) { + smtp_set_exception (transport, respbuf, _("STARTTLS response error"), ex); + g_free (respbuf); + goto exception_cleanup; + } + } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */ + + /* Okay, now toggle SSL/TLS mode */ +#ifdef HAVE_NSS + ret = camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)); +#else /* HAVE_OPENSSL */ + ret = camel_tcp_stream_openssl_enable_ssl (CAMEL_TCP_STREAM_OPENSSL (tcp_stream)); +#endif + if (ret != -1) + return TRUE; + + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to SMTP server %s in secure mode: %s"), + service->url->host, g_strerror (errno)); + + exception_cleanup: + camel_object_unref (CAMEL_OBJECT (transport->istream)); + transport->istream = NULL; + camel_object_unref (CAMEL_OBJECT (transport->ostream)); + transport->ostream = NULL; + + return FALSE; +#endif /* HAVE_NSS || HAVE_OPENSSL */ +} + +#define EXCEPTION_RETRY(ex) (camel_exception_get_id (ex) != CAMEL_EXCEPTION_USER_CANCEL && \ + camel_exception_get_id (ex) != CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) + +static gboolean +connect_to_server_wrapper (CamelService *service, CamelException *ex) +{ +#if defined (HAVE_NSS) || defined (HAVE_OPENSSL) + CamelSmtpTransport *transport = (CamelSmtpTransport *) service; + + if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) { + /* First try STARTTLS */ + if (!connect_to_server (service, TRUE, ex) && + !transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS && + EXCEPTION_RETRY (ex)) { + /* STARTTLS is unavailable - okay, now try port 465 */ + camel_exception_clear (ex); + return connect_to_server (service, FALSE, ex); + } + + return TRUE; + } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) { + /* If the server supports STARTTLS, use it */ + return connect_to_server (service, TRUE, ex); + } else { + /* User doesn't care about SSL */ + return connect_to_server (service, FALSE, ex); + } +#else + return connect_to_server (service, FALSE, ex); +#endif } static gboolean @@ -364,10 +480,10 @@ smtp_connect (CamelService *service, CamelException *ex) if (!truth) return FALSE; - return connect_to_server (service, ex); + return connect_to_server_wrapper (service, ex); } - if (!connect_to_server (service, ex)) + if (!connect_to_server_wrapper (service, ex)) return FALSE; /* check to see if AUTH is required, if so...then AUTH ourselves */ @@ -539,7 +655,7 @@ query_auth_types (CamelService *service, CamelException *ex) CamelServiceAuthType *authtype; GList *types, *t, *next; - if (!connect_to_server (service, ex)) + if (!connect_to_server_wrapper (service, ex)) return NULL; types = g_list_copy (service->provider->authtypes); @@ -852,6 +968,9 @@ smtp_helo (CamelSmtpTransport *transport, CamelException *ex) } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) { d(fprintf (stderr, "This server supports enhanced status codes\n")); transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES; + } else if (!strncmp (token, "STARTTLS", 8)) { + d(fprintf (stderr, "This server supports STARTTLS\n")); + transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS; } else if (!transport->authtypes && !strncmp (token, "AUTH", 4)) { /* Don't bother parsing any authtypes if we already have a list. * Some servers will list AUTH twice, once the standard way and diff --git a/camel/providers/smtp/camel-smtp-transport.h b/camel/providers/smtp/camel-smtp-transport.h index 8bba00e987..2f6ac261f4 100644 --- a/camel/providers/smtp/camel-smtp-transport.h +++ b/camel/providers/smtp/camel-smtp-transport.h @@ -47,11 +47,16 @@ extern "C" { #define CAMEL_IS_SMTP_TRANSPORT(o) (CAMEL_CHECK_TYPE((o), CAMEL_SMTP_TRANSPORT_TYPE)) -#define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0) -#define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1) -#define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2) +#define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0) +#define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1) +#define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2) +#define CAMEL_SMTP_TRANSPORT_STARTTLS (1 << 3) -#define CAMEL_SMTP_TRANSPORT_USE_SSL (1 << 3) +#define CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS (1 << 4) +#define CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE (1 << 5) + +#define CAMEL_SMTP_TRANSPORT_USE_SSL (CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS | \ + CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) typedef struct { CamelTransport parent_object; -- cgit v1.2.3