2011-03-06 06:58:58 -05:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <strings.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/select.h>
|
|
|
|
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
#include <openssl/err.h>
|
2012-02-19 06:36:44 -05:00
|
|
|
|
|
|
|
#include "imapfilter.h"
|
|
|
|
#include "session.h"
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect to mail server.
|
|
|
|
*/
|
|
|
|
int
|
2012-02-11 16:47:08 -05:00
|
|
|
open_connection(session *ssn)
|
2011-03-06 06:58:58 -05:00
|
|
|
{
|
|
|
|
struct addrinfo hints, *res, *ressave;
|
|
|
|
int n, sockfd;
|
|
|
|
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
|
|
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
|
2012-02-11 16:47:08 -05:00
|
|
|
n = getaddrinfo(ssn->server, ssn->port, &hints, &res);
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
error("gettaddrinfo; %s\n", gai_strerror(n));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ressave = res;
|
|
|
|
|
|
|
|
sockfd = -1;
|
|
|
|
|
|
|
|
while (res) {
|
|
|
|
sockfd = socket(res->ai_family, res->ai_socktype,
|
|
|
|
res->ai_protocol);
|
|
|
|
|
|
|
|
if (sockfd >= 0) {
|
|
|
|
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
sockfd = -1;
|
|
|
|
}
|
|
|
|
res = res->ai_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ressave)
|
|
|
|
freeaddrinfo(ressave);
|
|
|
|
|
|
|
|
if (sockfd == -1) {
|
|
|
|
error("error while initiating connection to %s at port %s\n",
|
2012-02-11 16:47:08 -05:00
|
|
|
ssn->server, ssn->port);
|
2011-03-06 06:58:58 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssn->socket = sockfd;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
if (ssn->sslproto) {
|
2012-02-11 16:47:08 -05:00
|
|
|
if (open_secure_connection(ssn) == -1) {
|
2011-03-06 06:58:58 -05:00
|
|
|
close_connection(ssn);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ssn->socket;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize SSL/TLS connection.
|
|
|
|
*/
|
|
|
|
int
|
2012-02-11 16:47:08 -05:00
|
|
|
open_secure_connection(session *ssn)
|
2011-03-06 06:58:58 -05:00
|
|
|
{
|
|
|
|
int r, e;
|
|
|
|
SSL_CTX *ctx;
|
2012-04-24 17:09:12 -04:00
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1000000fL
|
2011-04-04 17:44:12 -04:00
|
|
|
const SSL_METHOD *method;
|
2012-04-24 17:09:12 -04:00
|
|
|
#else
|
|
|
|
SSL_METHOD *method;
|
|
|
|
#endif
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
method = NULL;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
if (ssn->sslproto && (!strncasecmp(ssn->sslproto, "ssl3", 4) ||
|
|
|
|
!strncasecmp(ssn->sslproto, "ssl2", 4)))
|
2011-03-06 06:58:58 -05:00
|
|
|
method = SSLv23_client_method();
|
2012-02-11 16:47:08 -05:00
|
|
|
else
|
|
|
|
method = TLSv1_client_method();
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
if (!(ctx = SSL_CTX_new(method)))
|
|
|
|
goto fail;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
if (!(ssn->sslconn = SSL_new(ctx)))
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
SSL_set_fd(ssn->sslconn, ssn->socket);
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
for (;;) {
|
2012-02-23 13:17:41 -05:00
|
|
|
if ((r = SSL_connect(ssn->sslconn)) > 0)
|
2011-03-06 06:58:58 -05:00
|
|
|
break;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
switch (SSL_get_error(ssn->sslconn, r)) {
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
|
|
error("initiating SSL connection to %s; the "
|
2012-02-15 14:48:46 -05:00
|
|
|
"connection has been closed cleanly\n",
|
|
|
|
ssn->server);
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
2012-02-16 12:49:57 -05:00
|
|
|
case SSL_ERROR_NONE:
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
|
|
case SSL_ERROR_WANT_ACCEPT:
|
|
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
break;
|
|
|
|
case SSL_ERROR_SYSCALL:
|
|
|
|
e = ERR_get_error();
|
2012-02-15 14:48:46 -05:00
|
|
|
if (e == 0 && r == 0)
|
|
|
|
error("initiating SSL connection to %s; EOF in "
|
|
|
|
"violation of the protocol\n", ssn->server);
|
|
|
|
else if (e == 0 && r == -1)
|
2011-03-06 06:58:58 -05:00
|
|
|
error("initiating SSL connection to %s; %s\n",
|
2012-02-11 16:47:08 -05:00
|
|
|
ssn->server, strerror(errno));
|
2012-02-15 14:48:46 -05:00
|
|
|
else
|
|
|
|
error("initiating SSL connection to %s; %s\n",
|
|
|
|
ssn->server, ERR_error_string(e, NULL));
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
|
|
|
case SSL_ERROR_SSL:
|
2012-02-15 14:48:46 -05:00
|
|
|
error("initiating SSL connection to %s; %s\n",
|
|
|
|
ssn->server, ERR_error_string(ERR_get_error(),
|
|
|
|
NULL));
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
|
|
|
default:
|
2012-02-16 12:49:57 -05:00
|
|
|
break;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (get_option_boolean("certificates") && get_cert(ssn) == -1)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
2012-02-23 13:17:41 -05:00
|
|
|
ssn->sslconn = NULL;
|
2011-03-06 06:58:58 -05:00
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disconnect from mail server.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
close_connection(session *ssn)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
|
|
|
|
close_secure_connection(ssn);
|
|
|
|
|
|
|
|
if (ssn->socket != -1) {
|
|
|
|
r = close(ssn->socket);
|
|
|
|
ssn->socket = -1;
|
|
|
|
|
|
|
|
if (r == -1)
|
|
|
|
error("closing socket; %s\n", strerror(errno));
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Shutdown SSL/TLS connection.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
close_secure_connection(session *ssn)
|
|
|
|
{
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
if (ssn->sslconn) {
|
|
|
|
SSL_shutdown(ssn->sslconn);
|
|
|
|
SSL_free(ssn->sslconn);
|
|
|
|
ssn->sslconn = NULL;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read data from socket.
|
|
|
|
*/
|
|
|
|
ssize_t
|
|
|
|
socket_read(session *ssn, char *buf, size_t len, long timeout, int timeoutfail)
|
|
|
|
{
|
|
|
|
int s;
|
|
|
|
ssize_t r;
|
|
|
|
fd_set fds;
|
|
|
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
|
|
|
struct timeval *tvp;
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
s = 1;
|
|
|
|
tvp = NULL;
|
|
|
|
|
|
|
|
memset(buf, 0, len + 1);
|
|
|
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
tv.tv_sec = timeout;
|
|
|
|
tv.tv_usec = 0;
|
|
|
|
tvp = &tv;
|
|
|
|
}
|
|
|
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(ssn->socket, &fds);
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
if (ssn->sslconn) {
|
|
|
|
if (SSL_pending(ssn->sslconn) > 0 ||
|
2011-03-06 06:58:58 -05:00
|
|
|
((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
|
|
|
|
FD_ISSET(ssn->socket, &fds))) {
|
|
|
|
r = socket_secure_read(ssn, buf, len);
|
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
goto fail;
|
|
|
|
}
|
2012-02-19 06:36:44 -05:00
|
|
|
} else {
|
2011-03-06 06:58:58 -05:00
|
|
|
if ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
|
|
|
|
FD_ISSET(ssn->socket, &fds)) {
|
|
|
|
r = read(ssn->socket, buf, len);
|
|
|
|
|
|
|
|
if (r == -1) {
|
|
|
|
error("reading data; %s\n", strerror(errno));
|
|
|
|
goto fail;
|
|
|
|
} else if (r == 0) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s == -1) {
|
|
|
|
error("waiting to read from socket; %s\n", strerror(errno));
|
|
|
|
goto fail;
|
|
|
|
} else if (s == 0 && timeoutfail) {
|
|
|
|
error("timeout period expired while waiting to read data\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
fail:
|
|
|
|
close_connection(ssn);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read data from a TLS/SSL connection.
|
|
|
|
*/
|
|
|
|
ssize_t
|
|
|
|
socket_secure_read(session *ssn, char *buf, size_t len)
|
|
|
|
{
|
|
|
|
int r, e;
|
|
|
|
|
|
|
|
for (;;) {
|
2012-02-23 13:17:41 -05:00
|
|
|
if ((r = (ssize_t) SSL_read(ssn->sslconn, buf, len)) > 0)
|
2011-03-06 06:58:58 -05:00
|
|
|
break;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
switch (SSL_get_error(ssn->sslconn, r)) {
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
2012-02-23 13:17:41 -05:00
|
|
|
error("reading data through SSL; the connection has "
|
|
|
|
"been closed cleanly\n");
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2012-02-16 12:49:57 -05:00
|
|
|
case SSL_ERROR_NONE:
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
|
|
case SSL_ERROR_WANT_ACCEPT:
|
|
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
|
|
break;
|
|
|
|
case SSL_ERROR_SYSCALL:
|
|
|
|
e = ERR_get_error();
|
2012-02-15 14:48:46 -05:00
|
|
|
if (e == 0 && r == 0)
|
2012-02-23 13:17:41 -05:00
|
|
|
error("reading data through SSL; EOF in "
|
|
|
|
"violation of the protocol\n");
|
2012-02-15 14:48:46 -05:00
|
|
|
else if (e == 0 && r == -1)
|
2012-02-23 13:17:41 -05:00
|
|
|
error("reading data through SSL; %s\n",
|
|
|
|
strerror(errno));
|
2012-02-15 14:48:46 -05:00
|
|
|
else
|
2012-02-22 12:58:35 -05:00
|
|
|
error("reading data through SSL; %s\n",
|
|
|
|
ERR_error_string(e, NULL));
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_SSL:
|
2012-02-22 12:58:35 -05:00
|
|
|
error("reading data through SSL; %s\n",
|
2011-03-06 06:58:58 -05:00
|
|
|
ERR_error_string(ERR_get_error(), NULL));
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2011-03-06 06:58:58 -05:00
|
|
|
default:
|
2012-02-16 12:49:57 -05:00
|
|
|
break;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
2012-02-23 11:58:54 -05:00
|
|
|
fail:
|
2012-02-23 13:17:41 -05:00
|
|
|
SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN |
|
|
|
|
SSL_RECEIVED_SHUTDOWN);
|
2012-02-23 11:58:54 -05:00
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write data to socket.
|
|
|
|
*/
|
|
|
|
ssize_t
|
|
|
|
socket_write(session *ssn, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
int s;
|
2012-02-16 12:49:57 -05:00
|
|
|
ssize_t r, t;
|
2011-03-06 06:58:58 -05:00
|
|
|
fd_set fds;
|
|
|
|
|
2012-02-16 12:49:57 -05:00
|
|
|
r = t = 0;
|
2011-03-06 06:58:58 -05:00
|
|
|
s = 1;
|
|
|
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(ssn->socket, &fds);
|
|
|
|
|
|
|
|
while (len) {
|
|
|
|
if ((s = select(ssn->socket + 1, NULL, &fds, NULL, NULL) > 0 &&
|
|
|
|
FD_ISSET(ssn->socket, &fds))) {
|
2012-02-23 13:17:41 -05:00
|
|
|
if (ssn->sslconn) {
|
2012-02-16 12:49:57 -05:00
|
|
|
r = socket_secure_write(ssn, buf, len);
|
2011-03-06 06:58:58 -05:00
|
|
|
|
2012-02-16 12:49:57 -05:00
|
|
|
if (r <= 0)
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
2012-02-19 06:36:44 -05:00
|
|
|
} else {
|
2012-02-16 12:49:57 -05:00
|
|
|
r = write(ssn->socket, buf, len);
|
2011-03-06 06:58:58 -05:00
|
|
|
|
2012-02-16 12:49:57 -05:00
|
|
|
if (r == -1) {
|
2011-03-06 06:58:58 -05:00
|
|
|
error("writing data; %s\n",
|
|
|
|
strerror(errno));
|
|
|
|
goto fail;
|
2012-02-16 12:49:57 -05:00
|
|
|
} else if (r == 0) {
|
2011-03-06 06:58:58 -05:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-16 12:49:57 -05:00
|
|
|
if (r > 0) {
|
|
|
|
len -= r;
|
|
|
|
buf += r;
|
|
|
|
t += r;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s == -1) {
|
|
|
|
error("waiting to write to socket; %s\n", strerror(errno));
|
|
|
|
goto fail;
|
|
|
|
} else if (s == 0) {
|
|
|
|
error("timeout period expired while waiting to write data\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2012-02-16 12:49:57 -05:00
|
|
|
return t;
|
2011-03-06 06:58:58 -05:00
|
|
|
fail:
|
|
|
|
close_connection(ssn);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write data to a TLS/SSL connection.
|
|
|
|
*/
|
|
|
|
ssize_t
|
|
|
|
socket_secure_write(session *ssn, const char *buf, size_t len)
|
|
|
|
{
|
2012-02-15 14:48:46 -05:00
|
|
|
int r, e;
|
2011-03-06 06:58:58 -05:00
|
|
|
|
|
|
|
for (;;) {
|
2012-02-23 13:17:41 -05:00
|
|
|
if ((r = (ssize_t) SSL_write(ssn->sslconn, buf, len)) > 0)
|
2011-03-06 06:58:58 -05:00
|
|
|
break;
|
|
|
|
|
2012-02-23 13:17:41 -05:00
|
|
|
switch (SSL_get_error(ssn->sslconn, r)) {
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
2012-02-23 13:17:41 -05:00
|
|
|
error("writing data through SSL; the connection has "
|
|
|
|
"been closed cleanly\n");
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2012-02-16 12:49:57 -05:00
|
|
|
case SSL_ERROR_NONE:
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
|
|
case SSL_ERROR_WANT_ACCEPT:
|
|
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
|
|
break;
|
|
|
|
case SSL_ERROR_SYSCALL:
|
|
|
|
e = ERR_get_error();
|
2012-02-15 14:48:46 -05:00
|
|
|
if (e == 0 && r == 0)
|
2012-02-23 13:17:41 -05:00
|
|
|
error("writing data through SSL; EOF in "
|
|
|
|
"violation of the protocol\n");
|
2012-02-15 14:48:46 -05:00
|
|
|
else if (e == 0 && r == -1)
|
2012-02-23 13:17:41 -05:00
|
|
|
error("writing data through SSL; %s\n",
|
|
|
|
strerror(errno));
|
2012-02-15 14:48:46 -05:00
|
|
|
else
|
2012-02-22 12:58:35 -05:00
|
|
|
error("writing data through SSL; %s\n",
|
|
|
|
ERR_error_string(e, NULL));
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2011-03-06 06:58:58 -05:00
|
|
|
case SSL_ERROR_SSL:
|
2012-02-22 12:58:35 -05:00
|
|
|
error("writing data through SSL; %s\n",
|
2011-03-06 06:58:58 -05:00
|
|
|
ERR_error_string(ERR_get_error(), NULL));
|
2012-02-23 11:58:54 -05:00
|
|
|
goto fail;
|
2011-03-06 06:58:58 -05:00
|
|
|
default:
|
2012-02-16 12:49:57 -05:00
|
|
|
break;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-15 14:48:46 -05:00
|
|
|
return r;
|
2012-02-23 11:58:54 -05:00
|
|
|
fail:
|
2012-02-23 13:17:41 -05:00
|
|
|
SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN |
|
|
|
|
SSL_RECEIVED_SHUTDOWN);
|
2012-02-23 11:58:54 -05:00
|
|
|
|
|
|
|
return -1;
|
2011-03-06 06:58:58 -05:00
|
|
|
}
|