summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/clamsmtpd.c397
-rw-r--r--src/clamsmtpd.h37
-rw-r--r--src/clio.c367
-rw-r--r--src/util.c58
5 files changed, 474 insertions, 387 deletions
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 <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+
+#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
*/