diff --git a/configure b/configure index 1fa1547..8260b34 100755 --- a/configure +++ b/configure @@ -675,6 +675,7 @@ enable_static_build enable_clamd enable_memcached +enable_starttls with_piler_user ' ac_precious_vars='build_alias @@ -1298,6 +1299,7 @@ --enable-static-build build statically linked executables (default: dynamically linked) --enable-clamd build clamd antivirus support --enable-memcached build memcached support + --enable-starttls build starttls support Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -3414,6 +3416,7 @@ have_tre="no" have_zip="no" have_zlib="no" +have_starttls="no" pdftotext="no" catdoc="no" @@ -3485,6 +3488,16 @@ +# Check whether --enable-starttls was given. +if test "${enable_starttls+set}" = set; then : + enableval=$enable_starttls; have_starttls=$enableval +else + have_starttls="no" +fi + + + + for ac_header in math.h do : @@ -4339,6 +4352,15 @@ antispam_libs="$antispam_libs -lzip" fi +if test "$have_starttls" = "yes"; then + echo "starttls support: yes" + +cat >>confdefs.h <<_ACEOF +#define HAVE_STARTTLS 1 +_ACEOF + +fi + echo if test "$have_clamd" = "yes"; then diff --git a/configure.in b/configure.in index 70bd3f6..6f8cf1f 100644 --- a/configure.in +++ b/configure.in @@ -40,6 +40,7 @@ have_tre="no" have_zip="no" have_zlib="no" +have_starttls="no" pdftotext="no" catdoc="no" @@ -99,6 +100,11 @@ [ --enable-memcached build memcached support], want_memcached=$enableval, want_memcached="no") +AC_ARG_ENABLE(starttls, + [ --enable-starttls build starttls support], have_starttls=$enableval, have_starttls="no") + + + dnl math library AC_CHECK_HEADERS(math.h, have_math=yes, have_math=no) @@ -278,6 +284,11 @@ antispam_libs="$antispam_libs -lzip" fi +if test "$have_starttls" = "yes"; then + echo "starttls support: yes" + AC_DEFINE_UNQUOTED(HAVE_STARTTLS, 1, [starttls support]) +fi + echo if test "$have_clamd" = "yes"; then diff --git a/etc/example.conf b/etc/example.conf index 0ed00ce..461293d 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -47,6 +47,21 @@ workdir=/var/piler/tmp +; +; starttls stuff +; + +; whether to enable (1) or disable (0) starttls support +tls_enable=0 + +; PEM file containing both the certificate and the private key. +; Make sure to create this file (and secure it with chmod 600 /usr/local/etc/piler.pem) +; before turning on starttls support! +pemfile= + +; cipher list to use, see 'man SSL_CTX_set_cipher_list' for more details +cipher_list=HIGH:MEDIUM + ; piler's own header to indicate previously archived messages piler_header_field=X-piler: piler already archived this email diff --git a/piler-config.h.in b/piler-config.h.in index 57c0eb9..0bbe3fa 100644 --- a/piler-config.h.in +++ b/piler-config.h.in @@ -17,3 +17,4 @@ #undef HAVE_UNRTF #undef HAVE_ZIP +#undef HAVE_STARTTLS diff --git a/src/cfg.c b/src/cfg.c index 53b9584..0497bd8 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -61,6 +61,7 @@ { "archive_emails_not_having_message_id", "integer", (void*) int_parser, offsetof(struct __config, archive_emails_not_having_message_id), "0", sizeof(int)}, { "backlog", "integer", (void*) int_parser, offsetof(struct __config, backlog), "20", sizeof(int)}, + { "cipher_list", "string", (void*) string_parser, offsetof(struct __config, cipher_list), "HIGH:MEDIUM", MAXVAL-1}, { "clamd_addr", "string", (void*) string_parser, offsetof(struct __config, clamd_addr), "", MAXVAL-1}, { "clamd_port", "integer", (void*) int_parser, offsetof(struct __config, clamd_port), "0", sizeof(int)}, { "clamd_socket", "string", (void*) string_parser, offsetof(struct __config, clamd_socket), CLAMD_SOCKET, MAXVAL-1}, @@ -85,11 +86,13 @@ { "mysqldb", "string", (void*) string_parser, offsetof(struct __config, mysqldb), "piler", MAXVAL-1}, { "mysql_connect_timeout", "integer", (void*) int_parser, offsetof(struct __config, mysql_connect_timeout), "2", sizeof(int)}, { "number_of_worker_processes", "integer", (void*) int_parser, offsetof(struct __config, number_of_worker_processes), "10", sizeof(int)}, + { "pemfile", "string", (void*) string_parser, offsetof(struct __config, pemfile), "", MAXVAL-1}, { "pidfile", "string", (void*) string_parser, offsetof(struct __config, pidfile), PIDFILE, MAXVAL-1}, { "piler_header_field", "string", (void*) string_parser, offsetof(struct __config, piler_header_field), "", MAXVAL-1}, { "queuedir", "string", (void*) string_parser, offsetof(struct __config, queuedir), QUEUE_DIR, MAXVAL-1}, { "session_timeout", "integer", (void*) int_parser, offsetof(struct __config, session_timeout), "420", sizeof(int)}, { "spam_header_line", "string", (void*) string_parser, offsetof(struct __config, spam_header_line), "", MAXVAL-1}, + { "tls_enable", "integer", (void*) int_parser, offsetof(struct __config, tls_enable), "0", sizeof(int)}, { "update_counters_to_memcached", "integer", (void*) int_parser, offsetof(struct __config, update_counters_to_memcached), "0", sizeof(int)}, { "username", "string", (void*) string_parser, offsetof(struct __config, username), "piler", MAXVAL-1}, { "use_antivirus", "integer", (void*) int_parser, offsetof(struct __config, use_antivirus), "1", sizeof(int)}, diff --git a/src/cfg.h b/src/cfg.h index 52e4f7c..9b44123 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -20,6 +20,10 @@ int clamd_port; char clamd_socket[MAXVAL]; + int tls_enable; + char pemfile[MAXVAL]; + char cipher_list[MAXVAL]; + int use_antivirus; char memcached_servers[MAXVAL]; diff --git a/src/config.h b/src/config.h index f8fd96e..82c8f23 100644 --- a/src/config.h +++ b/src/config.h @@ -13,7 +13,7 @@ #define VERSION "0.1.21" -#define BUILD 719 +#define BUILD 722 #define HOSTID "mailarchiver" diff --git a/src/defs.h b/src/defs.h index 2431cdb..0ea9c7a 100644 --- a/src/defs.h +++ b/src/defs.h @@ -178,6 +178,7 @@ char attachments[SMALLBUFSIZE]; char internal_sender, internal_recipient, external_recipient; int direction; + int tls; int spam_message; int fd, hdr_len, tot_len, num_of_rcpt_to, rav; int need_scan; @@ -239,6 +240,8 @@ struct __data { int folder; char recursive_folder_names; + char starttls[TINYBUFSIZE]; + #ifdef HAVE_TRE struct rule *archiving_rules; struct rule *retention_rules; diff --git a/src/imap.c b/src/imap.c index fb27bb5..b017162 100644 --- a/src/imap.c +++ b/src/imap.c @@ -218,7 +218,6 @@ unsigned long host=0; struct sockaddr_in remote_addr; X509* server_cert; - SSL_METHOD *meth; char *str; @@ -237,11 +236,10 @@ if(use_ssl == 1){ - SSLeay_add_ssl_algorithms(); - meth = SSLv3_client_method(); + SSL_library_init(); SSL_load_error_strings(); - data->ctx = SSL_CTX_new(meth); + data->ctx = SSL_CTX_new(SSLv3_client_method()); CHK_NULL(data->ctx, "internal SSL error"); data->ssl = SSL_new(data->ctx); @@ -314,6 +312,7 @@ SSL_shutdown(data->ssl); SSL_free(data->ssl); SSL_CTX_free(data->ctx); + ERR_free_strings(); } } diff --git a/src/misc.c b/src/misc.c index bc44d5e..a41707d 100644 --- a/src/misc.c +++ b/src/misc.c @@ -322,12 +322,67 @@ } +int ssl_want_retry(SSL *ssl, int ret, int timeout){ + int i; + fd_set fds; + struct timeval tv; + int sock; + + // something went wrong. I'll retry, die quietly, or complain + i = SSL_get_error(ssl, ret); + if(i == SSL_ERROR_NONE) + return 1; + + tv.tv_sec = timeout/1000; + tv.tv_usec = 0; + FD_ZERO(&fds); + + switch(i){ + case SSL_ERROR_WANT_READ: // pause until the socket is readable + sock = SSL_get_rfd(ssl); + FD_SET(sock, &fds); + i = select(sock+1, &fds, 0, 0, &tv); + break; + + case SSL_ERROR_WANT_WRITE: // pause until the socket is writeable + sock = SSL_get_wfd(ssl); + FD_SET(sock, &fds); + i = select(sock+1, 0, &fds, 0, &tv); + break; + + case SSL_ERROR_ZERO_RETURN: // the sock closed, just return quietly + i = 0; + break; + + default: // ERROR - unexpected error code + i = -1; + break; + }; + + return i; +} + + +int ssl_read_timeout(SSL *ssl, void *buf, int len, int timeout){ + int i; + + while(1){ + i = SSL_read(ssl, (char*)buf, len); + if(i > 0) break; + i = ssl_want_retry(ssl, i, timeout); + if(i <= 0) break; + } + + return i; +} + + int recvtimeoutssl(int s, char *buf, int len, int timeout, int use_ssl, SSL *ssl){ memset(buf, 0, len); if(use_ssl == 1){ - return SSL_read(ssl, buf, len-1); + return ssl_read_timeout(ssl, buf, len-1, timeout); } else { return recvtimeout(s, buf, len-1, timeout); diff --git a/src/piler.c b/src/piler.c index e2b8415..d36fef7 100644 --- a/src/piler.c +++ b/src/piler.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include @@ -247,6 +249,13 @@ unlink(cfg.pidfile); +#ifdef HAVE_STARTTLS + if(data.ctx){ + SSL_CTX_free(data.ctx); + ERR_free_strings(); + } +#endif + exit(1); } @@ -257,6 +266,27 @@ } +#ifdef HAVE_STARTTLS +int init_ssl(){ + + SSL_library_init(); + SSL_load_error_strings(); + + data.ctx = SSL_CTX_new(SSLv23_server_method()); + + if(data.ctx == NULL){ syslog(LOG_PRIORITY, "SSL_CTX_new() failed"); return ERR; } + + if(SSL_CTX_set_cipher_list(data.ctx, cfg.cipher_list) == 0){ syslog(LOG_PRIORITY, "failed to set cipher list: '%s'", cfg.cipher_list); return ERR; } + + if(SSL_CTX_use_PrivateKey_file(data.ctx, cfg.pemfile, SSL_FILETYPE_PEM) != 1){ syslog(LOG_PRIORITY, "cannot load private key from %s", cfg.pemfile); return ERR; } + + if(SSL_CTX_use_certificate_file(data.ctx, cfg.pemfile, SSL_FILETYPE_PEM) != 1){ syslog(LOG_PRIORITY, "cannot load certificate from %s", cfg.pemfile); return ERR; } + + return OK; +} +#endif + + void initialise_configuration(){ struct session_data sdata; @@ -293,6 +323,14 @@ data.archiving_rules = NULL; data.retention_rules = NULL; + memset(data.starttls, 0, TINYBUFSIZE); + +#ifdef HAVE_STARTTLS + if(cfg.tls_enable > 0 && data.ctx == NULL && init_ssl() == OK){ + snprintf(data.starttls, TINYBUFSIZE-1, "250-STARTTLS\r\n"); + } +#endif + mysql_init(&(sdata.mysql)); mysql_options(&(sdata.mysql), MYSQL_OPT_CONNECT_TIMEOUT, (const char*)&cfg.mysql_connect_timeout); if(mysql_real_connect(&(sdata.mysql), cfg.mysqlhost, cfg.mysqluser, cfg.mysqlpwd, cfg.mysqldb, cfg.mysqlport, cfg.mysqlsocket, 0) == 0){ @@ -347,6 +385,8 @@ data.recursive_folder_names = 0; data.archiving_rules = NULL; data.retention_rules = NULL; + data.ctx = NULL; + data.ssl = NULL; initialise_configuration(); diff --git a/src/session.c b/src/session.c index 3d8d14f..5f1a9b3 100644 --- a/src/session.c +++ b/src/session.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include @@ -31,10 +33,15 @@ struct timezone tz; struct timeval tv1, tv2; +#ifdef HAVE_STARTTLS + int starttls = 0; + char ssl_error[SMALLBUFSIZE]; +#endif state = SMTP_STATE_INIT; init_session_data(&sdata); + sdata.tls = 0; bzero(&counters, sizeof(counters)); @@ -70,7 +77,7 @@ send(new_sd, buf, strlen(buf), 0); if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: sent: %s", sdata.ttmpfile, buf); - while((n = recvtimeout(new_sd, puf, MAXBUFSIZE, TIMEOUT)) > 0){ + while((n = recvtimeoutssl(new_sd, puf, MAXBUFSIZE, TIMEOUT, sdata.tls, data->ssl)) > 0){ pos = 0; /* accept mail data */ @@ -120,7 +127,8 @@ for(i=0; issl); #ifdef HAVE_LMTP } @@ -213,7 +221,8 @@ if(inj == ERR) snprintf(sdata.acceptbuf, SMALLBUFSIZE-1, "451 %s <%s>\r\n", sdata.ttmpfile, rctptoemail); - send(new_sd, sdata.acceptbuf, strlen(sdata.acceptbuf), 0); + //send(new_sd, sdata.acceptbuf, strlen(sdata.acceptbuf), 0); + write1(new_sd, sdata.acceptbuf, sdata.tls, data->ssl); if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: sent: %s", sdata.ttmpfile, sdata.acceptbuf); @@ -288,7 +297,9 @@ if(strncasecmp(buf, SMTP_CMD_EHLO, strlen(SMTP_CMD_EHLO)) == 0 || strncasecmp(buf, LMTP_CMD_LHLO, strlen(LMTP_CMD_LHLO)) == 0){ if(state == SMTP_STATE_INIT) state = SMTP_STATE_HELO; - snprintf(buf, MAXBUFSIZE-1, SMTP_RESP_250_EXTENSIONS, cfg->hostid); + if(sdata.tls == 0) snprintf(buf, MAXBUFSIZE-1, SMTP_RESP_250_EXTENSIONS, cfg->hostid, data->starttls); + else snprintf(buf, MAXBUFSIZE-1, SMTP_RESP_250_EXTENSIONS, cfg->hostid, ""); + strncat(resp, buf, MAXBUFSIZE-1); continue; @@ -306,6 +317,29 @@ } + #ifdef HAVE_STARTTLS + if(cfg->tls_enable > 0 && strncasecmp(buf, SMTP_CMD_STARTTLS, strlen(SMTP_CMD_STARTTLS)) == 0 && strlen(data->starttls) > 4 && sdata.tls == 0){ + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: starttls request from client", sdata.ttmpfile); + + if(data->ctx){ + data->ssl = SSL_new(data->ctx); + if(data->ssl){ + if(SSL_set_fd(data->ssl, new_sd) == 1){ + strncat(resp, SMTP_RESP_220_READY_TO_START_TLS, MAXBUFSIZE-1); + starttls = 1; + state = SMTP_STATE_INIT; + + continue; + } syslog(LOG_PRIORITY, "%s: SSL_set_fd() failed", sdata.ttmpfile); + } syslog(LOG_PRIORITY, "%s: SSL_new() failed", sdata.ttmpfile); + } syslog(LOG_PRIORITY, "%s: SSL ctx is null!", sdata.ttmpfile); + + + strncat(resp, SMTP_RESP_454_ERR_TLS_TEMP_ERROR, MAXBUFSIZE-1); + continue; + } + #endif + if(strncasecmp(buf, SMTP_CMD_MAIL_FROM, strlen(SMTP_CMD_MAIL_FROM)) == 0){ @@ -433,9 +467,32 @@ /* now we can send our buffered response */ if(strlen(resp) > 0){ - send(new_sd, resp, strlen(resp), 0); + //send(new_sd, resp, strlen(resp), 0); + write1(new_sd, resp, sdata.tls, data->ssl); + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: sent: %s", sdata.ttmpfile, resp); memset(resp, 0, MAXBUFSIZE); + + #ifdef HAVE_STARTTLS + if(starttls == 1 && sdata.tls == 0){ + + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: waiting for ssl handshake", sdata.ttmpfile); + + rc = SSL_accept(data->ssl); + + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: SSL_accept() finished", sdata.ttmpfile); + + if(rc == 1){ + sdata.tls = 1; + } + else { + ERR_error_string_n(ERR_get_error(), ssl_error, SMALLBUFSIZE); + syslog(LOG_PRIORITY, "%s: SSL_accept() failed, rc=%d, errorcode: %d, error text: %s\n", sdata.ttmpfile, rc, SSL_get_error(data->ssl, rc), ssl_error); + } + } + #endif + + } if(state == SMTP_STATE_FINISHED){ @@ -451,7 +508,9 @@ if(state < SMTP_STATE_QUIT && inj == ERR){ snprintf(buf, MAXBUFSIZE-1, SMTP_RESP_421_ERR, cfg->hostid); - send(new_sd, buf, strlen(buf), 0); + //send(new_sd, buf, strlen(buf), 0); + write1(new_sd, buf, sdata.tls, data->ssl); + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "%s: sent: %s", sdata.ttmpfile, buf); if(sdata.fd != -1){ @@ -475,6 +534,13 @@ mysql_close(&(sdata.mysql)); #endif +#ifdef HAVE_STARTTLS + if(sdata.tls == 1){ + SSL_shutdown(data->ssl); + SSL_free(data->ssl); + } +#endif + if(cfg->verbosity >= _LOG_INFO) syslog(LOG_PRIORITY, "processed %llu messages", counters.c_rcvd); return (int)counters.c_rcvd; diff --git a/src/smtpcodes.h b/src/smtpcodes.h index ac4b49c..b5c425b 100644 --- a/src/smtpcodes.h +++ b/src/smtpcodes.h @@ -26,13 +26,15 @@ #define SMTP_CMD_NOOP "NOOP" #define SMTP_CMD_XFORWARD "XFORWARD" #define SMTP_CMD_XCLIENT "XCLIENT" +#define SMTP_CMD_STARTTLS "STARTTLS" // SMTP responses #define SMTP_RESP_220_BANNER "220 %s ESMTP\r\n" +#define SMTP_RESP_220_READY_TO_START_TLS "220 Ready to start TLS\r\n" #define SMTP_RESP_221_GOODBYE "221 %s Goodbye\r\n" #define SMTP_RESP_250_OK "250 Ok\r\n" -#define SMTP_RESP_250_EXTENSIONS "250-%s\r\n250-PIPELINING\r\n250-SIZE\r\n250 8BITMIME\r\n" +#define SMTP_RESP_250_EXTENSIONS "250-%s\r\n250-PIPELINING\r\n%s250-SIZE\r\n250 8BITMIME\r\n" #define SMTP_RESP_354_DATA_OK "354 Send mail data; end it with .\r\n" @@ -41,9 +43,11 @@ #define SMTP_RESP_421_ERR_WRITE_FAILED "421 writing queue file failed\r\n" #define SMTP_RESP_450_ERR_CMD_NOT_IMPLEMENTED "450 command not implemented\r\n" #define SMTP_RESP_451_ERR "451 Error in processing, try again later\r\n" +#define SMTP_RESP_454_ERR_TLS_TEMP_ERROR "454 TLS not available currently\r\n" #define SMTP_RESP_502_ERR "502 Command not implemented\r\n" #define SMTP_RESP_503_ERR "503 Bad command sequence\r\n" +#define SMTP_RESP_530_ERR_MUST_ISSUE_STARTTLS_FIRST "530 MUST issue STARTTLS command first\r\n" #define SMTP_RESP_550_ERR "550 Access denied.\r\n" #define SMTP_RESP_550_ERR_PREF "550 Access denied." #define SMTP_RESP_550_INVALID_RECIPIENT "550 Unknown recipient\r\n"