From e093d11a092e84957992643351d2413dabd46f3f Mon Sep 17 00:00:00 2001 From: Janos SUTO Date: Fri, 22 Mar 2024 06:02:08 +0100 Subject: [PATCH] piler-smtp refactor Signed-off-by: Janos SUTO --- etc/example.conf | 9 + src/bdat.c | 6 +- src/cfg.c | 12 ++ src/cfg.h | 3 + src/config.h | 3 + src/defs.h | 9 +- src/misc.c | 26 +++ src/misc.h | 1 + src/piler-smtp.c | 11 +- src/session.c | 228 ++++++++++++++++------- src/smtp.c | 143 ++++++++------- src/smtp.h | 12 +- src/smtpcodes.h | 4 +- src/tai.h | 4 +- tests/cases/05-smtp.inc | 22 +-- unit_tests/run.sh | 9 + unit_tests/smtp.c | 389 +++++++++++++++++++++++++++++++--------- unit_tests/test.h | 1 + 18 files changed, 638 insertions(+), 254 deletions(-) diff --git a/etc/example.conf b/etc/example.conf index 823facc7..c0d152c1 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -263,3 +263,12 @@ archive_address= ; rules. In other words if you decide to use the acl file, then ; everyone is not explicitly permitted is denied. smtp_access_list=0 + +; max message size in bytes +; piler-smtp will reject any message that's bigger than this number +max_message_size=50000000 + +; max memory in bytes piler-smtp uses for buffering messages +; when this limit is exceeded, no new emails will be accepted +; until the used memory decreases below this level +max_smtp_memory=500000000 diff --git a/src/bdat.c b/src/bdat.c index 8dde847b..8438a62e 100644 --- a/src/bdat.c +++ b/src/bdat.c @@ -26,21 +26,21 @@ void reset_bdat_counters(struct smtp_session *session){ } -void get_bdat_size_to_read(struct smtp_session *session, char *buf){ +void get_bdat_size_to_read(struct smtp_session *session){ char *p; session->bdat_bytes_to_read = 0; session->protocol_state = SMTP_STATE_BDAT; - p = strcasestr(buf, " LAST"); + p = strcasestr(session->buf, " LAST"); if(p){ *p = '\0'; } // determine the size to be read - p = strchr(buf, ' '); + p = strchr(session->buf, ' '); if(p){ session->bdat_bytes_to_read = atoi(p); if(session->cfg->verbosity >= _LOG_DEBUG) syslog(LOG_INFO, "fd=%d: BDAT len=%d", session->net.socket, session->bdat_bytes_to_read); diff --git a/src/cfg.c b/src/cfg.c index e510bc4e..40cf5ab3 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -23,6 +23,11 @@ int int_parser(char *src, int *target){ return 0; }; +int uint64_parser(char *src, uint64 *target){ + *target = strtoull(src, (char**)NULL, 10); + return 0; +} + struct _parse_rule { char *name; char *type; @@ -63,7 +68,9 @@ struct _parse_rule config_parse_rules[] = { "listen_port", "integer", (void*) int_parser, offsetof(struct config, listen_port), "25", sizeof(int)}, { "locale", "string", (void*) string_parser, offsetof(struct config, locale), "", MAXVAL-1}, { "max_connections", "integer", (void*) int_parser, offsetof(struct config, max_connections), "64", sizeof(int)}, + { "max_message_size", "integer", (void*) int_parser, offsetof(struct config, max_message_size), "50000000", sizeof(int)}, { "max_requests_per_child", "integer", (void*) int_parser, offsetof(struct config, max_requests_per_child), "10000", sizeof(int)}, + { "max_smtp_memory", "uint64", (void*) uint64_parser, offsetof(struct config, max_smtp_memory), "500000000", sizeof(uint64)}, { "memcached_servers", "string", (void*) string_parser, offsetof(struct config, memcached_servers), "127.0.0.1", MAXVAL-1}, { "memcached_to_db_interval", "integer", (void*) int_parser, offsetof(struct config, memcached_to_db_interval), "900", sizeof(int)}, { "memcached_ttl", "integer", (void*) int_parser, offsetof(struct config, memcached_ttl), "86400", sizeof(int)}, @@ -217,6 +224,7 @@ struct config read_config(char *configfile){ void print_config_item(struct config *cfg, struct _parse_rule *rules, int i){ int j; float f; + uint64 u; char *p, buf[MAXVAL]; p = (char*)cfg + rules[i].offset; @@ -225,6 +233,10 @@ void print_config_item(struct config *cfg, struct _parse_rule *rules, int i){ memcpy((char*)&j, p, sizeof(int)); printf("%s=%d\n", rules[i].name, j); } + else if(strcmp(rules[i].type, "uint64") == 0){ + memcpy((char*)&u, p, sizeof(uint64)); + printf("%s=%llu\n", rules[i].name, u); + } else if(strcmp(rules[i].type, "float") == 0){ memcpy((char*)&f, p, sizeof(float)); printf("%s=%.4f\n", rules[i].name, f); diff --git a/src/cfg.h b/src/cfg.h index 5929e17c..5ff0e1fa 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -110,6 +110,9 @@ struct config { int debug; int smtp_access_list; + + int max_message_size; + uint64 max_smtp_memory; }; diff --git a/src/config.h b/src/config.h index 7060faaf..7965c836 100644 --- a/src/config.h +++ b/src/config.h @@ -9,6 +9,8 @@ #include "piler-config.h" #include "params.h" +typedef unsigned long long uint64; + #define BUILD 1001 #define HOSTID "mailarchiver" @@ -30,6 +32,7 @@ #define SMALLBUFSIZE 512 #define BIGBUFSIZE 131072 #define REALLYBIGBUFSIZE 524288 +#define SMTPBUFSIZE 2048000 #define TINYBUFSIZE 128 #define MAXVAL 256 #define RANDOM_POOL "/dev/urandom" diff --git a/src/defs.h b/src/defs.h index 6a2a6037..aa1f6d02 100644 --- a/src/defs.h +++ b/src/defs.h @@ -403,7 +403,6 @@ struct smtp_session { char ttmpfile[SMALLBUFSIZE]; char mailfrom[SMALLBUFSIZE]; char rcptto[MAX_RCPT_TO][SMALLBUFSIZE]; - char buf[MAXBUFSIZE]; char remote_host[INET6_ADDRSTRLEN+1]; char nullbyte; time_t lasttime; @@ -411,13 +410,17 @@ struct smtp_session { int slot; int fd; int bad; - int buflen; - int last_data_char; int tot_len; int bdat_bytes_to_read; int num_of_rcpt_to; struct config *cfg; struct net net; + int max_message_size; + char *buf; + int buflen; + int bufsize; + int too_big; + int mail_size; }; struct tls_protocol { diff --git a/src/misc.c b/src/misc.c index 884f2a1b..b08200a4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -781,3 +781,29 @@ int append_string_to_buffer(char **buffer, char *str){ return 0; } + + +int get_size_from_smtp_mail_from(char *s){ + int size=0; + char *p; + + p = strcasestr(s, "SIZE="); + if(p){ + p += strlen("SIZE="); + char *q = p; + for(; *q; q++){ + if(isspace(*q)) break; + } + + // We extract max. 9 characters, which is just under 1GB + // and not overflowing an int variable + if(q - p <= 9){ + char c = *q; + *q = '\0'; + size = atoi(p); + *q = c; + } + } + + return size; +} diff --git a/src/misc.h b/src/misc.h index 3392eb95..a8e8187c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -55,5 +55,6 @@ int init_ssl_to_server(struct data *data); #endif int append_string_to_buffer(char **buffer, char *str); +int get_size_from_smtp_mail_from(char *s); #endif /* _MISC_H */ diff --git a/src/piler-smtp.c b/src/piler-smtp.c index 91481069..1e75053d 100644 --- a/src/piler-smtp.c +++ b/src/piler-smtp.c @@ -33,6 +33,7 @@ extern int optind; struct epoll_event event, *events=NULL; int num_connections = 0; int listenerfd = -1; +int loglevel = 1; char *configfile = CONFIG_FILE; struct config cfg; @@ -48,6 +49,7 @@ void usage(){ printf(" -d Fork to the background\n"); printf(" -v Return the version and build number\n"); printf(" -V Return the version and some build parameters\n"); + printf(" -L Set the log level: 1-5\n"); exit(0); } @@ -138,16 +140,20 @@ int main(int argc, char **argv){ int client_len = sizeof(struct sockaddr_storage); ssize_t readlen; struct sockaddr_storage client_address; - char readbuf[BIGBUFSIZE]; + char readbuf[REALLYBIGBUFSIZE]; int efd; - while((i = getopt(argc, argv, "c:dvVh")) > 0){ + while((i = getopt(argc, argv, "c:L:dvVh")) > 0){ switch(i){ case 'c' : configfile = optarg; break; + case 'L': + loglevel = atoi(optarg); + break; + case 'd' : daemonise = 1; break; @@ -321,6 +327,7 @@ int main(int argc, char **argv){ break; } + readbuf[readlen] = '\0'; handle_data(session, &readbuf[0], readlen, &cfg); if(session->protocol_state == SMTP_STATE_BDAT && session->bad == 1){ diff --git a/src/session.c b/src/session.c index b8b9ce76..a7ca4094 100644 --- a/src/session.c +++ b/src/session.c @@ -9,6 +9,36 @@ int get_session_slot(struct smtp_session **sessions, int max_connections); void init_smtp_session(struct smtp_session *session, int slot, int sd, char *client_addr, struct config *cfg); +#define GOT_CRLF_DOT_CRLF(p) *p == '\r' && *(p+1) == '\n' && *(p+2) == '.' && *(p+3) == '\r' && *(p+4) == '\n' ? 1 : 0 + +uint64 get_sessions_total_memory(struct smtp_session **sessions, int max_connections){ + uint64 total = 0; + + for(int i=0; ibufsize; + } + + return total; +} + + +/* + * If the sending party sets the email size when it sends the "mail from" + * part in the smtp transaction, eg. MAIL FROM: size=509603 + * then piler-smtp could know the email size in advance and could do + * a better estimate on the allowed number of smtp sessions. + */ + +uint64 get_sessions_total_expected_mail_size(struct smtp_session **sessions, int max_connections){ + uint64 total = 0; + + for(int i=0; imail_size; + } + + return total; +} + int start_new_session(struct smtp_session **sessions, int socket, int *num_connections, struct smtp_acl *smtp_acl[], char *client_addr, struct config *cfg){ int slot; @@ -31,6 +61,21 @@ int start_new_session(struct smtp_session **sessions, int socket, int *num_conne return -1; } + /* + * We are under the max_smtp_memory threshold + */ + + uint64 expected_total_mail_size = get_sessions_total_expected_mail_size(sessions, cfg->max_connections); + uint64 total_memory = get_sessions_total_memory(sessions, cfg->max_connections); + + if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "DEBUG: total smtp memory allocated: %llu, expected total size: %llu", total_memory, expected_total_mail_size); + + if(total_memory > cfg->max_smtp_memory || expected_total_mail_size > cfg->max_smtp_memory){ + syslog(LOG_PRIORITY, "ERROR: too much memory consumption: %llu", total_memory); + send(socket, SMTP_RESP_451_ERR_TOO_MANY_REQUESTS, strlen(SMTP_RESP_451_ERR_TOO_MANY_REQUESTS), 0); + return -1; + } + slot = get_session_slot(sessions, cfg->max_connections); if(slot >= 0 && sessions[slot] == NULL){ @@ -84,11 +129,8 @@ struct smtp_session *get_session_by_socket(struct smtp_session **sessions, int m void init_smtp_session(struct smtp_session *session, int slot, int sd, char *client_addr, struct config *cfg){ - int i; - session->slot = slot; - session->buflen = 0; session->protocol_state = SMTP_STATE_INIT; session->cfg = cfg; @@ -100,27 +142,24 @@ void init_smtp_session(struct smtp_session *session, int slot, int sd, char *cli session->net.ssl = NULL; session->nullbyte = 0; - session->last_data_char = 0; - session->fd = -1; - - memset(session->mailfrom, 0, SMALLBUFSIZE); - - session->num_of_rcpt_to = 0; - for(i=0; ircptto[i], 0, SMALLBUFSIZE); - - memset(session->buf, 0, MAXBUFSIZE); snprintf(session->remote_host, sizeof(session->remote_host)-1, "%s", client_addr); - reset_bdat_counters(session); + session->buf = NULL; + session->buflen = 0; + session->bufsize = 0; - time(&(session->lasttime)); + reset_smtp_session(session); } void free_smtp_session(struct smtp_session *session){ if(session){ + if(session->buf != NULL){ + free(session->buf); + } + if(session->net.use_ssl == 1){ SSL_shutdown(session->net.ssl); SSL_free(session->net.ssl); @@ -160,71 +199,126 @@ void tear_down_session(struct smtp_session **sessions, int slot, int *num_connec } +inline int get_last_newline_position(char *buf, int buflen){ + int i; + + for(i=buflen; i>0; i--){ + if(*(buf+i) == '\n'){ + i++; + break; + } + } + + return i; +} + + +void flush_buffer(struct smtp_session *session){ + // In the DATA phase skip the 1st character if it's a dot (.) + // and there are more characters before the trailing CR-LF + // + // See https://www.ietf.org/rfc/rfc5321.html#section-4.5.2 for more + for(int i=0; ibuflen; i++){ + if(*(session->buf+i) == '\n' && *(session->buf+i+1) == '.' && *(session->buf+i+2) == '.'){ + int dst = i + 2; + int src = dst + 1; + int l = session->buflen - src; + memmove(session->buf + dst, session->buf + src, l); + session->buflen -= 1; + } + } + + // Exclude the trailing \r\n.\r\n sequence + + session->buflen -= 5; + + if(write(session->fd, session->buf, session->buflen) != session->buflen){ + session->bad = 1; + syslog(LOG_PRIORITY, "ERROR (line: %d) %s: failed to write %d bytes", __LINE__, __func__, session->buflen); + } + + session->tot_len = session->buflen; +} + + void handle_data(struct smtp_session *session, char *readbuf, int readlen, struct config *cfg){ - int puflen, rc, nullbyte; - char *p, copybuf[BIGBUFSIZE+MAXBUFSIZE], puf[MAXBUFSIZE]; + // Update lasttime if we have something to process + time(&(session->lasttime)); - // if there's something in the saved buffer, then let's merge them + if(session->protocol_state == SMTP_STATE_BDAT){ + process_bdat(session, readbuf, readlen, cfg); + return; + } - int remaininglen = readlen + session->buflen; + // realloc memory if the new chunk doesn't fit in - if(session->buflen > 0){ - memset(copybuf, 0, sizeof(copybuf)); + if(session->buflen + readlen + 10 > session->bufsize){ + // Handle if the current memory allocation for this email is above the max_message_size threshold - memcpy(copybuf, session->buf, session->buflen); - memcpy(©buf[session->buflen], readbuf, readlen); + if(session->buflen > cfg->max_message_size){ + if(session->too_big == 0) syslog(LOG_PRIORITY, "ERROR: too big email: %d vs %d", session->buflen, cfg->max_message_size); + session->bad = 1; + session->too_big = 1; + } + if(session->bad == 0){ + char *q = realloc(session->buf, session->bufsize + SMTPBUFSIZE); + if(q){ + session->buf = q; + memset(session->buf+session->bufsize, 0, SMTPBUFSIZE); + session->bufsize += SMTPBUFSIZE; + } else { + syslog(LOG_PRIORITY, "ERROR: realloc %s %s %d", session->ttmpfile, __func__, __LINE__); + session->bad = 1; + } + } + } + + // process smtp command + if(session->protocol_state != SMTP_STATE_DATA){ + + // We got ~2 MB of garbage and no valid smtp command + // Terminate the connection + if(session->buflen + readlen > SMTPBUFSIZE - 10){ + session->bad = 1; + } + + // We are at the beginning of the smtp transaction + if(session->bad == 1){ + write1(&(session->net), SMTP_RESP_451_ERR, strlen(SMTP_RESP_451_ERR)); + syslog(LOG_PRIORITY, "ERROR: sent 451 temp error back to client %s", session->ttmpfile); + return; + } + + //printf("got %d *%s*\n", readlen, readbuf); + + memcpy(session->buf + session->buflen, readbuf, readlen); + session->buflen += readlen; + + int pos = get_last_newline_position(session->buf, session->buflen); + + if(pos < readlen) return; // no complete command + + process_smtp_command(session, cfg); + + memset(session->buf, 0, session->bufsize); session->buflen = 0; - memset(session->buf, 0, MAXBUFSIZE); - p = ©buf[0]; - } - else { - readbuf[readlen] = 0; - p = readbuf; + return; } + if(session->bad == 0){ + memcpy(session->buf + session->buflen, readbuf, readlen); + session->buflen += readlen; - do { - puflen = read_one_line(p, remaininglen, '\n', puf, sizeof(puf)-1, &rc, &nullbyte); - p += puflen; - remaininglen -= puflen; - - if(nullbyte){ - session->nullbyte = 1; + char *p = session->buf + session->buflen - 5; + if(session->buflen >= 5 && GOT_CRLF_DOT_CRLF(p)){ + flush_buffer(session); + process_command_period(session); } - - // complete line: rc == OK and puflen > 0 - // incomplete line with something in the buffer: rc == ERR and puflen > 0 - - if(puflen > 0){ - // Update lasttime if we have a line to process - time(&(session->lasttime)); - - // Save incomplete line to buffer - if(rc == ERR){ - memcpy(session->buf, puf, puflen); - session->buflen = puflen; - } - - // We have a complete line to process - - if(rc == OK){ - if(session->protocol_state == SMTP_STATE_BDAT){ - process_bdat(session, puf, puflen, cfg); - } - else if(session->protocol_state == SMTP_STATE_DATA){ - sig_block(SIGALRM); - process_data(session, puf, puflen); - sig_unblock(SIGALRM); - } - else { - process_smtp_command(session, puf, cfg); - } - } - } - - } while(puflen > 0); + } else if(strstr(readbuf, "\r\n.\r\n")){ + process_command_period(session); + } } diff --git a/src/smtp.c b/src/smtp.c index 7d9ee749..84503cfe 100644 --- a/src/smtp.c +++ b/src/smtp.c @@ -15,59 +15,59 @@ #include "smtp.h" -void process_smtp_command(struct smtp_session *session, char *buf, struct config *cfg){ +void process_smtp_command(struct smtp_session *session, struct config *cfg){ char response[SMALLBUFSIZE]; - if(session->cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "got on fd=%d: *%s*", session->net.socket, buf); + if(session->cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "got on fd=%d: *%s*", session->net.socket, session->buf); - if(strncasecmp(buf, SMTP_CMD_HELO, strlen(SMTP_CMD_HELO)) == 0){ + if(strncasecmp(session->buf, SMTP_CMD_HELO, strlen(SMTP_CMD_HELO)) == 0){ process_command_helo(session, response, sizeof(response)); return; } - if(strncasecmp(buf, SMTP_CMD_EHLO, strlen(SMTP_CMD_EHLO)) == 0 || - strncasecmp(buf, LMTP_CMD_LHLO, strlen(LMTP_CMD_LHLO)) == 0){ - process_command_ehlo_lhlo(session, response, sizeof(response)); + if(strncasecmp(session->buf, SMTP_CMD_EHLO, strlen(SMTP_CMD_EHLO)) == 0 || + strncasecmp(session->buf, LMTP_CMD_LHLO, strlen(LMTP_CMD_LHLO)) == 0){ + process_command_ehlo_lhlo(session, response, sizeof(response), cfg); return; } - if(strncasecmp(buf, SMTP_CMD_HELP, strlen(SMTP_CMD_HELP)) == 0){ + if(strncasecmp(session->buf, SMTP_CMD_HELP, strlen(SMTP_CMD_HELP)) == 0){ send_smtp_response(session, SMTP_RESP_221_PILER_SMTP_OK); return; } - if(strncasecmp(buf, SMTP_CMD_MAIL_FROM, strlen(SMTP_CMD_MAIL_FROM)) == 0){ - process_command_mail_from(session, buf); + if(strncasecmp(session->buf, SMTP_CMD_MAIL_FROM, strlen(SMTP_CMD_MAIL_FROM)) == 0){ + process_command_mail_from(session); return; } - if(strncasecmp(buf, SMTP_CMD_RCPT_TO, strlen(SMTP_CMD_RCPT_TO)) == 0){ - process_command_rcpt_to(session, buf, cfg); + if(strncasecmp(session->buf, SMTP_CMD_RCPT_TO, strlen(SMTP_CMD_RCPT_TO)) == 0){ + process_command_rcpt_to(session, cfg); return; } - if(strncasecmp(buf, SMTP_CMD_DATA, strlen(SMTP_CMD_DATA)) == 0){ + if(strncasecmp(session->buf, SMTP_CMD_DATA, strlen(SMTP_CMD_DATA)) == 0){ process_command_data(session, cfg); return; } /* Support only BDAT xxxx LAST command */ - if(session->cfg->enable_chunking == 1 && strncasecmp(buf, SMTP_CMD_BDAT, strlen(SMTP_CMD_BDAT)) == 0 && strcasestr(buf, "LAST")){ - get_bdat_size_to_read(session, buf); + if(session->cfg->enable_chunking == 1 && strncasecmp(session->buf, SMTP_CMD_BDAT, strlen(SMTP_CMD_BDAT)) == 0 && strcasestr(session->buf, "LAST")){ + get_bdat_size_to_read(session); return; } - if(strncasecmp(buf, SMTP_CMD_QUIT, strlen(SMTP_CMD_QUIT)) == 0){ + if(strncasecmp(session->buf, SMTP_CMD_QUIT, strlen(SMTP_CMD_QUIT)) == 0){ process_command_quit(session, response, sizeof(response)); return; } - if(strncasecmp(buf, SMTP_CMD_RESET, strlen(SMTP_CMD_RESET)) == 0){ + if(strncasecmp(session->buf, SMTP_CMD_RESET, strlen(SMTP_CMD_RESET)) == 0){ process_command_reset(session); return; } - if(session->cfg->tls_enable == 1 && strncasecmp(buf, SMTP_CMD_STARTTLS, strlen(SMTP_CMD_STARTTLS)) == 0 && session->net.use_ssl == 0){ + if(session->cfg->tls_enable == 1 && strncasecmp(session->buf, SMTP_CMD_STARTTLS, strlen(SMTP_CMD_STARTTLS)) == 0 && session->net.use_ssl == 0){ process_command_starttls(session); return; } @@ -76,41 +76,6 @@ void process_smtp_command(struct smtp_session *session, char *buf, struct config } -void process_data(struct smtp_session *session, char *buf, int buflen){ - if(session->last_data_char == '\n' && strcmp(buf, ".\r\n") == 0){ - process_command_period(session); - } - else { - // write line to file - int written=0, n_writes=0; - - // In the DATA phase skip the 1st character if it's a dot (.) - // and there are more characters before the trailing CR-LF - // - // See https://www.ietf.org/rfc/rfc5321.html#section-4.5.2 for more. - - int dotstuff = 0; - if(*buf == '.' && buflen > 1 && *(buf+1) != '\r' && *(buf+1) != '\n') dotstuff = 1; - - while(written < buflen) { - int len = write(session->fd, buf+dotstuff+written, buflen-dotstuff-written); - - n_writes++; - - if(len > 0){ - if(len != buflen-dotstuff) syslog(LOG_PRIORITY, "WARN: partial write: %d/%d bytes (round: %d)", len, buflen-dotstuff, n_writes); - written += len + dotstuff; - session->tot_len += len; - dotstuff = 0; - } - else syslog(LOG_PRIORITY, "ERROR (line: %d) process_data(): written %d bytes", __LINE__, len); - } - } - - session->last_data_char = buf[buflen-1]; -} - - void wait_for_ssl_accept(struct smtp_session *session){ int rc; @@ -152,7 +117,7 @@ void process_command_helo(struct smtp_session *session, char *buf, int buflen){ } -void process_command_ehlo_lhlo(struct smtp_session *session, char *buf, int buflen){ +void process_command_ehlo_lhlo(struct smtp_session *session, char *buf, int buflen, struct config *cfg){ char extensions[SMALLBUFSIZE]; memset(extensions, 0, sizeof(extensions)); @@ -163,7 +128,9 @@ void process_command_ehlo_lhlo(struct smtp_session *session, char *buf, int bufl if(session->net.use_ssl == 0 && session->cfg->tls_enable == 1) snprintf(extensions, sizeof(extensions)-1, "%s", SMTP_EXTENSION_STARTTLS); if(session->cfg->enable_chunking == 1) strncat(extensions, SMTP_EXTENSION_CHUNKING, sizeof(extensions)-strlen(extensions)-2); - snprintf(buf, buflen-1, SMTP_RESP_250_EXTENSIONS, session->cfg->hostid, extensions); + //#define SMTP_RESP_250_EXTENSIONS "250-%s\r\n%s250-SIZE %d\r\n250 8BITMIME\r\n" + + snprintf(buf, buflen-1, SMTP_RESP_250_EXTENSIONS, session->cfg->hostid, cfg->max_message_size, extensions); send_smtp_response(session, buf); } @@ -234,26 +201,29 @@ void process_command_starttls(struct smtp_session *session){ } -void process_command_mail_from(struct smtp_session *session, char *buf){ +void process_command_mail_from(struct smtp_session *session){ + memset(session->mailfrom, 0, SMALLBUFSIZE); + if(session->protocol_state != SMTP_STATE_HELO && session->protocol_state != SMTP_STATE_PERIOD && session->protocol_state != SMTP_STATE_BDAT){ send(session->net.socket, SMTP_RESP_503_ERR, strlen(SMTP_RESP_503_ERR), 0); } else { - memset(&(session->ttmpfile[0]), 0, SMALLBUFSIZE); - make_random_string((unsigned char*)&(session->ttmpfile[0]), QUEUE_ID_LEN); session->protocol_state = SMTP_STATE_MAIL_FROM; - extractEmail(buf, session->mailfrom); + extractEmail(session->buf, session->mailfrom); - reset_bdat_counters(session); - session->tot_len = 0; + int mailsize = get_size_from_smtp_mail_from(session->buf); + + reset_smtp_session(session); + + session->mail_size = mailsize; send_smtp_response(session, SMTP_RESP_250_OK); } } -void process_command_rcpt_to(struct smtp_session *session, char *buf, struct config *cfg){ +void process_command_rcpt_to(struct smtp_session *session, struct config *cfg){ if(session->protocol_state == SMTP_STATE_MAIL_FROM || session->protocol_state == SMTP_STATE_RCPT_TO){ @@ -262,7 +232,7 @@ void process_command_rcpt_to(struct smtp_session *session, char *buf, struct con session->protocol_state = SMTP_STATE_RCPT_TO; if(session->num_of_rcpt_to < MAX_RCPT_TO){ - extractEmail(buf, session->rcptto[session->num_of_rcpt_to]); + extractEmail(session->buf, session->rcptto[session->num_of_rcpt_to]); // Check if we should accept archive_address only if(cfg->archive_address[0] && !strstr(cfg->archive_address, session->rcptto[session->num_of_rcpt_to])){ @@ -322,13 +292,23 @@ void process_command_period(struct smtp_session *session){ syslog(LOG_PRIORITY, "received: %s, from=%s, size=%d, client=%s, fd=%d, fsync=%ld", session->ttmpfile, session->mailfrom, session->tot_len, session->remote_host, session->net.socket, tvdiff(tv2, tv1)); - move_email(session); + if(session->bad == 1 || session->too_big == 1){ + snprintf(buf, sizeof(buf)-1, SMTP_RESP_451_ERR); + unlink(session->ttmpfile); + syslog(LOG_PRIORITY, "ERROR: problem in processing, removing %s", session->ttmpfile); + if(session->too_big) + snprintf(buf, sizeof(buf)-1, "%s", SMTP_RESP_552_ERR_TOO_BIG_EMAIL); + else + snprintf(buf, sizeof(buf)-1, "%s", SMTP_RESP_451_ERR); + } else { + move_email(session); + snprintf(buf, sizeof(buf)-1, "250 OK <%s>\r\n", session->ttmpfile); + } - snprintf(buf, sizeof(buf)-1, "250 OK <%s>\r\n", session->ttmpfile); - - session->buflen = 0; - session->last_data_char = 0; - memset(session->buf, 0, sizeof(session->buf)); + if(session->buf){ + memset(session->buf, 0, session->bufsize); + session->buflen = 0; + } send_smtp_response(session, buf); } @@ -344,15 +324,32 @@ void process_command_quit(struct smtp_session *session, char *buf, int buflen){ void process_command_reset(struct smtp_session *session){ + reset_smtp_session(session); send_smtp_response(session, SMTP_RESP_250_OK); - - session->tot_len = 0; - session->fd = -1; session->protocol_state = SMTP_STATE_HELO; - session->last_data_char = 0; +} + + +void reset_smtp_session(struct smtp_session *session){ + session->tot_len = 0; + session->mail_size = 0; + session->too_big = 0; + session->bad = 0; + + session->fd = -1; + + session->num_of_rcpt_to = 0; + for(int i=0; ircptto[i], 0, SMALLBUFSIZE); reset_bdat_counters(session); + time(&(session->lasttime)); + memset(&(session->ttmpfile[0]), 0, SMALLBUFSIZE); - make_random_string((unsigned char *)&(session->ttmpfile[0]), QUEUE_ID_LEN); + make_random_string((unsigned char*)&(session->ttmpfile[0]), QUEUE_ID_LEN); + + if(session->buf){ + memset(session->buf, 0, session->bufsize); + } + session->buflen = 0; } diff --git a/src/smtp.h b/src/smtp.h index a6858d17..4803c165 100644 --- a/src/smtp.h +++ b/src/smtp.h @@ -7,22 +7,24 @@ #include -void process_smtp_command(struct smtp_session *session, char *buf, struct config *cfg); +void process_smtp_command(struct smtp_session *session, struct config *cfg); void process_data(struct smtp_session *session, char *buf, int buflen); void send_smtp_response(struct smtp_session *session, char *buf); void process_command_helo(struct smtp_session *session, char *buf, int buflen); -void process_command_ehlo_lhlo(struct smtp_session *session, char *buf, int buflen); +void process_command_ehlo_lhlo(struct smtp_session *session, char *buf, int buflen, struct config *cfg); void process_command_quit(struct smtp_session *session, char *buf, int buflen); void process_command_reset(struct smtp_session *session); -void process_command_mail_from(struct smtp_session *session, char *buf); -void process_command_rcpt_to(struct smtp_session *session, char *buf, struct config *cfg); +void process_command_mail_from(struct smtp_session *session); +void process_command_rcpt_to(struct smtp_session *session, struct config *cfg); void process_command_data(struct smtp_session *session, struct config *cfg); void process_command_period(struct smtp_session *session); void process_command_starttls(struct smtp_session *session); void reset_bdat_counters(struct smtp_session *session); -void get_bdat_size_to_read(struct smtp_session *session, char *buf); +void get_bdat_size_to_read(struct smtp_session *session); void process_bdat(struct smtp_session *session, char *readbuf, int readlen, struct config *cfg); +void reset_smtp_session(struct smtp_session *session); + #endif diff --git a/src/smtpcodes.h b/src/smtpcodes.h index 01e124eb..2e8c6c48 100644 --- a/src/smtpcodes.h +++ b/src/smtpcodes.h @@ -38,7 +38,7 @@ #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\n%s250-SIZE\r\n250 8BITMIME\r\n" +#define SMTP_RESP_250_EXTENSIONS "250-%s\r\n250-SIZE %d\r\n%s250 8BITMIME\r\n" #define SMTP_EXTENSION_STARTTLS "250-STARTTLS\r\n" #define SMTP_EXTENSION_CHUNKING "250-CHUNKING\r\n" @@ -51,6 +51,7 @@ #define SMTP_RESP_421_ERR_TMP "421 %s service not available\r\n" #define SMTP_RESP_421_ERR_WRITE_FAILED "421 writing queue file failed\r\n" #define SMTP_RESP_421_ERR_ALL_PORTS_ARE_BUSY "421 All server ports are busy\r\n" +#define SMTP_RESP_451_ERR_TOO_MANY_REQUESTS "451 Too many requests, try again later\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" @@ -59,6 +60,7 @@ #define SMTP_RESP_550_ERR_INVALID_RECIPIENT "550 Invalid recipient\r\n" #define SMTP_RESP_550_ERR_YOU_ARE_BANNED_BY_LOCAL_POLICY "550 You are banned by local policy\r\n" #define SMTP_RESP_550_ERR "550 Service currently unavailable\r\n" +#define SMTP_RESP_552_ERR_TOO_BIG_EMAIL "552 Too big email\r\n" // LMTP commands diff --git a/src/tai.h b/src/tai.h index 6b670fdf..8e8d894a 100644 --- a/src/tai.h +++ b/src/tai.h @@ -5,12 +5,12 @@ #ifndef _TAI_H #define _TAI_H +#include "config.h" + #define TAI_PACK 8 #define TAIA_PACK 16 #define TIMESTAMP 25 -typedef unsigned long long uint64; - struct tai { uint64 x; }; diff --git a/tests/cases/05-smtp.inc b/tests/cases/05-smtp.inc index ae269876..56171afa 100644 --- a/tests/cases/05-smtp.inc +++ b/tests/cases/05-smtp.inc @@ -6,17 +6,17 @@ case1() { setup - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Inbox" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Inbox2" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Levelszemet" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Levelszemet2" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam0" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam1" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam2" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/journal" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/deduptest" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/special" --socket --no-counter - "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu extra@addr.ess another@extra.addr -p 25 -t 20 --dir "$EML_DIR/virus" --socket --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Inbox" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Inbox2" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Levelszemet" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/Levelszemet2" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam0" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam1" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/spam2" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/journal" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/deduptest" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu -p 25 -t 20 --dir "$EML_DIR/special" --no-counter + "$SMTP_SOURCE_PROG" -s $SMTP_HOST -r archive@cust1.acts.hu extra@addr.ess another@extra.addr -p 25 -t 20 --dir "$EML_DIR/virus" --no-counter wait_until_emails_are_processed "piler1" 3020 diff --git a/unit_tests/run.sh b/unit_tests/run.sh index 8d7169c2..e0df424d 100755 --- a/unit_tests/run.sh +++ b/unit_tests/run.sh @@ -24,6 +24,13 @@ setup_mysql() { mysql -u piler -ppiler123 piler1 < ../util/db-mysql.sql } +run_smtp_tests() { + mkdir -p /var/piler/store/00/piler /var/piler/tmp /var/piler/manticore + chown -R piler:piler /var/piler/ + ../src/piler-smtp -L 5 -d + ./smtp -s 127.0.0.1 +} + if [[ -v BUILD_NUMBER ]]; then setup_mysql fi @@ -37,3 +44,5 @@ fi ./check_hash ./check_decoder ./check_attachments + +if [[ -v BUILD_NUMBER ]]; then run_smtp_tests; fi diff --git a/unit_tests/smtp.c b/unit_tests/smtp.c index 31914c74..cf63b797 100644 --- a/unit_tests/smtp.c +++ b/unit_tests/smtp.c @@ -4,11 +4,13 @@ #include "test.h" - extern char *optarg; extern int optind; -char *testmessage = "From: aaa@aaa.fu\nTo: bela@aaa.fu\nMessage-Id: ajajajaja\nSubject: this is a test\n\nAaaaaa."; +char *testmessage = "From: aaa@aaa.fu\r\nTo: bela@aaa.fu\r\nMessage-Id: ajajajaja\r\nSubject: this is a test\r\n\r\nAaaaaa"; +char *testmessage2 = " and the last line\r\n"; + +char *recipient = "aaa@worker0"; int helo = 0; // 0=HELO, 1=EHLO @@ -16,12 +18,24 @@ void usage(){ printf("\nusage: smtp\n\n"); printf(" -s SMTP server\n"); printf(" -p SMTP port (25)\n"); + printf(" -r Envelope recipient\n"); printf(" -t Timeout in sec (10)\n"); exit(0); } +int countreplies(char *s){ + int replies = 0; + + for(; *s; s++){ + if(*s == '\n') replies++; + } + + return replies; +} + + void connect_to_smtp_server(char *server, int port, struct data *data){ int rc; char port_string[8], buf[MAXBUFSIZE]; @@ -53,14 +67,15 @@ void connect_to_smtp_server(char *server, int port, struct data *data){ } recvtimeoutssl(data->net, buf, sizeof(buf)); - printf("rcvd: %s", buf); ENDE: freeaddrinfo(res); } -void send_smtp_command(struct net *net, char *cmd, char *buf, int buflen){ +void send_smtp_command(struct net *net, char *cmd, char *buf, int buflen, int expectedreplies){ + int tot=0; + if(net == NULL || cmd == NULL) return; if(net->socket == -1){ @@ -68,25 +83,32 @@ void send_smtp_command(struct net *net, char *cmd, char *buf, int buflen){ return; } - printf("sent: %s", cmd); write1(net, cmd, strlen(cmd)); - recvtimeoutssl(net, buf, buflen); - printf("rcvd: %s", buf); + + while(1){ + tot += recvtimeoutssl(net, buf+tot, buflen); + if(countreplies(buf) == expectedreplies) break; + } } void send_helo_command(struct net *net){ + int replies=1; char recvbuf[MAXBUFSIZE]; if(helo == 0){ - send_smtp_command(net, "HELO aaaa.fu\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "HELO"); + send_smtp_command(net, "HELO aaaa.fu\r\n", recvbuf, sizeof(recvbuf)-1, replies); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); } else { - send_smtp_command(net, "EHLO aaaa.fu\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250-", 4) == 0 && "EHLO"); - if(net->use_ssl == 0) assert(strstr(recvbuf, "250-STARTTLS") && "STARTTLS"); - else assert(strstr(recvbuf, "250-STARTTLS") == NULL && "STARTTLS"); + //replies = 6; + replies = 5; + if(net->use_ssl == 1) replies--; + + send_smtp_command(net, "EHLO aaaa.fu\r\n", recvbuf, sizeof(recvbuf)-1, replies); + ASSERT(strncmp(recvbuf, "250-", 4) == 0, recvbuf); + if(net->use_ssl == 0){ ASSERT(strstr(recvbuf, "250-STARTTLS"), recvbuf); } + else { ASSERT(strstr(recvbuf, "250-STARTTLS") == NULL, recvbuf); } } } @@ -94,209 +116,392 @@ void send_helo_command(struct net *net){ static void test_smtp_commands_one_at_a_time(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "RCPT TO: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "RCPT"); + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "354 ", 4) == 0 && "DATA"); + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\n", testmessage); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "PERIOD"); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "221 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); } -static void test_smtp_commands_pipelining(char *server, int port, struct data *data){ +static void test_smtp_commands_one_at_a_time_data_in_2_parts(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\nRCPT TO: \r\nDATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); + + write1(data->net, testmessage, strlen(testmessage)); + + snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\n", testmessage2); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); + + close(data->net->socket); + + TEST_FOOTER(); +} + + +/*static void test_smtp_commands_pipelining(char *server, int port, struct data *data){ + char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + + TEST_HEADER(); + + connect_to_smtp_server(server, port, data); + + send_helo_command(data->net); + + snprintf(sendbuf, sizeof(sendbuf)-1, "MAIL FROM: \r\nRCPT TO: <%s>\r\nDATA\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 3); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\nQUIT\r\n", testmessage); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 2); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); close(data->net->socket); -} + + TEST_FOOTER(); +}*/ static void test_smtp_commands_with_reset_command(char *server, int port, struct data *data){ - char recvbuf[MAXBUFSIZE]; + char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + + TEST_HEADER(); connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "RSET\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "RSET"); + send_smtp_command(data->net, "RSET\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "RCPT TO: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "503 ", 4) == 0 && "RCPT"); + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "503 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "221 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); } static void test_smtp_commands_partial_command(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); write1(data->net, "M", 1); - printf("sent: M\n"); - send_smtp_command(data->net, "AIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + send_smtp_command(data->net, "AIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "RCPT TO: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "RCPT"); + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "354 ", 4) == 0 && "DATA"); + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\n", testmessage); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "PERIOD"); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "221 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); } -static void test_smtp_commands_partial_command_pipelining(char *server, int port, struct data *data){ +/*static void test_smtp_commands_partial_command_pipelining(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); write1(data->net, "M", 1); - printf("sent: M\n"); - send_smtp_command(data->net, "AIL FROM: \r\nRCPT TO: \r\nDATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + snprintf(sendbuf, sizeof(sendbuf)-1, "AIL FROM: \r\nRCPT TO: <%s>\r\nDATA\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 3); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\nQUIT\r\n", testmessage); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 2); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); close(data->net->socket); -} + + TEST_FOOTER(); +}*/ static void test_smtp_commands_starttls(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "STARTTLS\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "220 ", 4) == 0 && "STARTTLS"); + send_smtp_command(data->net, "STARTTLS\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "220 ", 4) == 0, recvbuf); init_ssl_to_server(data); data->net->use_ssl = 1; send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "RCPT TO: \r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "RCPT"); + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "354 ", 4) == 0 && "DATA"); + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\n", testmessage); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "PERIOD"); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); - send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "221 ", 4) == 0 && "QUIT"); + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); } static void test_smtp_commands_period_command_in_2_parts(char *server, int port, char *part1, char *part2, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\nRCPT TO: \r\nDATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + // As long as pipelining support is not reintroduced + // + /*snprintf(sendbuf, sizeof(sendbuf)-1, "MAIL FROM: \r\nRCPT TO: <%s>\r\nDATA\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 3); + if(!strstr(recvbuf, "354 ")) recvtimeoutssl(data->net, recvbuf, sizeof(recvbuf)-1); + ASSERT(strstr(recvbuf, "354 "), recvbuf);*/ + + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); + snprintf(sendbuf, sizeof(sendbuf)-1, "%s%s", testmessage, part1); write1(data->net, sendbuf, strlen(sendbuf)); snprintf(sendbuf, sizeof(sendbuf), "%s", part2); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "QUIT\r\n"); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "QUIT"); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); } static void test_smtp_commands_period_command_in_its_own_packet(char *server, int port, struct data *data){ char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + TEST_HEADER(); + connect_to_smtp_server(server, port, data); send_helo_command(data->net); - send_smtp_command(data->net, "MAIL FROM: \r\nRCPT TO: \r\nDATA\r\n", recvbuf, sizeof(recvbuf)-1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "MAIL"); + // As long as pipelining support is not reintroduced + // + /*snprintf(sendbuf, sizeof(sendbuf)-1, "MAIL FROM: \r\nRCPT TO: <%s>\r\nDATA\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 3); + if(!strstr(recvbuf, "354 ")) recvtimeoutssl(data->net, recvbuf, sizeof(recvbuf)-1); + ASSERT(strstr(recvbuf, "354 "), recvbuf);*/ + + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "%s", testmessage); write1(data->net, sendbuf, strlen(sendbuf)); snprintf(sendbuf, sizeof(sendbuf), "\r\n.\r\n"); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); snprintf(sendbuf, sizeof(sendbuf)-1, "QUIT\r\n"); - send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); - assert(strncmp(recvbuf, "250 ", 4) == 0 && "QUIT"); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); close(data->net->socket); + + TEST_FOOTER(); +} + + +static void test_smtp_commands_with_partial_data_lines(char *server, int port, struct data *data){ + char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + int yes=1; + + TEST_HEADER(); + + connect_to_smtp_server(server, port, data); + + setsockopt(data->net->socket, IPPROTO_TCP, TCP_NODELAY, &yes, (socklen_t)sizeof(int)); + + send_helo_command(data->net); + + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "DATA\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "354 ", 4) == 0, recvbuf); + + + snprintf(sendbuf, sizeof(sendbuf)-1, "From: aaa@aaa.fu\r\nTo: bela@aaa.fu\r\nMessage-Id: ajajajaja\r\nSubject: this is a test\r\n\r\nAaaaaa"); + write1(data->net, sendbuf, strlen(sendbuf)); sleep(2); + + snprintf(sendbuf, sizeof(sendbuf)-1, "jjdkdjkd dkd some garbage."); + write1(data->net, sendbuf, strlen(sendbuf)); sleep(2); + + snprintf(sendbuf, sizeof(sendbuf)-1, "\r\nSome shit again au aua ua au aua uuu"); + write1(data->net, sendbuf, strlen(sendbuf)); sleep(2); + + snprintf(sendbuf, sizeof(sendbuf)-1, ".\r\nAnd the last line.\r\n.\r\n"); + write1(data->net, sendbuf, strlen(sendbuf)); + + snprintf(sendbuf, sizeof(sendbuf)-1, "%s\r\n.\r\nQUIT\r\n", testmessage); + recvtimeoutssl(data->net, recvbuf, sizeof(recvbuf)); + + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + + close(data->net->socket); + + TEST_FOOTER(); +} + + +static void test_smtp_bdat_last_one_at_a_time(char *server, int port, struct data *data){ + char recvbuf[MAXBUFSIZE], sendbuf[MAXBUFSIZE]; + + TEST_HEADER(); + + connect_to_smtp_server(server, port, data); + + send_helo_command(data->net); + + send_smtp_command(data->net, "MAIL FROM: \r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "RCPT TO: <%s>\r\n", recipient); + send_smtp_command(data->net, sendbuf, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + snprintf(sendbuf, sizeof(sendbuf)-1, "BDAT %ld LAST\r\n", strlen(testmessage)+strlen(testmessage2)); + write1(data->net, sendbuf, strlen(sendbuf)); + + write1(data->net, testmessage, strlen(testmessage)); + + send_smtp_command(data->net, testmessage2, recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "250 ", 4) == 0, recvbuf); + + send_smtp_command(data->net, "QUIT\r\n", recvbuf, sizeof(recvbuf)-1, 1); + ASSERT(strncmp(recvbuf, "221 ", 4) == 0, recvbuf); + + close(data->net->socket); + + TEST_FOOTER(); } @@ -317,6 +522,7 @@ int main(int argc, char **argv){ { {"server", required_argument, 0, 's' }, {"port", required_argument, 0, 'p' }, + {"rcpt", required_argument, 0, 'r' }, {"timeout", required_argument, 0, 't' }, {"lhlo", no_argument, 0, 'l' }, {"help", no_argument, 0, 'h' }, @@ -325,9 +531,9 @@ int main(int argc, char **argv){ int option_index = 0; - int c = getopt_long(argc, argv, "c:s:p:t:lh?", long_options, &option_index); + int c = getopt_long(argc, argv, "c:s:p:t:r:lh?", long_options, &option_index); #else - int c = getopt(argc, argv, "c:s:p:t:lh?"); + int c = getopt(argc, argv, "c:s:p:t:r:lh?"); #endif @@ -343,6 +549,10 @@ int main(int argc, char **argv){ port = atoi(optarg); break; + case 'r' : + recipient = optarg; + break; + case 't' : net.timeout = atoi(optarg); break; @@ -357,7 +567,7 @@ int main(int argc, char **argv){ break; - default : + default : break; } } @@ -367,21 +577,26 @@ int main(int argc, char **argv){ data.net = &net; test_smtp_commands_one_at_a_time(server, port, &data); - test_smtp_commands_pipelining(server, port, &data); + test_smtp_commands_one_at_a_time_data_in_2_parts(server, port, &data); + ////test_smtp_commands_pipelining(server, port, &data); test_smtp_commands_with_reset_command(server, port, &data); test_smtp_commands_partial_command(server, port, &data); - test_smtp_commands_partial_command_pipelining(server, port, &data); + ////test_smtp_commands_partial_command_pipelining(server, port, &data); + test_smtp_commands_period_command_in_2_parts(server, port, "\r", "\n.\r\n", &data); test_smtp_commands_period_command_in_2_parts(server, port, "\r\n", ".\r\n", &data); test_smtp_commands_period_command_in_2_parts(server, port, "\r\n.", "\r\n", &data); test_smtp_commands_period_command_in_2_parts(server, port, "\r\n.\r", "\n", &data); test_smtp_commands_period_command_in_its_own_packet(server, port, &data); - helo = 1; // we must use EHLO to get the STARTTLS in the response - test_smtp_commands_starttls(server, port, &data); + test_smtp_commands_with_partial_data_lines(server, port, &data); + helo = 1; // we must use EHLO to get the STARTTLS in the response + + test_smtp_commands_starttls(server, port, &data); + data.net->use_ssl = 0; + + test_smtp_bdat_last_one_at_a_time(server, port, &data); return 0; } - - diff --git a/unit_tests/test.h b/unit_tests/test.h index 7002a098..1fdc9e8c 100644 --- a/unit_tests/test.h +++ b/unit_tests/test.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include