From ff787d95f48993deb8108a859462c535f60ddd6b Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 27 Aug 2004 23:08:18 +0000 Subject: - Changed to buffered IO --- src/Makefile.am | 2 +- src/clamsmtpd.c | 397 ++++++++++---------------------------------------------- src/clamsmtpd.h | 37 +++++- src/clio.c | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.c | 58 --------- 5 files changed, 474 insertions(+), 387 deletions(-) create mode 100644 src/clio.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index f312ff9..528c347 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,7 @@ sbin_PROGRAMS = clamsmtpd clamsmtpd_SOURCES = clamsmtpd.c clamsmtpd.h util.c util.h sock_any.h sock_any.c \ - compat.c compat.h usuals.h + compat.c compat.h usuals.h clio.c clamsmtpd_CFLAGS = -Wall diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index 01a986b..be9c4ce 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -70,9 +70,6 @@ typedef struct clamsmtp_thread } clamsmtp_thread_t; -#define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (LINE_LENGTH - 2)) -#define RETURN(x) { ret = x; goto cleanup; } - /* ----------------------------------------------------------------------- * STRINGS */ @@ -113,7 +110,7 @@ clamsmtp_thread_t; #define STARTTLS_CMD "STARTTLS" #define BDAT_CMD "BDAT" -#define DATA_END_SIG CRLF "." CRLF +#define DATA_END_SIG "." CRLF #define DATA_RSP "354" #define OK_RSP "250" @@ -189,11 +186,7 @@ static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname); static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename); static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline); static int read_server_response(clamsmtp_context_t* ctx); -static int connect_socket(clamsmtp_context_t* ctx, struct sockaddr_any* sany, const char* addrname); static void read_junk(clamsmtp_context_t* ctx, int fd); -static int read_line(clamsmtp_context_t* ctx, int* fd, int trim); -static int write_data(clamsmtp_context_t* ctx, int* fd, unsigned char* buf); -static int write_data_raw(clamsmtp_context_t* ctx, int* fd, unsigned char* buf, int len); /* ---------------------------------------------------------------------------------- @@ -529,9 +522,10 @@ static void connection_loop(int sock) { messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_maxthreads); - write_data(NULL, &fd, SMTP_STARTBUSY); + write(fd, SMTP_STARTBUSY, KL(SMTP_STARTBUSY)); shutdown(fd, SHUT_RDWR); close(fd); + fd = -1; } } @@ -581,25 +575,26 @@ static void* thread_main(void* arg) memset(&ctx, 0, sizeof(ctx)); - ctx.server = -1; - ctx.clam = -1; + clio_init(&(ctx.server), "SERVER"); + clio_init(&(ctx.client), "CLIENT"); + clio_init(&(ctx.clam), "CLAM "); plock(); /* Assign a unique id to the connection */ ctx.id = g_unique_id++; /* Get the client socket */ - ctx.client = thread->fd; + ctx.client.fd = thread->fd; punlock(); - ASSERT(ctx.client != -1); - messagex(&ctx, LOG_DEBUG, "processing %d on thread %x", ctx.client, (int)pthread_self()); + ASSERT(ctx.client.fd != -1); + messagex(&ctx, LOG_DEBUG, "processing %d on thread %x", ctx.client.fd, (int)pthread_self()); memset(&addr, 0, sizeof(addr)); SANY_LEN(addr) = sizeof(addr); /* Get the peer name */ - if(getpeername(ctx.client, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1 || + if(getpeername(ctx.client.fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1 || sock_any_ntop(&addr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1) message(&ctx, LOG_WARNING, "couldn't get peer address"); else @@ -626,7 +621,7 @@ static void* thread_main(void* arg) /* Connect to the server */ - if((ctx.server = connect_socket(&ctx, outaddr, outname)) == -1) + if(clio_connect(&ctx, &(ctx.server), outaddr, outname) == -1) RETURN(-1); /* ... and to the AV daemon */ @@ -643,22 +638,11 @@ cleanup: disconnect_clam(&ctx); /* Let the client know about fatal errors */ - if(!processing && ret == -1 && ctx.client != -1) - write_data(&ctx, &(ctx.client), SMTP_STARTFAILED); + if(!processing && ret == -1 && clio_valid(&(ctx.client))) + clio_write_data(&ctx, &(ctx.client), SMTP_STARTFAILED); - if(ctx.client != -1) - { - shutdown(ctx.client, SHUT_RDWR); - close(ctx.client); - messagex(&ctx, LOG_NOTICE, "closed client connection"); - } - - if(ctx.server != -1) - { - shutdown(ctx.server, SHUT_RDWR); - close(ctx.server); - messagex(&ctx, LOG_DEBUG, "closed server connection"); - } + clio_disconnect(&ctx, &(ctx.client)); + clio_disconnect(&ctx, &(ctx.server)); /* mark this as done */ plock(); @@ -675,41 +659,31 @@ cleanup: static int smtp_passthru(clamsmtp_context_t* ctx) { + clio_t* io = NULL; char logline[LINE_LENGTH]; int r, ret = 0; - fd_set 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(ctx->clam != -1 && ctx->server != -1); + ASSERT(clio_valid(&(ctx->clam)) && + clio_valid(&(ctx->clam))); logline[0] = 0; for(;;) { - FD_ZERO(&mask); - - FD_SET(ctx->client, &mask); - FD_SET(ctx->server, &mask); - - switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_timeout)) - { - case 0: - messagex(ctx, LOG_ERR, "network operation timed out"); - neterror = 1; - RETURN(-1); - case -1: - message(ctx, LOG_ERR, "couldn't select on sockets"); + if(clio_select(ctx, &io) == -1) + { neterror = 1; RETURN(-1); - }; + } /* Client has data available, read a line and process */ - if(FD_ISSET(ctx->client, &mask)) + if(io == &(ctx->client)) { - if(read_line(ctx, &(ctx->client), 0) == -1) + if(clio_read_line(ctx, &(ctx->client), CLIO_DISCARD) == -1) RETURN(-1); /* Client disconnected, we're done */ @@ -719,7 +693,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) /* We don't let clients send really long lines */ if(LINE_TOO_LONG(ctx)) { - if(write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) RETURN(-1); continue; @@ -733,7 +707,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) if(is_first_word(ctx->line, DATA_CMD, KL(DATA_CMD))) { /* Send back the intermediate response to the client */ - if(write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) RETURN(-1); /* @@ -790,7 +764,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) { messagex(ctx, LOG_DEBUG, "ESMTP feature not supported"); - if(write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1) RETURN(-1); /* Command handled */ @@ -810,16 +784,16 @@ static int smtp_passthru(clamsmtp_context_t* ctx) logline[0] = 0; /* All other commands just get passed through to server */ - if(write_data(ctx, &(ctx->server), ctx->line) == -1) + if(clio_write_data(ctx, &(ctx->server), ctx->line) == -1) RETURN(-1); continue; } /* Server has data available, read a line and forward */ - if(FD_ISSET(ctx->server, &mask)) + if(io == &(ctx->server)) { - if(read_line(ctx, &(ctx->server), 0) == -1) + if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) RETURN(-1); if(ctx->linelen == 0) @@ -848,7 +822,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) { messagex(ctx, LOG_DEBUG, "intercepting initial response"); - if(write_data(ctx, &(ctx->client), SMTP_BANNER) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1) RETURN(-1); /* Command handled */ @@ -870,7 +844,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) { messagex(ctx, LOG_DEBUG, "intercepting host response"); - if(write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) RETURN(-1); continue; @@ -881,7 +855,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) { messagex(ctx, LOG_DEBUG, "intercepting host response"); - if(write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) + if(clio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) RETURN(-1); continue; @@ -909,7 +883,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) } } - if(write_data(ctx, &(ctx->client), ctx->line) == -1) + if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) RETURN(-1); continue; @@ -918,8 +892,8 @@ static int smtp_passthru(clamsmtp_context_t* ctx) cleanup: - if(!neterror && ret == -1 && ctx->client != -1) - write_data(ctx, &(ctx->client), SMTP_FAILED); + if(!neterror && ret == -1 && clio_valid(&(ctx->client))) + clio_write_data(ctx, &(ctx->client), SMTP_FAILED); return ret; } @@ -967,7 +941,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN); /* transfer_to_file deletes the temp file on failure */ - if((r = transfer_to_file(ctx, buf)) > 0) + if((r = transfer_to_file(ctx, buf)) != -1) { havefile = 1; r = clam_scan_file(ctx, buf, logline); @@ -981,7 +955,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) * the server about any of this yet */ case -1: - if(write_data(ctx, &(ctx->client), SMTP_FAILED)) + if(clio_write_data(ctx, &(ctx->client), SMTP_FAILED)) RETURN(-1); break; @@ -1001,8 +975,8 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) * choose to reset the connection to reuse it if it wants. */ case 1: - if(write_data(ctx, &(ctx->client), - g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) + if(clio_write_data(ctx, &(ctx->client), + g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) RETURN(-1); /* Any special post operation actions on the virus */ @@ -1030,7 +1004,7 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) ASSERT(tempname); /* Ask the server for permission to send data */ - if(write_data(ctx, &(ctx->server), SMTP_DATA) == -1) + if(clio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1) return -1; if(read_server_response(ctx) == -1) @@ -1039,7 +1013,7 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) /* If server returns an error then tell the client */ if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP))) { - if(write_data(ctx, &(ctx->client), ctx->line) == -1) + if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) return -1; messagex(ctx, LOG_DEBUG, "server refused data transfer"); @@ -1051,7 +1025,7 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) if(transfer_from_file(ctx, tempname) == -1) { /* Tell the client it went wrong */ - write_data(ctx, &(ctx->client), SMTP_FAILED); + clio_write_data(ctx, &(ctx->client), SMTP_FAILED); return -1; } @@ -1059,7 +1033,7 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) if(read_server_response(ctx) == -1) return -1; - if(write_data(ctx, &(ctx->client), ctx->line) == -1) + if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) return -1; return 0; @@ -1068,7 +1042,7 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) static int read_server_response(clamsmtp_context_t* ctx) { /* Read response line from the server */ - if(read_line(ctx, &(ctx->server), 0) == -1) + if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) return -1; if(ctx->linelen == 0) @@ -1076,7 +1050,7 @@ static int read_server_response(clamsmtp_context_t* ctx) messagex(ctx, LOG_ERR, "server disconnected unexpectedly"); /* Tell the client it went wrong */ - write_data(ctx, &(ctx->client), SMTP_FAILED); + clio_write_data(ctx, &(ctx->client), SMTP_FAILED); return 0; } @@ -1096,22 +1070,22 @@ static int connect_clam(clamsmtp_context_t* ctx) int ret = 0; ASSERT(ctx); - ASSERT(ctx->clam == -1); + ASSERT(!clio_valid(&(ctx->clam))); - if((ctx->clam = connect_socket(ctx, &g_clamaddr, g_clamname)) == -1) + if(clio_connect(ctx, &(ctx->clam), &g_clamaddr, g_clamname) == -1) RETURN(-1); - read_junk(ctx, ctx->clam); + read_junk(ctx, ctx->clam.fd); /* Send a session and a check header to ClamAV */ - if(write_data(ctx, &(ctx->clam), "SESSION\n") == -1) + if(clio_write_data(ctx, &(ctx->clam), "SESSION\n") == -1) RETURN(-1); - read_junk(ctx, ctx->clam); + read_junk(ctx, ctx->clam.fd); /* - if(write_data(ctx, &(ctx->clam), "PING\n") == -1 || - read_line(ctx, &(ctx->clam), 1) == -1) + if(clio_write_data(ctx, &(ctx->clam), "PING\n") == -1 || + clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1) RETURN(-1); if(strcmp(ctx->line, CONNECT_RESPONSE) != 0) @@ -1120,35 +1094,24 @@ static int connect_clam(clamsmtp_context_t* ctx) RETURN(-1); } */ - messagex(ctx, LOG_DEBUG, "connected to clamd: %s", g_clamname); cleanup: if(ret < 0) - { - if(ctx->clam != -1) - { - close(ctx->clam); - ctx->clam = -1; - } - } + clio_disconnect(ctx, &(ctx->clam)); return ret; } static int disconnect_clam(clamsmtp_context_t* ctx) { - if(ctx->clam == -1) + if(!clio_valid(&(ctx->clam))) return 0; - if(write_data(ctx, &(ctx->clam), CLAM_DISCONNECT) != -1) - read_junk(ctx, ctx->clam); - - shutdown(ctx->clam, SHUT_RDWR); - close(ctx->clam); - ctx->clam = -1; + if(clio_write_data(ctx, &(ctx->clam), CLAM_DISCONNECT) != -1) + read_junk(ctx, ctx->clam.fd); - messagex(ctx, LOG_DEBUG, "disconnected from clamd"); + clio_disconnect(ctx, &(ctx->clam)); return 0; } @@ -1162,10 +1125,10 @@ static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* l strcat(ctx->line, tempname); strcat(ctx->line, "\n"); - if(write_data(ctx, &(ctx->clam), ctx->line) == -1) + if(clio_write_data(ctx, &(ctx->clam), ctx->line) == -1) return -1; - len = read_line(ctx, &(ctx->clam), 1); + len = clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM); if(len == 0) { messagex(ctx, LOG_ERR, "clamd disconnected unexpectedly"); @@ -1262,18 +1225,12 @@ static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) { - /* If there aren't any lines in the message and just an - end signature then start at the dot. */ - const char* topsig = strchr(DATA_END_SIG, '.'); - const char* cursig = topsig; FILE* tfile = NULL; int tfd = -1; + int ended_crlf = 1; /* If the last line ended with a CRLF */ int ret = 0; - char ch; int count = 0; - ASSERT(topsig != NULL); - if((tfd = mkstemp(tempname)) == -1 || (tfile = fdopen(tfd, "w")) == NULL) { @@ -1285,48 +1242,25 @@ static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) for(;;) { - switch(read(ctx->client, &ch, 1)) + switch(clio_read_line(ctx, &(ctx->client), CLIO_QUIET)) { case 0: messagex(ctx, LOG_ERR, "unexpected end of data from client"); RETURN(-1); case -1: - message(ctx, LOG_ERR, "error reading from client"); + /* Message already printed */ RETURN(-1); }; - if((char)ch != *cursig) - { - /* Write out the part of the sig we kept back */ - if(cursig != topsig) - { - /* We check errors on this later */ - fwrite(topsig, 1, cursig - topsig, tfile); - count += (cursig - topsig); - } - - /* We've seen at least one char not in the sig */ - cursig = topsig = DATA_END_SIG; - } + if(ended_crlf && strcmp(ctx->line, DATA_END_SIG) == 0) + break; - /* The sig may have been reset above so check again */ - if((char)ch == *cursig) - { - cursig++; + /* We check errors on this later */ + fwrite(ctx->line, 1, ctx->linelen, tfile); - if(!*cursig) - { - /* We found end of data */ - break; - } - } - - else - { - fputc(ch, tfile); - count++; - } + /* Check if this line ended with a CRLF */ + ended_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0); } if(ferror(tfile)) @@ -1384,15 +1318,15 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) */ if(is_blank_line(ctx->line)) { - if(write_data_raw(ctx, &(ctx->server), (char*)g_header, strlen(g_header)) == -1 || - write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) + if(clio_write_data_raw(ctx, &(ctx->server), (char*)g_header, strlen(g_header)) == -1 || + clio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) RETURN(-1); header = 1; } } - if(write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) + if(clio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) RETURN(-1); } @@ -1402,7 +1336,7 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) RETURN(-1); } - if(write_data(ctx, &(ctx->server), DATA_END_SIG) == -1) + if(clio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1) RETURN(-1); messagex(ctx, LOG_DEBUG, "sent email data"); @@ -1420,36 +1354,6 @@ cleanup: * NETWORKING */ -static int connect_socket(clamsmtp_context_t* ctx, struct sockaddr_any* sany, const char* addrname) -{ - int sock = -1; - int ret = 0; - - if((sock = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) - RETURN(-1); - - if(setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) == -1 || - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) == -1) - messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection"); - - if(connect(sock, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1) - RETURN(-1); - -cleanup: - if(ret < 0) - { - if(sock != -1) - close(sock); - - message(ctx, LOG_ERR, "couldn't connect to: %s", addrname); - return -1; - } - - ASSERT(sock != -1); - messagex(ctx, LOG_DEBUG, "connected to: %s", addrname); - return sock; -} - static void read_junk(clamsmtp_context_t* ctx, int fd) { char buf[16]; @@ -1481,160 +1385,3 @@ static void read_junk(clamsmtp_context_t* ctx, int fd) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); } - -static int read_line(clamsmtp_context_t* ctx, int* fd, int trim) -{ - int l; - char* t; - const char* e; - - if(*fd == -1) - { - messagex(ctx, LOG_WARNING, "tried to read from a closed connection"); - return 0; - } - - ctx->line[0] = 0; - e = ctx->line + (LINE_LENGTH - 1); - - for(t = ctx->line; t < e; ++t) - { - l = read(*fd, (void*)t, sizeof(char)); - - /* We got a character */ - if(l == 1) - { - /* End of line */ - if(*t == '\n') - { - ++t; - break; - } - - /* We skip spaces at the beginning if trimming */ - if(trim && t == ctx->line && isspace(*t)) - continue; - } - - /* If it's the end of file then return that */ - else if(l == 0) - { - /* Put in an extra line if there was anything */ - if(t > ctx->line && !trim) - { - *t = '\n'; - ++t; - } - - break; - } - - else if(l == -1) - { - if(errno == EINTR) - { - /* When the application is quiting */ - if(g_quit) - return -1; - - /* For any other signal we go again */ - continue; - } - - /* - * The basic logic here is that if we've had a fatal error - * reading from the socket once then we shut it down as it's - * no good trying to read from again later. - */ - shutdown(*fd, SHUT_RDWR); - close(*fd); - *fd = -1; - - if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network read operation timed out"); - else - message(ctx, LOG_ERR, "couldn't read data from socket"); - - return -1; - } - } - - *t = 0; - - if(trim) - { - while(t > ctx->line && isspace(*(t - 1))) - { - --t; - *t = 0; - } - } - - ctx->linelen = t - ctx->line; - log_fd_data(ctx, ctx->line, fd, 1); - - return ctx->linelen; -} - -static int write_data_raw(clamsmtp_context_t* ctx, int* fd, unsigned char* buf, int len) -{ - int r; - - while(len > 0) - { - r = write(*fd, buf, len); - - if(r > 0) - { - buf += r; - len -= r; - } - - else if(r == -1) - { - if(errno == EINTR) - { - /* When the application is quiting */ - if(g_quit) - return -1; - - /* For any other signal we go again */ - continue; - } - - /* - * The basic logic here is that if we've had a fatal error - * writing to the socket once then we shut it down as it's - * no good trying to write to it again later. - */ - shutdown(*fd, SHUT_RDWR); - close(*fd); - *fd = -1; - - if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network write operation timed out"); - else - message(ctx, LOG_ERR, "couldn't write data to socket"); - - return -1; - } - } - - return 0; -} - -static int write_data(clamsmtp_context_t* ctx, int* fd, unsigned char* buf) -{ - int len = strlen(buf); - - if(*fd == -1) - { - message(ctx, LOG_ERR, "connection closed. can't write data."); - return -1; - } - - if(ctx != NULL) - log_fd_data(ctx, buf, fd, 0); - - return write_data_raw(ctx, fd, buf, len); -} diff --git a/src/clamsmtpd.h b/src/clamsmtpd.h index 4c581d2..ca3df37 100644 --- a/src/clamsmtpd.h +++ b/src/clamsmtpd.h @@ -39,6 +39,17 @@ #ifndef __CLAMSMTPD_H__ #define __CLAMSMTPD_H__ +#define BUF_LEN 256 + +typedef struct clio +{ + int fd; + const char* name; + unsigned char buf[BUF_LEN]; + size_t buflen; +} +clio_t; + /* * A generous maximum line length. It needs to be longer than * a full path on this system can be, because we pass the file @@ -55,9 +66,9 @@ typedef struct clamsmtp_context { unsigned int id; /* Identifier for the connection */ - int client; /* Connection to client */ - int server; /* Connection to server */ - int clam; /* Connection to clamd */ + clio_t client; /* Connection to client */ + clio_t server; /* Connection to server */ + clio_t clam; /* Connection to clamd */ char line[LINE_LENGTH]; /* Working buffer */ int linelen; /* Length of valid data in above */ @@ -67,5 +78,25 @@ clamsmtp_context_t; extern int g_daemonized; /* Currently running as a daemon */ extern int g_debuglevel; /* what gets logged to console */ extern pthread_mutex_t g_mutex; /* The main mutex */ +extern struct timeval g_timeout; +extern int g_quit; + +struct sockaddr_any; +#define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (LINE_LENGTH - 2)) +#define RETURN(x) { ret = x; goto cleanup; } + + +#define CLIO_TRIM 0x00000001 +#define CLIO_DISCARD 0x00000002 +#define CLIO_QUIET 0x00000004 +#define clio_valid(io) ((io)->fd != -1) + +void clio_init(clio_t* io, const char* name); +int clio_connect(clamsmtp_context_t* ctx, clio_t* io, struct sockaddr_any* sany, const char* addrname); +void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io); +int clio_select(clamsmtp_context_t* ctx, clio_t** io); +int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int trim); +int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data); +int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len); #endif /* __CLAMSMTPD_H__ */ diff --git a/src/clio.c b/src/clio.c new file mode 100644 index 0000000..f2d53cd --- /dev/null +++ b/src/clio.c @@ -0,0 +1,367 @@ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "usuals.h" +#include "sock_any.h" +#include "clamsmtpd.h" +#include "util.h" + +#define MAX_LOG_LINE 79 +#define IO_UNKNOWN "??? " + +static void close_raw(int* fd) +{ + ASSERT(fd); + shutdown(*fd, SHUT_RDWR); + close(*fd); + *fd = -1; +} + +static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, int read) +{ + char buf[MAX_LOG_LINE + 1]; + int pos, len; + + ASSERT(ctx && io && data); + + for(;;) + { + data += strspn(data, "\r\n"); + + if(!*data) + break; + + pos = strcspn(data, "\r\n"); + + len = pos < MAX_LOG_LINE ? pos : MAX_LOG_LINE; + memcpy(buf, data, len); + buf[len] = 0; + + messagex(ctx, LOG_DEBUG, "%s%s%s", + io->name ? io->name : IO_UNKNOWN, + read ? " < " : " > ", buf); + + data += pos; + } +} + +void clio_init(clio_t* io, const char* name) +{ + ASSERT(io && name); + memset(io, 0, sizeof(*io)); + io->name = name; + io->fd = -1; +} + +int clio_connect(clamsmtp_context_t* ctx, clio_t* io, struct sockaddr_any* sany, + const char* addrname) +{ + int ret = 0; + + ASSERT(ctx && io && sany && addrname); + ASSERT(io->fd == -1); + + if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) + RETURN(-1); + + if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) == -1 || + setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) == -1) + messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection"); + + if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1) + RETURN(-1); + +cleanup: + if(ret < 0) + { + if(io->fd != -1) + close(io->fd); + + message(ctx, LOG_ERR, "couldn't connect to: %s", addrname); + return -1; + } + + ASSERT(io->fd != -1); + messagex(ctx, LOG_DEBUG, "%s connected to: %s", + io->name ? io->name : IO_UNKNOWN, addrname); + return 0; +} + +void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io) +{ + ASSERT(ctx && io); + + if(clio_valid(io)) + { + close_raw(&(io->fd)); + messagex(ctx, LOG_NOTICE, "%s connection closed", + io->name ? io->name : IO_UNKNOWN); + } +} + +int clio_select(clamsmtp_context_t* ctx, clio_t** io) +{ + fd_set mask; + + ASSERT(ctx && io); + FD_ZERO(&mask); + *io = NULL; + + /* First check if buffers have any data */ + + if(clio_valid(&(ctx->server))) + { + if(ctx->server.buflen > 0) + { + *io = &(ctx->server); + return 0; + } + + FD_SET(ctx->server.fd, &mask); + } + + if(clio_valid(&(ctx->client))) + { + if(ctx->client.buflen > 0) + { + *io = &(ctx->client); + return 0; + } + + FD_SET(ctx->client.fd, &mask); + } + + /* Select on the above */ + + switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_timeout)) + { + case 0: + messagex(ctx, LOG_ERR, "network operation timed out"); + return -1; + case -1: + message(ctx, LOG_ERR, "couldn't select on sockets"); + return -1; + }; + + /* See what came in */ + + if(FD_ISSET(ctx->server.fd, &mask)) + { + *io = &(ctx->server); + return 0; + } + + if(FD_ISSET(ctx->client.fd, &mask)) + { + *io = &(ctx->client); + return 0; + } + + ASSERT(0 && "invalid result from select"); + return -1; +} + +int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) +{ + int l, x; + int bufused; + char* t; + unsigned char* p; + + ASSERT(ctx && io); + + if(!clio_valid(io)) + { + messagex(ctx, LOG_WARNING, "tried to read from a closed connection"); + return 0; + } + + ctx->line[0] = 0; + t = ctx->line; + l = LINE_LENGTH - 1; + + for(;;) + { + /* refil buffer if necessary */ + if(io->buflen == 0) + { + ASSERT(io->fd != -1); + io->buflen = read(io->fd, io->buf, sizeof(char) * BUF_LEN); + + if(io->buflen == -1) + { + io->buflen = 0; + + if(errno == EINTR) + { + /* When the application is quiting */ + if(g_quit) + return -1; + + /* For any other signal we go again */ + continue; + } + + /* + * The basic logic here is that if we've had a fatal error + * reading from the socket once then we shut it down as it's + * no good trying to read from again later. + */ + close_raw(&(io->fd)); + + if(errno == EAGAIN) + messagex(ctx, LOG_WARNING, "network read operation timed out"); + else + message(ctx, LOG_ERR, "couldn't read data from socket"); + + return -1; + } + } + + /* End of data */ + if(io->buflen == 0) + break; + + /* Check for a new line */ + p = (unsigned char*)memchr(io->buf, '\n', io->buflen); + + if(p != NULL) + { + x = (p - io->buf) + 1; + io->buflen -= x; + } + + else + { + x = io->buflen; + io->buflen = 0; + } + + if(x > l) + x = l; + + /* Copy from buffer line */ + memcpy(t, io->buf, x); + t += x; + l -= x; + + /* Move whatever we have in the buffer to the front */ + if(io->buflen > 0) + memmove(io->buf, io->buf + x, io->buflen); + + /* Found a new line, done */ + if(p != NULL) + break; + + /* If discarding then don't break when full */ + if(!(opts && CLIO_DISCARD) && l == 0) + break; + } + + ctx->linelen = (LINE_LENGTH - l) - 1; + ASSERT(ctx->linelen < LINE_LENGTH); + ctx->line[ctx->linelen] = 0; + + if(opts & CLIO_TRIM && ctx->linelen > 0) + { + t = ctx->line; + + while(*t && isspace(*t)) + t++; + + /* Bump the entire line down */ + l = t - ctx->line; + memmove(ctx->line, t, (ctx->linelen + 1) - l); + ctx->linelen -= l; + + /* Now the end */ + t = ctx->line + ctx->linelen; + + while(t > ctx->line && isspace(*(t - 1))) + { + *(--t) = 0; + ctx->linelen--; + } + } + + if(!(opts & CLIO_QUIET)) + log_io_data(ctx, io, ctx->line, 1); + + return ctx->linelen; +} + +int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data) +{ + int len = strlen(data); + ASSERT(ctx && io && data); + + if(!clio_valid(io)) + { + message(ctx, LOG_ERR, "connection closed. can't write data."); + return -1; + } + + log_io_data(ctx, io, data, 0); + return clio_write_data_raw(ctx, io, (unsigned char*)data, len); +} + +int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len) +{ + int r; + + ASSERT(ctx && io && buf); + + if(io->fd == -1) + return 0; + + while(len > 0) + { + r = write(io->fd, buf, len); + + if(r > 0) + { + buf += r; + len -= r; + } + + else if(r == -1) + { + if(errno == EINTR) + { + /* When the application is quiting */ + if(g_quit) + return -1; + + /* For any other signal we go again */ + continue; + } + + /* + * The basic logic here is that if we've had a fatal error + * writing to the socket once then we shut it down as it's + * no good trying to write to it again later. + */ + close_raw(&(io->fd)); + + if(errno == EAGAIN) + messagex(ctx, LOG_WARNING, "network write operation timed out"); + else + message(ctx, LOG_ERR, "couldn't write data to socket"); + + return -1; + } + } + + return 0; +} diff --git a/src/util.c b/src/util.c index b994f7f..8b6a816 100644 --- a/src/util.c +++ b/src/util.c @@ -126,64 +126,6 @@ void message(clamsmtp_context_t* ctx, int level, const char* msg, ...) va_end(ap); } -#define MAX_LOG_LINE 79 - -void log_fd_data(clamsmtp_context_t* ctx, const char* data, int* fd, int read) -{ - #define offsetof(s, m) ((size_t)&(((s*)0)->m)) - #define ismember(o, m) (((char*)(m)) < (((char*)(o)) + sizeof(*(o)))) - #define ptrdiff(o, t) - - char prefix[16]; - - ASSERT(ctx); - ASSERT(ismember(ctx, fd)); - - switch((char*)fd - (char*)ctx) - { - case offsetof(clamsmtp_context_t, client): - strcpy(prefix, "CLIENT "); - break; - case offsetof(clamsmtp_context_t, server): - strcpy(prefix, "SERVER "); - break; - case offsetof(clamsmtp_context_t, clam): - strcpy(prefix, "CLAM "); - break; - default: - strcpy(prefix, "???? "); - break; - } - - strcat(prefix, read ? "< " : "> "); - log_data(ctx, data, prefix); -} - - -void log_data(clamsmtp_context_t* ctx, const char* data, const char* prefix) -{ - char buf[MAX_LOG_LINE + 1]; - int pos, len; - - for(;;) - { - data += strspn(data, "\r\n"); - - if(!*data) - break; - - pos = strcspn(data, "\r\n"); - - len = pos < MAX_LOG_LINE ? pos : MAX_LOG_LINE; - memcpy(buf, data, len); - buf[len] = 0; - - messagex(ctx, LOG_DEBUG, "%s%s", prefix, buf); - - data += pos; - } -} - /* ---------------------------------------------------------------------------------- * Parsing */ -- cgit v1.2.3