diff options
Diffstat (limited to 'common/smtppass.c')
-rw-r--r-- | common/smtppass.c | 235 |
1 files changed, 168 insertions, 67 deletions
diff --git a/common/smtppass.c b/common/smtppass.c index c2d2136..b13bdac 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -174,6 +174,8 @@ static int smtp_passthru(spctx_t* ctx); static int connect_out(spctx_t* ctx); static int read_server_response(spctx_t* ctx); static int parse_config_file(const char* configfile); +static char* parse_address(char* line); +static int is_successful_rsp(char* line); /* Used externally in some cases */ int sp_parse_option(const char* name, const char* option); @@ -602,6 +604,18 @@ static void cleanup_context(spctx_t* ctx) ctx->cachename[0] = 0; } + if(ctx->recipients) + { + free(ctx->recipients); + ctx->recipients = NULL; + } + + if(ctx->sender) + { + free(ctx->sender); + ctx->sender = NULL; + } + ctx->logline[0] = 0; } @@ -762,17 +776,20 @@ static int connect_out(spctx_t* ctx) static int smtp_passthru(spctx_t* ctx) { + char* t; int r, ret = 0; unsigned int mask; int neterror = 0; int first_rsp = 1; /* The first 220 response from server to be filtered */ - int filter_ehlo = 0; /* Filtering parts of an EHLO extensions response */ int filter_host = 0; /* Next response is 250 hostname, which we change */ ASSERT(spio_valid(&(ctx->client)) && spio_valid(&(ctx->server))); + #define C_LINE ctx->client.line + #define S_LINE ctx->server.line + for(;;) { mask = spio_select(ctx, &(ctx->client), &(ctx->server), NULL); @@ -786,15 +803,15 @@ static int smtp_passthru(spctx_t* ctx) /* Client has data available, read a line and process */ if(mask & 1) { - if(spio_read_line(ctx, &(ctx->client), SPIO_DISCARD) == -1) + if((r = spio_read_line(ctx, &(ctx->client), SPIO_DISCARD)) == -1) RETURN(-1); /* Client disconnected, we're done */ - if(ctx->linelen == 0) + if(r == 0) RETURN(0); /* We don't let clients send really long lines */ - if(LINE_TOO_LONG(ctx)) + if(LINE_TOO_LONG(r)) { if(spio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) RETURN(-1); @@ -803,11 +820,10 @@ static int smtp_passthru(spctx_t* ctx) } /* Only valid after EHLO or HELO commands */ - filter_ehlo = 0; filter_host = 0; /* Handle the DATA section via our AV checker */ - if(is_first_word(ctx->line, DATA_CMD, KL(DATA_CMD))) + if(is_first_word(C_LINE, DATA_CMD, KL(DATA_CMD))) { /* Send back the intermediate response to the client */ if(spio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) @@ -832,29 +848,14 @@ static int smtp_passthru(spctx_t* ctx) } /* - * We filter out features that we can't support in - * the EHLO response (ESMTP). See below - */ - else if(is_first_word(ctx->line, EHLO_CMD, KL(EHLO_CMD))) - { - sp_messagex(ctx, LOG_DEBUG, "filtering EHLO response"); - filter_ehlo = 1; - filter_host = 1; - - /* New email so cleanup */ - cleanup_context(ctx); - } - - /* * We need our response to HELO to be modified in order * to prevent complaints about mail loops */ - else if(is_first_word(ctx->line, HELO_CMD, KL(HELO_CMD))) + else if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)) || + is_first_word(C_LINE, HELO_CMD, KL(HELO_CMD))) { + /* EHLO can have multline responses so we set a flag */ filter_host = 1; - - /* A new email so cleanup */ - cleanup_context(ctx); } /* @@ -862,8 +863,8 @@ static int smtp_passthru(spctx_t* ctx) * filtered out their service extensions earlier in the EHLO response. * This is just for errant clients. */ - else if(is_first_word(ctx->line, STARTTLS_CMD, KL(STARTTLS_CMD)) || - is_first_word(ctx->line, BDAT_CMD, KL(BDAT_CMD))) + else if(is_first_word(C_LINE, STARTTLS_CMD, KL(STARTTLS_CMD)) || + is_first_word(C_LINE, BDAT_CMD, KL(BDAT_CMD))) { sp_messagex(ctx, LOG_DEBUG, "ESMTP feature not supported"); @@ -874,20 +875,8 @@ static int smtp_passthru(spctx_t* ctx) continue; } - /* Append recipients to log line */ - else if((r = check_first_word(ctx->line, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0) - sp_add_log(ctx, "from=", ctx->line + r); - - /* Append sender to log line */ - else if((r = check_first_word(ctx->line, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0) - sp_add_log(ctx, "to=", ctx->line + r); - - /* Reset log line */ - else if(is_first_word(ctx->line, RSET_CMD, KL(RSET_CMD))) - cleanup_context(ctx); - /* All other commands just get passed through to server */ - if(spio_write_data(ctx, &(ctx->server), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->server), C_LINE) == -1) RETURN(-1); continue; @@ -896,13 +885,13 @@ static int smtp_passthru(spctx_t* ctx) /* Server has data available, read a line and forward */ if(mask & 2) { - if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1) + if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1) RETURN(-1); - if(ctx->linelen == 0) + if(r == 0) RETURN(0); - if(LINE_TOO_LONG(ctx)) + if(LINE_TOO_LONG(r)) sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); /* @@ -921,7 +910,7 @@ static int smtp_passthru(spctx_t* ctx) { first_rsp = 0; - if(is_first_word(ctx->line, START_RSP, KL(START_RSP))) + if(is_first_word(S_LINE, START_RSP, KL(START_RSP))) { sp_messagex(ctx, LOG_DEBUG, "intercepting initial response"); @@ -940,40 +929,49 @@ static int smtp_passthru(spctx_t* ctx) */ if(filter_host) { + /* Can have multi-line responses, and we want to be + * sure to only replace the first one. */ filter_host = 0; /* Check for a simple '250 xxxx' */ - if(is_first_word(ctx->line, OK_RSP, KL(OK_RSP))) + if(is_first_word(S_LINE, OK_RSP, KL(OK_RSP))) { sp_messagex(ctx, LOG_DEBUG, "intercepting host response"); if(spio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) RETURN(-1); + + /* A new email so cleanup */ + cleanup_context(ctx); + continue; } /* Check for the continued response '250-xxxx' */ - if(check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0) + if(check_first_word(S_LINE, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0) { sp_messagex(ctx, LOG_DEBUG, "intercepting host response"); if(spio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) RETURN(-1); + /* New email so cleanup */ + cleanup_context(ctx); + continue; } } - /* - * Filter out any EHLO responses that we can't or don't want - * to support. For example pipelining or TLS. - */ - if(filter_ehlo) + if(is_successful_rsp(S_LINE)) { - if((r = check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS)) > 0) + /* + * Filter out any EHLO responses that we can't or don't want + * to support. For example pipelining or TLS. + */ + if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD))) { - char* p = ctx->line + r; + char* p = S_LINE + r; if(is_first_word(p, ESMTP_PIPELINE, KL(ESMTP_PIPELINE)) || is_first_word(p, ESMTP_TLS, KL(ESMTP_TLS)) || is_first_word(p, ESMTP_CHUNK, KL(ESMTP_CHUNK)) || @@ -984,9 +982,45 @@ static int smtp_passthru(spctx_t* ctx) continue; } } + + /* MAIL FROM */ + if((r = check_first_word(C_LINE, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0) + { + t = parse_address(C_LINE + r); + sp_add_log(ctx, "from=", t); + + /* Make note of the sender for later */ + ctx->sender = (char*)reallocf(ctx->sender, strlen(t) + 1); + if(ctx->sender) + strcpy(ctx->sender, t); + } + + /* RCPT TO */ + else if((r = check_first_word(C_LINE, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0) + { + t = parse_address(C_LINE + r); + sp_add_log(ctx, "to=", t); + + /* Make note of the recipient for later */ + r = ctx->recipients ? strlen(ctx->recipients) : 0; + ctx->recipients = (char*)reallocf(ctx->recipients, r + strlen(t) + 2); + if(ctx->recipients) + { + /* Recipients are separated by lines */ + if(r != 0) + strcat(ctx->recipients, "\n"); + strcat(ctx->recipients, t); + } + } + + /* RSET */ + else if(is_first_word(C_LINE, RSET_CMD, KL(RSET_CMD))) + { + cleanup_context(ctx); + } } - if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->client), S_LINE) == -1) RETURN(-1); continue; @@ -1005,6 +1039,44 @@ cleanup: * SMTP PASSTHRU FUNCTIONS FOR DATA CHECK */ +static char* parse_address(char* line) +{ + char* t; + line = trim_start(line); + + /* + * We parse out emails in the form of <blah@blah.com> + * as well as accept other addresses. + */ + if(line[0] == '<') + { + if((t = strchr(line, '>')) != NULL) + { + *t = 0; + line++; + return line; + } + } + + return trim_end(line); +} + +static int is_successful_rsp(char* line) +{ + /* + * We check for both '250 xxx' type replies + * and the continued response '250-xxxx' type + */ + + line = trim_start(line); + + if(line[0] == '2' && isdigit(line[1]) && isdigit(line[2]) && + (line[3] == ' ' || line[3] == '-')) + return 1; + + return 0; +} + void sp_add_log(spctx_t* ctx, char* prefix, char* line) { int l = SP_LINE_LENGTH; @@ -1028,12 +1100,14 @@ void sp_add_log(spctx_t* ctx, char* prefix, char* line) int sp_read_data(spctx_t* ctx, const char** data) { + int r; + ASSERT(ctx); ASSERT(data); *data = NULL; - switch(spio_read_line(ctx, &(ctx->client), SPIO_QUIET)) + switch(r = spio_read_line(ctx, &(ctx->client), SPIO_QUIET)) { case 0: sp_messagex(ctx, LOG_ERR, "unexpected end of data from client"); @@ -1043,13 +1117,13 @@ int sp_read_data(spctx_t* ctx, const char** data) return -1; }; - if(ctx->_crlf && strcmp(ctx->line, DATA_END_SIG) == 0) + if(ctx->_crlf && strcmp(ctx->client.line, DATA_END_SIG) == 0) return 0; /* Check if this line ended with a CRLF */ - ctx->_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0); - *data = ctx->line; - return ctx->linelen; + ctx->_crlf = (strcmp(CRLF, ctx->client.line + (r - KL(CRLF))) == 0); + *data = ctx->client.line; + return r; } int sp_write_data(spctx_t* ctx, const char* buf, int len) @@ -1140,6 +1214,7 @@ int sp_done_data(spctx_t* ctx, const char* header) FILE* file = 0; int had_header = 0; int ret = 0; + char line[SP_LINE_LENGTH]; ASSERT(ctx->cachename[0]); /* Must still be around */ ASSERT(!ctx->cachefile); /* File must be closed */ @@ -1160,9 +1235,9 @@ int sp_done_data(spctx_t* ctx, const char* header) RETURN(-1); /* If server returns an error then tell the client */ - if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP))) + if(!is_first_word(ctx->server.line, DATA_RSP, KL(DATA_RSP))) { - if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1) RETURN(-1); sp_messagex(ctx, LOG_DEBUG, "server refused data transfer"); @@ -1173,15 +1248,24 @@ int sp_done_data(spctx_t* ctx, const char* header) sp_messagex(ctx, LOG_DEBUG, "sending from cache file: %s", ctx->cachename); /* Transfer actual file data */ - while(fgets(ctx->line, SP_LINE_LENGTH, file) != NULL) + while(fgets(line, SP_LINE_LENGTH, file) != NULL) { + /* + * If the line is <CRLF>.<CRLF> we need to change it so that + * it doesn't end the email. We do this by adding a space. + * This won't occur much in clamsmtpd, but proxsmtpd might + * have filters that accidentally put this in. + */ + if(strcmp(line, "." CRLF) == 0) + strncpy(line, ". " CRLF, SP_LINE_LENGTH); + if(header && !had_header) { /* * The first blank line we see means the headers are done. * At this point we add in our virus checked header. */ - if(is_blank_line(ctx->line)) + if(is_blank_line(line)) { if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, strlen(header)) == -1 || spio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) @@ -1191,7 +1275,7 @@ int sp_done_data(spctx_t* ctx, const char* header) } } - if(spio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) + if(spio_write_data_raw(ctx, &(ctx->server), line, strlen(line)) == -1) RETURN(-1); } @@ -1211,7 +1295,7 @@ int sp_done_data(spctx_t* ctx, const char* header) if(read_server_response(ctx) == -1) RETURN(-1); - if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1) RETURN(-1); cleanup: @@ -1260,11 +1344,13 @@ int sp_fail_data(spctx_t* ctx, const char* smtp_status) static int read_server_response(spctx_t* ctx) { + int r; + /* Read response line from the server */ - if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1) + if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1) return -1; - if(ctx->linelen == 0) + if(r == 0) { sp_messagex(ctx, LOG_ERR, "server disconnected unexpectedly"); @@ -1273,12 +1359,27 @@ static int read_server_response(spctx_t* ctx) return 0; } - if(LINE_TOO_LONG(ctx)) + if(LINE_TOO_LONG(r)) sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); return 0; } +void sp_setup_env(spctx_t* ctx) +{ + if(ctx->sender) + setenv("SENDER", ctx->sender, 1); + + if(ctx->recipients) + setenv("RECIPIENTS", ctx->recipients, 1); + + if(ctx->cachename[0]) + setenv("EMAIL", ctx->cachename, 1); + + setenv("TMP", g_state.directory, 1); +} + + /* ---------------------------------------------------------------------------------- * LOGGING */ |