Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First experimental implementation of ESMTP, HELO fallback and various improvements #11

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
34dbda4
simplify SSL config flag semantics, default is having SSL disabled un…
Oct 2, 2012
6574304
simplify read_remote logic and prevent infinite loops due to pos == l…
Oct 2, 2012
0ead6c3
rework yet again read_remote to be more secure, add HELO fallback and…
Oct 2, 2012
f22e7c5
add VERBOSE feature to debug network communication
Oct 2, 2012
aab5fbc
various bugfixes to VERBOSE logging and ESMTP parsing
Oct 2, 2012
d508bdb
fix buggy boolean guard
Oct 2, 2012
31fb2f4
fix ESMTP parsing
Oct 2, 2012
e5e6bc9
deliver_host simplify and make mail read loop more reliable
Oct 2, 2012
f5cc3fa
add VERBOSE option warning to config file
Oct 2, 2012
e8c66f2
Improve configuration file routines and port parsing
Oct 3, 2012
2fca723
Fix some issues with format and simplify a condition
Oct 3, 2012
7a19086
Make config file parsing more solid
Oct 3, 2012
76e0821
Improve HELO fallback and ESMTP capabilities refresh
Oct 3, 2012
c917cec
Drop entirely the no login fallback
Oct 3, 2012
bf5941f
Improve message on I/O error
Oct 3, 2012
005a007
Replace SECURETRANS with the more specific USESSL (probably SECURETRA…
Oct 3, 2012
1ae3e6b
First implementation of the PLAIN authentication method
Oct 3, 2012
460cc2d
More read_remote improvements, to error handling and parsing
Oct 3, 2012
1148a3d
Minor style fix
Oct 3, 2012
06621be
Update man page, minor style fix in config file.
Oct 3, 2012
c7e813d
Fix typo in man page.
Oct 3, 2012
b7977a8
Fix error message
Oct 3, 2012
c899ca3
Avoid mixed declaration and code.
Oct 3, 2012
faca752
Make connection structure to avoid modifying global data
Oct 3, 2012
afe9ead
Use memset() to 0 rather than the deprecated bzero()
Oct 3, 2012
138ea03
Try a better guess in the unlikely event that _SC_OPEN_MAX is unavail…
Oct 3, 2012
d380e95
Make local mail delivery function check for read errors.
Oct 3, 2012
1fcb993
Fix config file entry parsing
Oct 3, 2012
5591c0f
Revert "Fix config file entry parsing"
Oct 3, 2012
dc4f1a7
Fix man page entries.
Oct 3, 2012
87405fe
Simplify logging messages.
Oct 3, 2012
58109b0
Make PLAIN authentication work
Oct 3, 2012
ef4681b
Some minor fixes
Oct 3, 2012
8709011
Consistently replace bcopy() and bzero() with memcpy() and memset()
Oct 3, 2012
8761545
Clear file error flag
Oct 4, 2012
4752047
Check for I/O errors even on mail bounces
Oct 4, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 68 additions & 22 deletions conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <stdarg.h>

Expand Down Expand Up @@ -74,12 +75,34 @@ trim_line(char *line)
static void
chomp(char *str)
{
char *p;
size_t i;
size_t len = strlen(str);

/* remove trailing spaces */
for (i = 0; i < len; i++) {
if (!isspace(str[i]))
break;
}

memmove(str, str + i, len + 1 - i);
len -= i;
if (len == 0)
return;
if (str[len - 1] == '\n')
str[len - 1] = 0;

/* remove ending spaces (also handles ending '\n', if any) */
while (len-- > 0) {
if (!isspace(str[len]))
break;
}

str[len + 1] = 0;

/* remove comments */
p = strchr(str, '#');
if (p) {
*p = 0;
}
}

/*
Expand All @@ -88,7 +111,7 @@ chomp(char *str)
* file format is:
* user|host:password
*
* A line starting with # is treated as comment and ignored.
* Anything following a # is treated as comment and ignored.
*/
void
parse_authfile(const char *path)
Expand All @@ -97,6 +120,7 @@ parse_authfile(const char *path)
struct authuser *au;
FILE *a;
char *data;
int error;
int lineno = 0;

a = fopen(path, "r");
Expand All @@ -105,16 +129,11 @@ parse_authfile(const char *path)
/* NOTREACHED */
}

while (!feof(a)) {
if (fgets(line, sizeof(line), a) == NULL)
break;
while (fgets(line, sizeof(line), a)) {
lineno++;

chomp(line);

/* We hit a comment */
if (*line == '#')
continue;
/* Ignore empty lines */
if (*line == 0)
continue;
Expand All @@ -138,8 +157,14 @@ parse_authfile(const char *path)

SLIST_INSERT_HEAD(&authusers, au, next);
}


error = ferror(a);
fclose(a);

if (error) {
errlog(1, "I/O error while reading file `%s'", path);
/* NOTREACHED */
}
}

/*
Expand All @@ -153,6 +178,7 @@ parse_conf(const char *config_path)
char *data;
FILE *conf;
char line[2048];
int error;
int lineno = 0;

conf = fopen(config_path, "r");
Expand All @@ -164,17 +190,11 @@ parse_conf(const char *config_path)
/* NOTREACHED */
}

while (!feof(conf)) {
if (fgets(line, sizeof(line), conf) == NULL)
break;
while (fgets(line, sizeof(line), conf)) {
lineno++;

chomp(line);

/* We hit a comment */
if (strchr(line, '#'))
*strchr(line, '#') = 0;

data = line;
word = strsep(&data, EQS);

Expand All @@ -186,12 +206,20 @@ parse_conf(const char *config_path)
data = strdup(data);
else
data = NULL;

if (strcmp(word, "SMARTHOST") == 0 && data != NULL)
config.smarthost = data;
else if (strcmp(word, "PORT") == 0 && data != NULL)
config.port = atoi(data);
else if (strcmp(word, "ALIASES") == 0 && data != NULL)
else if (strcmp(word, "PORT") == 0 && data != NULL) {
char*check;
long port = strtol(data, &check, 10);

if (*check != '\0' || port < 0 || port > 0xffff) {
errlogx(1, "invalid value for PORT in %s:%d", config_path, lineno);
/* NOTREACHED */
}

config.port = (unsigned int)port;
} else if (strcmp(word, "ALIASES") == 0 && data != NULL)
config.aliases = data;
else if (strcmp(word, "SPOOLDIR") == 0 && data != NULL)
config.spooldir = data;
Expand All @@ -217,8 +245,12 @@ parse_conf(const char *config_path)
user = NULL;
config.masquerade_host = host;
config.masquerade_user = user;
} else if (strcmp(word, "STARTTLS") == 0 && data == NULL)
} else if (strcmp(word, "VERBOSE") == 0 && data == NULL)
config.features |= VERBOSE;
else if (strcmp(word, "STARTTLS") == 0 && data == NULL)
config.features |= STARTTLS;
else if (strcmp(word, "NOHELO") == 0 && data == NULL)
config.features |= NOHELO;
else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL)
config.features |= TLS_OPP;
else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL)
Expand All @@ -235,5 +267,19 @@ parse_conf(const char *config_path)
}
}

error = ferror(conf);
fclose(conf);

if (error) {
errlog(1, "I/O error while reading file `%s'", config_path);
/* NOTREACHED */
}

/* ensure a meaningful configuration */
if ((config.features & STARTTLS) != 0) {
if ((config.features & SECURETRANS) == 0) {
syslog(LOG_WARNING, "STARTTLS enabled in `%s', implicitly assuming SECURETRANSFER is enabled", config_path);
config.features |= SECURETRANS;
}
}
}
112 changes: 66 additions & 46 deletions crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <openssl/pem.h>
#include <openssl/rand.h>

#include <string.h>
#include <syslog.h>

#include "dma.h"
Expand Down Expand Up @@ -77,7 +78,7 @@ init_cert_file(SSL_CTX *ctx, const char *path)
}

int
smtp_init_crypto(int fd, int feature)
smtp_init_crypto(struct connection *c)
{
SSL_CTX *ctx = NULL;
const SSL_METHOD *meth = NULL;
Expand Down Expand Up @@ -107,64 +108,70 @@ smtp_init_crypto(int fd, int feature)
}

/*
* If the user wants STARTTLS, we have to send EHLO here
* If STARTTLS is required, issue it here
*/
if (((feature & SECURETRANS) != 0) &&
(feature & STARTTLS) != 0) {
/* TLS init phase, disable SSL_write */
config.features |= NOSSL;

send_remote_command(fd, "EHLO %s", hostname());
if (read_remote(fd, 0, NULL) == 2) {
send_remote_command(fd, "STARTTLS");
if (read_remote(fd, 0, NULL) != 2) {
if ((feature & TLS_OPP) == 0) {
syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr);
return (1);
} else {
syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr);
return (0);
}
if ((config.features & STARTTLS) != 0) {
/* TLS init phase */
if ((c->flags & HASSTARTTLS) != 0) {
send_remote_command(c, "STARTTLS");
if (read_remote(c, NULL, NULL) != 220) {
/* even in opportunistic TLS, if server marked it as available, an error
* is unexpected
*/
syslog(LOG_ERR, "remote delivery deferred: STARTTLS failed: %s", neterr);
return (1);
}

/* End of TLS init phase */
c->flags |= USESTARTTLS;
} else {
if ((config.features & TLS_OPP) == 0) {
/* remote has no STARTTLS but user required it */
syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available");
return (1);
}

/* disable STARTTLS, opportunistic mode */
syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available");
}
/* End of TLS init phase, enable SSL_write/read */
config.features &= ~NOSSL;
}

config.ssl = SSL_new(ctx);
if (config.ssl == NULL) {
c->ssl = SSL_new(ctx);
if (c->ssl == NULL) {
syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s",
ssl_errstr());
return (1);
}

/* Set ssl to work in client mode */
SSL_set_connect_state(config.ssl);
SSL_set_connect_state(c->ssl);

/* Set fd for SSL in/output */
error = SSL_set_fd(config.ssl, fd);
error = SSL_set_fd(c->ssl, c->fd);
if (error == 0) {
syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s",
ssl_errstr());
return (1);
}

/* Open SSL connection */
error = SSL_connect(config.ssl);
error = SSL_connect(c->ssl);
if (error < 0) {
syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s",
ssl_errstr());
return (1);
}

/* Get peer certificate */
cert = SSL_get_peer_certificate(config.ssl);
cert = SSL_get_peer_certificate(c->ssl);
if (cert == NULL) {
syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s",
ssl_errstr());
}
X509_free(cert);


/* At this point we can safely use SSL write/read*/
c->flags |= USESSL;
return (0);
}

Expand Down Expand Up @@ -217,10 +224,10 @@ hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
*/

/* start out by storing key in pads */
bzero( k_ipad, sizeof k_ipad);
bzero( k_opad, sizeof k_opad);
bcopy( key, k_ipad, key_len);
bcopy( key, k_opad, key_len);
memset(k_ipad, 0, sizeof(k_ipad));
memset(k_opad, 0, sizeof(k_opad));
memcpy(k_ipad, key, key_len);
memcpy(k_opad, key, key_len);

/* XOR key with ipad and opad values */
for (i=0; i<64; i++) {
Expand Down Expand Up @@ -250,27 +257,40 @@ hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
* CRAM-MD5 authentication
*/
int
smtp_auth_md5(int fd, char *login, char *password)
smtp_auth_md5(struct connection *c, char *login, char *password)
{
unsigned char digest[BUF_SIZE];
char buffer[BUF_SIZE], ascii_digest[33];
char *temp;
int len, i;
size_t buffsize = sizeof(buffer);
static char hextab[] = "0123456789abcdef";

temp = calloc(BUF_SIZE, 1);

memset(buffer, 0, sizeof(buffer));
memset(digest, 0, sizeof(digest));
memset(ascii_digest, 0, sizeof(ascii_digest));

/* Send AUTH command according to RFC 2554 */
send_remote_command(fd, "AUTH CRAM-MD5");
if (read_remote(fd, sizeof(buffer), buffer) != 3) {
syslog(LOG_DEBUG, "smarthost authentication:"
" AUTH cram-md5 not available: %s", neterr);
send_remote_command(c, "AUTH CRAM-MD5");
if (read_remote(c, &buffsize, buffer) != 334) {
/* if cram-md5 is not available */
free(temp);
return (-1);
syslog(LOG_DEBUG, "smarthost authentication:"
" AUTH CRAM-MD5 failed: %s", neterr);
return (1);
}

if (buffsize > sizeof(buffer)) {
syslog(LOG_DEBUG, "smarthost authentication:"
" oversized response to AUTH CRAM-MD5");
return (1);
}

/* allocate decoding buffer */
temp = calloc(BUF_SIZE, 1);
if (!temp) {
syslog(LOG_WARNING, "remote delivery deferred:"
" memory allocation failed");
return (1);
}

/* skip 3 char status + 1 char space */
Expand All @@ -291,17 +311,17 @@ smtp_auth_md5(int fd, char *login, char *password)
/* encode answer */
len = base64_encode(buffer, strlen(buffer), &temp);
if (len < 0) {
syslog(LOG_ERR, "can not encode auth reply: %m");
return (-1);
syslog(LOG_ERR, "cannot encode auth reply: %m");
return (1);
}

/* send answer */
send_remote_command(fd, "%s", temp);
send_remote_command(c, "%s", temp);
free(temp);
if (read_remote(fd, 0, NULL) != 2) {
if (read_remote(c, NULL, NULL) != 220) {
syslog(LOG_WARNING, "remote delivery deferred:"
" AUTH cram-md5 failed: %s", neterr);
return (-2);
" AUTH CRAM-MD5 failed: %s", neterr);
return (1);
}

return (0);
Expand Down
Loading