summaryrefslogtreecommitdiff
path: root/src/clamsmtpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/clamsmtpd.c')
-rw-r--r--src/clamsmtpd.c1312
1 files changed, 229 insertions, 1083 deletions
diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c
index 4c08919..9f35165 100644
--- a/src/clamsmtpd.c
+++ b/src/clamsmtpd.c
@@ -33,45 +33,51 @@
*
* CONTRIBUTORS
* Nate Nielsen <nielsen@memberwebs.com>
- * Andreas Steinmetz <ast@domdv.de>
*/
-#include <sys/time.h>
#include <sys/types.h>
-#include <sys/socket.h>
#include <sys/param.h>
-#include <sys/stat.h>
+#include <paths.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
-#include <fcntl.h>
#include <syslog.h>
-#include <signal.h>
#include <errno.h>
#include <err.h>
#include "usuals.h"
-#ifdef LINUX_TRANSPARENT_PROXY
-#include <linux/netfilter_ipv4.h>
-#endif
-
#include "compat.h"
#include "sock_any.h"
-#include "clamsmtpd.h"
-#include "util.h"
+#include "stringx.h"
+
+#define SP_LEGACY_OPTIONS
+#include "smtppass.h"
/* -----------------------------------------------------------------------
* STRUCTURES
*/
-typedef struct clamsmtp_thread
+typedef struct clstate
{
- pthread_t tid; /* Written to by the main thread */
- int fd; /* The file descriptor or -1 */
+ /* Settings ------------------------------- */
+ struct sockaddr_any clamaddr; /* Address for connecting to clamd */
+ const char* clamname;
+ const char* header; /* The header to add to email */
+ const char* directory; /* The directory for temp files */
+ int bounce; /* Send back a reject line */
+ int quarantine; /* Leave virus files in temp dir */
+ int debug_files; /* Leave all files in temp dir */
}
-clamsmtp_thread_t;
+clstate_t;
+
+typedef struct clctx
+{
+ spctx_t sp; /* The main sp context */
+ spio_t clam; /* Connection to clamd */
+}
+clctx_t;
/* -----------------------------------------------------------------------
* STRINGS
@@ -79,43 +85,8 @@ clamsmtp_thread_t;
#define CRLF "\r\n"
-#define SMTP_TOOLONG "500 Line too long" CRLF
-#define SMTP_STARTBUSY "554 Server Busy" CRLF
-#define SMTP_STARTFAILED "554 Local Error" CRLF
-#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF
-#define SMTP_DATAINTERMED "354 Start mail input; end with <CRLF>.<CRLF>" CRLF
-#define SMTP_FAILED "451 Local Error" CRLF
-#define SMTP_NOTSUPP "502 Command not implemented" CRLF
#define SMTP_DATAVIRUSOK "250 Virus Detected; Discarded Email" CRLF
-#define SMTP_OK "250 Ok" CRLF
-
-#define SMTP_DATA "DATA" CRLF
-#define SMTP_BANNER "220 clamsmtp" CRLF
-#define SMTP_HELO_RSP "250 clamsmtp" CRLF
-#define SMTP_EHLO_RSP "250-clamsmtp" CRLF
-#define SMTP_DELIMS "\r\n\t :"
-#define SMTP_MULTI_DELIMS " -"
-
-#define ESMTP_PIPELINE "PIPELINING"
-#define ESMTP_TLS "STARTTLS"
-#define ESMTP_CHUNK "CHUNKING"
-#define ESMTP_BINARY "BINARYMIME"
-#define ESMTP_CHECK "CHECKPOINT"
-
-#define HELO_CMD "HELO"
-#define EHLO_CMD "EHLO"
-#define FROM_CMD "MAIL FROM"
-#define TO_CMD "RCPT TO"
-#define DATA_CMD "DATA"
-#define RSET_CMD "RSET"
-#define STARTTLS_CMD "STARTTLS"
-#define BDAT_CMD "BDAT"
-
-#define DATA_END_SIG "." CRLF
-
-#define DATA_RSP "354"
-#define OK_RSP "250"
-#define START_RSP "220"
+#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF
#define CLAM_OK "OK"
#define CLAM_ERROR "ERROR"
@@ -129,37 +100,35 @@ clamsmtp_thread_t;
#define DEFAULT_CONFIG CONF_PREFIX "/clamsmtpd.conf"
+#define CFG_CLAMADDR "ClamAddress"
+#define CFG_HEADER "ScanHeader"
+#define CFG_DIRECTORY "TempDirectory"
+#define CFG_BOUNCE "Bounce"
+#define CFG_QUARANTINE "Quarantine"
+#define CFG_DEBUGFILES "DebugFiles"
+
+#define DEFAULT_CLAMAV "/var/run/clamav/clamd"
+#define DEFAULT_HEADER "X-Virus-Scanned: ClamAV using ClamSMTP"
+
/* -----------------------------------------------------------------------
* GLOBALS
*/
-const clstate_t* g_state = NULL; /* The state and configuration of the daemon */
-unsigned int g_unique_id = 0x00100000; /* For connection ids */
-
+clstate_t g_clstate;
/* -----------------------------------------------------------------------
* FORWARD DECLARATIONS
*/
static void usage();
-static void on_quit(int signal);
-static void pid_file(const char* pidfile, int write);
-static void connection_loop(int sock);
-static void* thread_main(void* arg);
-static int smtp_passthru(clamsmtp_context_t* ctx);
-static int connect_out(clamsmtp_context_t* ctx);
-static int connect_clam(clamsmtp_context_t* ctx);
-static int disconnect_clam(clamsmtp_context_t* ctx);
-static void add_to_logline(char* logline, char* prefix, char* line);
-static int avcheck_data(clamsmtp_context_t* ctx, char* logline);
-static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname);
-static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname);
-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 void read_junk(clamsmtp_context_t* ctx, int fd);
+static int connect_clam(clctx_t* ctx);
+static int disconnect_clam(clctx_t* ctx);
+static int quarantine_virus(clctx_t* ctx);
+static int clam_scan_file(clctx_t* ctx);
+/* -----------------------------------------------------------------------
+ * SIMPLE MACROS
+ */
/* ----------------------------------------------------------------------------------
* STARTUP ETC...
@@ -169,44 +138,62 @@ int main(int argc, char* argv[])
{
const char* configfile = DEFAULT_CONFIG;
const char* pidfile = NULL;
- clstate_t state;
+ int dbg_level = -1;
int warnargs = 0;
- int sock;
- int true = 1;
int ch = 0;
+ int r;
char* t;
- clstate_init(&state);
- g_state = &state;
+ /* Setup some defaults */
+ memset(&g_clstate, 0, sizeof(g_clstate));
+ g_clstate.header = DEFAULT_HEADER;
+ g_clstate.directory = _PATH_TMP;
+
+ /* We need the default to parse into a useable form, so we do this: */
+ r = cb_parse_option(CFG_CLAMADDR, DEFAULT_CLAMAV);
+ ASSERT(r == 1);
+
+ sp_init("clamsmtpd");
+
+ /*
+ * We still accept our old arguments for compatibility reasons.
+ * We fill them into the spstate structure directly
+ */
/* Parse the arguments nicely */
while((ch = getopt(argc, argv, "bc:d:D:f:h:l:m:p:qt:v")) != -1)
{
switch(ch)
{
- /* Actively reject messages */
+ /* COMPAT: Actively reject messages */
case 'b':
- state.bounce = 1;
+ if((r = cb_parse_option(CFG_BOUNCE, "on")) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
- /* Change the CLAM socket */
+ /* COMPAT: Change the CLAM socket */
case 'c':
- state.clamname = optarg;
+ if((r = cb_parse_option(CFG_CLAMADDR, "on")) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
/* Don't daemonize */
case 'd':
- state.debug_level = strtol(optarg, &t, 10);
+ dbg_level = strtol(optarg, &t, 10);
if(*t) /* parse error */
errx(1, "invalid debug log level");
- state.debug_level += LOG_ERR;
+ dbg_level += LOG_ERR;
break;
- /* The directory for the files */
+ /* COMPAT: The directory for the files */
case 'D':
- state.directory = optarg;
+ if((r = sp_parse_option(CFG_DIRECTORY, optarg)) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
@@ -215,26 +202,27 @@ int main(int argc, char* argv[])
configfile = optarg;
break;
- /* The header to add */
+ /* COMPAT: The header to add */
case 'h':
- if(strlen(optarg) == 0)
- state.header = NULL;
- else
- state.header = optarg;
+ if((r = cb_parse_option(CFG_HEADER, optarg)) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
- /* Change our listening port */
+ /* COMPAT: Change our listening port */
case 'l':
- state.listenname = optarg;
+ if((r = sp_parse_option("Listen", optarg)) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
- /* The maximum number of threads */
+ /* COMPAT: The maximum number of threads */
case 'm':
- state.max_threads = strtol(optarg, &t, 10);
- if(*t) /* parse error */
- errx(1, "invalid max threads");
+ if((r = sp_parse_option("MaxConnections", optarg)) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
@@ -243,28 +231,34 @@ int main(int argc, char* argv[])
pidfile = optarg;
break;
- /* The timeout */
+ /* COMPAT: The timeout */
case 't':
- state.timeout.tv_sec = strtol(optarg, &t, 10);
- if(*t) /* parse error */
- errx(1, "invalid timeout");
+ if((r = sp_parse_option("TimeOut", optarg)) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
- /* Leave virus files in directory */
+ /* COMPAT: Leave virus files in directory */
case 'q':
- state.quarantine = 1;
+ if((r = cb_parse_option(CFG_QUARANTINE, "on")) < 0)
+ usage();
+ ASSERT(r == 1);
+ warnargs = 1;
break;
/* Print version number */
case 'v':
printf("clamsmtpd (version %s)\n", VERSION);
+ printf(" (config: %s)\n", DEFAULT_CONFIG);
exit(0);
break;
- /* Leave all files in the tmp directory */
+ /* COMPAT: Leave all files in the tmp directory */
case 'X':
- state.debug_files = 1;
+ if((r = cb_parse_option(CFG_DEBUGFILES, "on")) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
break;
@@ -283,106 +277,21 @@ int main(int argc, char* argv[])
usage();
if(argc == 1)
{
- state.outname = argv[0];
+ /* COMPAT: The out address */
+ if((r = sp_parse_option("OutAddress", argv[0])) < 0)
+ usage();
+ ASSERT(r == 1);
warnargs = 1;
}
if(warnargs)
warnx("please use configuration file instead of command-line flags: %s", configfile);
- /* Now parse the configuration file */
- if(clstate_parse_config(&state, configfile) == -1)
- {
- /* Only error when it was forced */
- if(configfile != DEFAULT_CONFIG)
- err(1, "couldn't open config file: %s", configfile);
- else
- warnx("default configuration file not found: %s", configfile);
- }
-
- clstate_validate(&state);
-
- messagex(NULL, LOG_DEBUG, "starting up...");
-
- /* When set to this we daemonize */
- if(g_state->debug_level == -1)
- {
- /* Fork a daemon nicely here */
- if(daemon(0, 0) == -1)
- {
- message(NULL, LOG_ERR, "couldn't run as daemon");
- exit(1);
- }
-
- messagex(NULL, LOG_DEBUG, "running as a daemon");
- state.daemonized = 1;
-
- /* Open the system log */
- openlog("clamsmtpd", 0, LOG_MAIL);
- }
-
- /* Create the socket */
- sock = socket(SANY_TYPE(g_state->listenaddr), SOCK_STREAM, 0);
- if(sock < 0)
- {
- message(NULL, LOG_CRIT, "couldn't open socket");
- exit(1);
- }
-
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&true, sizeof(true));
-
- /* Unlink the socket file if it exists */
- if(SANY_TYPE(g_state->listenaddr) == AF_UNIX)
- unlink(g_state->listenname);
-
- if(bind(sock, &SANY_ADDR(g_state->listenaddr), SANY_LEN(g_state->listenaddr)) != 0)
- {
- message(NULL, LOG_CRIT, "couldn't bind to address: %s", g_state->listenname);
- exit(1);
- }
-
- /* Let 5 connections queue up */
- if(listen(sock, 5) != 0)
- {
- message(NULL, LOG_CRIT, "couldn't listen on socket");
- exit(1);
- }
-
- messagex(NULL, LOG_DEBUG, "created socket: %s", g_state->listenname);
-
- /* Handle some signals */
- signal(SIGPIPE, SIG_IGN);
- signal(SIGHUP, SIG_IGN);
- signal(SIGINT, on_quit);
- signal(SIGTERM, on_quit);
-
- siginterrupt(SIGINT, 1);
- siginterrupt(SIGTERM, 1);
-
- if(pidfile)
- pid_file(pidfile, 1);
-
- messagex(NULL, LOG_DEBUG, "accepting connections");
-
- connection_loop(sock);
-
- if(pidfile)
- pid_file(pidfile, 0);
+ r = sp_run(configfile, pidfile, dbg_level);
- messagex(NULL, LOG_DEBUG, "stopped");
+ sp_done();
- /*
- * We have to do this at the very end because even printing
- * messages requires that g_state is valid.
- */
- clstate_cleanup(&state);
- return 0;
-}
-
-static void on_quit(int signal)
-{
- ((clstate_t*)g_state)->quit = 1;
- /* fprintf(stderr, "clamsmtpd: got signal to quit\n"); */
+ return r;
}
static void usage()
@@ -392,633 +301,23 @@ static void usage()
exit(2);
}
-static void pid_file(const char* pidfile, int write)
-{
- if(write)
- {
- FILE* f = fopen(pidfile, "w");
- if(f == NULL)
- {
- message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile);
- }
- else
- {
- fprintf(f, "%d\n", (int)getpid());
-
- if(ferror(f))
- message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile);
-
- fclose(f);
- }
-
- messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile);
- }
-
- else
- {
- unlink(pidfile);
- messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile);
- }
-}
-
-
-/* ----------------------------------------------------------------------------------
- * CONNECTION HANDLING
- */
-
-static void connection_loop(int sock)
-{
- clamsmtp_thread_t* threads = NULL;
- int fd, i, x, r;
-
- /* Create the thread buffers */
- threads = (clamsmtp_thread_t*)calloc(g_state->max_threads, sizeof(clamsmtp_thread_t));
- if(!threads)
- errx(1, "out of memory");
-
- /* Now loop and accept the connections */
- while(!g_state->quit)
- {
- fd = accept(sock, NULL, NULL);
- if(fd == -1)
- {
- switch(errno)
- {
- case EINTR:
- case EAGAIN:
- break;
-
- case ECONNABORTED:
- message(NULL, LOG_ERR, "couldn't accept a connection");
- break;
-
- default:
- message(NULL, LOG_ERR, "couldn't accept a connection");
- ((clstate_t*)g_state)->quit = 1;
- break;
- };
-
- if(g_state->quit)
- break;
-
- continue;
- }
-
- /* Set timeouts on client */
- if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &(g_state->timeout), sizeof(g_state->timeout)) < 0 ||
- setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &(g_state->timeout), sizeof(g_state->timeout)) < 0)
- message(NULL, LOG_WARNING, "couldn't set timeouts on incoming connection");
-
- /* Look for thread and also clean up others */
- for(i = 0; i < g_state->max_threads; i++)
- {
- /* Find a thread to run or clean up old threads */
- if(threads[i].tid != 0)
- {
- plock();
- x = threads[i].fd;
- punlock();
-
- if(x == -1)
- {
- messagex(NULL, LOG_DEBUG, "cleaning up completed thread");
- pthread_join(threads[i].tid, NULL);
- threads[i].tid = 0;
- }
-#ifdef _DEBUG
- else
- {
- /* For debugging connection problems: */
- messagex(NULL, LOG_DEBUG, "active connection thread: %x", (int)threads[i].tid);
- }
-#endif
- }
-
- /* Start a new thread if neccessary */
- if(fd != -1 && threads[i].tid == 0)
- {
- threads[i].fd = fd;
- r = pthread_create(&(threads[i].tid), NULL, thread_main,
- (void*)(threads + i));
- if(r != 0)
- {
- threads[i].fd = -1;
- threads[i].tid = 0;
-
- errno = r;
- message(NULL, LOG_ERR, "couldn't create thread for connection");
- write(fd, SMTP_STARTFAILED, KL(SMTP_STARTFAILED));
-
- shutdown(fd, SHUT_RDWR);
- close(fd);
- fd = -1;
-
- break;
- }
-
- messagex(NULL, LOG_DEBUG, "created thread for connection");
- fd = -1;
- break;
- }
- }
-
- /* Check to make sure we have a thread */
- if(fd != -1)
- {
- messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_state->max_threads);
-
- write(fd, SMTP_STARTBUSY, KL(SMTP_STARTBUSY));
- shutdown(fd, SHUT_RDWR);
- close(fd);
- fd = -1;
- }
- }
-
- messagex(NULL, LOG_DEBUG, "waiting for threads to quit");
-
- /* Quit all threads here */
- for(i = 0; i < g_state->max_threads; i++)
- {
- /* Clean up quit threads */
- if(threads[i].tid != 0)
- {
- if(threads[i].fd != -1)
- {
- plock();
- fd = threads[i].fd;
- threads[i].fd = -1;
- punlock();
-
- shutdown(fd, SHUT_RDWR);
- close(fd);
- }
-
- pthread_join(threads[i].tid, NULL);
- }
- }
-}
-
-static void* thread_main(void* arg)
-{
- clamsmtp_thread_t* thread = (clamsmtp_thread_t*)arg;
- clamsmtp_context_t* ctx = NULL;
- int processing = 0;
- int ret = 0;
- int fd;
-
- ASSERT(thread);
-
- siginterrupt(SIGINT, 1);
- siginterrupt(SIGTERM, 1);
-
- plock();
- /* Get the client socket */
- fd = thread->fd;
- punlock();
-
- ctx = (clamsmtp_context_t*)calloc(1, sizeof(clamsmtp_context_t));
- if(!ctx)
- {
- /* Special case. We don't have a context so clean up descriptor */
- close(fd);
-
- messagex(NULL, LOG_CRIT, "out of memory");
- RETURN(-1);
- }
-
- memset(ctx, 0, sizeof(*ctx));
-
- 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++;
-
- /* We don't care about wraps, but we don't want zero */
- if(g_unique_id == 0)
- g_unique_id++;
- punlock();
-
- ctx->client.fd = fd;
- ASSERT(ctx->client.fd != -1);
- messagex(ctx, LOG_DEBUG, "processing %d on thread %x", ctx->client.fd, (int)pthread_self());
-
- /* Connect to the outgoing server ... */
- if(connect_out(ctx) == -1)
- RETURN(-1);
-
- /* ... and to the AV daemon */
- if(connect_clam(ctx) == -1)
- RETURN(-1);
-
- /* call the processor */
- processing = 1;
- ret = smtp_passthru(ctx);
-
-cleanup:
-
- if(ctx)
- {
- disconnect_clam(ctx);
-
- /* Let the client know about fatal errors */
- if(!processing && ret == -1 && clio_valid(&(ctx->client)))
- clio_write_data(ctx, &(ctx->client), SMTP_STARTFAILED);
-
- clio_disconnect(ctx, &(ctx->client));
- clio_disconnect(ctx, &(ctx->server));
- }
-
- /* mark this as done */
- plock();
- thread->fd = -1;
- punlock();
-
- return (void*)(ret == 0 ? 0 : 1);
-}
-
-static int connect_out(clamsmtp_context_t* ctx)
-{
- struct sockaddr_any peeraddr;
- struct sockaddr_any addr;
- const struct sockaddr_any* outaddr;
- char buf[MAXPATHLEN];
- const char* outname;
-
- memset(&peeraddr, 0, sizeof(peeraddr));
- SANY_LEN(peeraddr) = sizeof(peeraddr);
-
- /* Get the peer name */
- if(getpeername(ctx->client.fd, &SANY_ADDR(peeraddr), &SANY_LEN(peeraddr)) == -1 ||
- sock_any_ntop(&peeraddr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1)
- message(ctx, LOG_WARNING, "couldn't get peer address");
- else
- messagex(ctx, LOG_INFO, "accepted connection from: %s", buf);
-
- /* Create the server connection address */
- outaddr = &(g_state->outaddr);
- outname = g_state->outname;
-
- /* For transparent proxying we have to discover the address to connect to */
- if(g_state->transparent)
- {
- memset(&addr, 0, sizeof(addr));
- SANY_LEN(addr) = sizeof(addr);
-
-#ifdef LINUX_TRANSPARENT_PROXY
- if(getsockopt(ctx->client.fd, SOL_IP, SO_ORIGINAL_DST, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1)
-#else
- if(getsockname(ctx->client.fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1)
-#endif
- {
- message(ctx, LOG_ERR, "couldn't get source address for transparent proxying");
- return -1;
- }
-
- /* Check address types */
- if(sock_any_cmp(&addr, &peeraddr, SANY_OPT_NOPORT) == 0)
- {
- messagex(ctx, LOG_ERR, "loop detected in transparent proxying");
- return -1;
- }
-
- outaddr = &addr;
- }
-
- /* No transparent proxy but check for loopback option */
- else
- {
- if(SANY_TYPE(*outaddr) == AF_INET &&
- outaddr->s.in.sin_addr.s_addr == 0)
- {
- /* Use the incoming IP as the default */
- memcpy(&addr, &(g_state->outaddr), sizeof(addr));
- memcpy(&(addr.s.in.sin_addr), &(peeraddr.s.in.sin_addr), sizeof(addr.s.in.sin_addr));
- outaddr = &addr;
- }
-#ifdef HAVE_INET6
- else if(SANY_TYPE(*outaddr) == AF_INET6 &&
- outaddr->s.in.in6.sin_addr.s_addr == 0)
- {
- /* Use the incoming IP as the default */
- memcpy(&addr, &(g_state->outaddr), sizeof(addr));
- memcpy(&(addr.s.in.sin6_addr), &(peeraddr.s.in.sin6_addr), sizeof(addr.s.in.sin6_addr));
- outaddr = &addr;
- }
-#endif
- }
-
- /* Reparse name if possible */
- if(outaddr != &(g_state->outaddr))
- {
- if(sock_any_ntop(outaddr, buf, MAXPATHLEN, 0) != -1)
- outname = buf;
- else
- outname = "unknown";
- }
-
- /* Connect to the server */
- if(clio_connect(ctx, &(ctx->server), outaddr, outname) == -1)
- return -1;
-
- return 0;
-}
-
/* ----------------------------------------------------------------------------------
- * SMTP HANDLING
+ * SP CALLBACKS
*/
-static int smtp_passthru(clamsmtp_context_t* ctx)
+int cb_check_data(spctx_t* sp)
{
- clio_t* io = NULL;
- char logline[LINE_LENGTH];
- int r, ret = 0;
- int neterror = 0;
+ int r = 0;
+ clctx_t* ctx = (clctx_t*)sp;
- 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 */
+ /* Connect to clamav */
+ if(!spio_valid(&(ctx->clam)))
+ r = connect_clam(ctx);
- ASSERT(clio_valid(&(ctx->clam)) &&
- clio_valid(&(ctx->clam)));
- logline[0] = 0;
+ if(r != -1 && (r = sp_cache_data(sp)) > 0)
- for(;;)
- {
- if(clio_select(ctx, &io) == -1)
- {
- neterror = 1;
- RETURN(-1);
- }
-
- /* Client has data available, read a line and process */
- if(io == &(ctx->client))
- {
- if(clio_read_line(ctx, &(ctx->client), CLIO_DISCARD) == -1)
- RETURN(-1);
-
- /* Client disconnected, we're done */
- if(ctx->linelen == 0)
- RETURN(0);
-
- /* We don't let clients send really long lines */
- if(LINE_TOO_LONG(ctx))
- {
- if(clio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1)
- RETURN(-1);
-
- continue;
- }
-
- /* 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)))
- {
- /* Send back the intermediate response to the client */
- if(clio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1)
- RETURN(-1);
-
- /*
- * Now go into avcheck mode. This also handles the eventual
- * sending of the data to the server, making the av check
- * transparent
- */
- if(avcheck_data(ctx, logline) == -1)
- RETURN(-1);
-
- /* Print the log out for this email */
- messagex(ctx, LOG_INFO, "%s", logline);
-
- /* Reset log line */
- logline[0] = 0;
-
- /* Command handled */
- continue;
- }
-
- /*
- * 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)))
- {
- messagex(ctx, LOG_DEBUG, "filtering EHLO response");
- filter_ehlo = 1;
- filter_host = 1;
-
- /* A new message */
- logline[0] = 0;
- }
-
- /*
- * 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)))
- {
- filter_host = 1;
-
- /* A new message line */
- logline[0] = 0;
- }
-
- /*
- * We don't like these commands. Filter them out. We should have
- * 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)))
- {
- messagex(ctx, LOG_DEBUG, "ESMTP feature not supported");
-
- if(clio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1)
- RETURN(-1);
-
- /* Command handled */
- continue;
- }
-
- /* Append recipients to log line */
- else if((r = check_first_word(ctx->line, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0)
- add_to_logline(logline, "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)
- add_to_logline(logline, "to=", ctx->line + r);
-
- /* Reset log line */
- else if(is_first_word(ctx->line, RSET_CMD, KL(RSET_CMD)))
- logline[0] = 0;
-
- /* All other commands just get passed through to server */
- if(clio_write_data(ctx, &(ctx->server), ctx->line) == -1)
- RETURN(-1);
-
- continue;
- }
-
- /* Server has data available, read a line and forward */
- if(io == &(ctx->server))
- {
- if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1)
- RETURN(-1);
-
- if(ctx->linelen == 0)
- RETURN(0);
-
- if(LINE_TOO_LONG(ctx))
- messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra");
-
- /*
- * We intercept the first response we get from the server.
- * This allows us to change header so that it doesn't look
- * to the client server that we're in a wierd loop.
- *
- * In different situations using the local hostname or
- * 'localhost' don't work because the receiving mail server
- * expects one of those to be its own name. We use 'clamsmtp'
- * instead. No properly configured server would have this
- * as their domain name, and RFC 2821 allows us to use
- * an arbitrary but identifying string.
- */
- if(first_rsp)
- {
- first_rsp = 0;
-
- if(is_first_word(ctx->line, START_RSP, KL(START_RSP)))
- {
- messagex(ctx, LOG_DEBUG, "intercepting initial response");
-
- if(clio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1)
- RETURN(-1);
-
- /* Command handled */
- continue;
- }
- }
-
- /*
- * Certain mail servers (Postfix 1.x in particular) do a loop check
- * on the 250 response after a EHLO or HELO. This is where we
- * filter that to prevent loopback errors.
- */
- if(filter_host)
- {
- filter_host = 0;
-
- /* Check for a simple '250 xxxx' */
- if(is_first_word(ctx->line, OK_RSP, KL(OK_RSP)))
- {
- messagex(ctx, LOG_DEBUG, "intercepting host response");
-
- if(clio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1)
- RETURN(-1);
-
- continue;
- }
-
- /* Check for the continued response '250-xxxx' */
- if(check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0)
- {
- messagex(ctx, LOG_DEBUG, "intercepting host response");
-
- if(clio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1)
- RETURN(-1);
-
- 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((r = check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS)) > 0)
- {
- char* p = ctx->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)) ||
- is_first_word(p, ESMTP_BINARY, KL(ESMTP_BINARY)) ||
- is_first_word(p, ESMTP_CHECK, KL(ESMTP_CHECK)))
- {
- messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space(p));
- continue;
- }
- }
- }
-
- if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1)
- RETURN(-1);
-
- continue;
- }
- }
-
-cleanup:
-
- if(!neterror && ret == -1 && clio_valid(&(ctx->client)))
- clio_write_data(ctx, &(ctx->client), SMTP_FAILED);
-
- return ret;
-}
-
-static void add_to_logline(char* logline, char* prefix, char* line)
-{
- int l = strlen(logline);
- char* t = logline;
-
- /* Simple optimization */
- logline += l;
- l = LINE_LENGTH - l;
-
- ASSERT(l >= 0);
-
- if(t[0] != 0)
- strlcat(logline, ", ", l);
-
- strlcat(logline, prefix, l);
-
- /* Skip initial white space */
- line = trim_start(line);
-
- strlcat(logline, line, l);
-
- /* Skip later white space */
- trim_end(logline);
-}
-
-static int avcheck_data(clamsmtp_context_t* ctx, char* logline)
-{
- /*
- * Note that most failures are non fatal in this function.
- * We only return -1 for data connection errors and the like,
- * For most others we actually send a response back to the
- * client letting them know what happened and let the SMTP
- * connection continue.
- */
-
- char buf[MAXPATHLEN];
- int havefile = 0;
- int r, ret = 0;
-
- strlcpy(buf, g_state->directory, MAXPATHLEN);
- strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN);
-
- /* transfer_to_file deletes the temp file on failure */
- if((r = transfer_to_file(ctx, buf)) > 0)
- {
- havefile = 1;
- r = clam_scan_file(ctx, buf, logline);
- }
+ /* ClamAV doesn't like empty files */
+ r = clam_scan_file(ctx);
switch(r)
{
@@ -1028,8 +327,8 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline)
* the server about any of this yet
*/
case -1:
- if(clio_write_data(ctx, &(ctx->client), SMTP_FAILED))
- RETURN(-1);
+ if(sp_fail_data(sp, NULL) == -1)
+ return -1;
break;
/*
@@ -1037,8 +336,8 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline)
* and transfer the file to it.
*/
case 0:
- if(complete_data_transfer(ctx, buf) == -1)
- RETURN(-1);
+ if(sp_done_data(sp, g_clstate.header) == -1)
+ return -1;
break;
/*
@@ -1048,12 +347,12 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline)
* choose to reset the connection to reuse it if it wants.
*/
case 1:
- if(clio_write_data(ctx, &(ctx->client),
- g_state->bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1)
- RETURN(-1);
-
/* Any special post operation actions on the virus */
- quarantine_virus(ctx, buf);
+ quarantine_virus(ctx);
+
+ if(sp_fail_data(sp, g_clstate.bounce ?
+ SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1)
+ return -1;
break;
default:
@@ -1061,109 +360,114 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline)
break;
};
-cleanup:
- if(havefile && !g_state->debug_files)
- {
- messagex(ctx, LOG_DEBUG, "deleting temporary file: %s", buf);
- unlink(buf);
- }
-
- return ret;
+ return 0;
}
-static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname)
+int cb_parse_option(const char* name, const char* value)
{
- ASSERT(ctx);
- ASSERT(tempname);
-
- /* Ask the server for permission to send data */
- if(clio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1)
- return -1;
-
- if(read_server_response(ctx) == -1)
- return -1;
+ if(strcasecmp(CFG_CLAMADDR, name) == 0)
+ {
+ if(sock_any_pton(value, &(g_clstate.clamaddr), SANY_OPT_DEFLOCAL) == -1)
+ errx(2, "invalid " CFG_CLAMADDR " socket name: %s", value);
+ g_clstate.clamname = value;
+ return 1;
+ }
- /* If server returns an error then tell the client */
- if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP)))
+ else if(strcasecmp(CFG_HEADER, name) == 0)
{
- if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1)
- return -1;
+ g_clstate.header = (const char*)trim_space((char*)value);
- messagex(ctx, LOG_DEBUG, "server refused data transfer");
+ if(strlen(g_clstate.header) == 0)
+ g_clstate.header = NULL;
- return 0;
+ return 1;
}
- /* Now pull up the file and send it to the server */
- if(transfer_from_file(ctx, tempname) == -1)
+ else if(strcasecmp(CFG_DIRECTORY, name) == 0)
{
- /* Tell the client it went wrong */
- clio_write_data(ctx, &(ctx->client), SMTP_FAILED);
- return -1;
+ g_clstate.directory = value;
+ return 1;
}
- /* Okay read the response from the server and echo it to the client */
- if(read_server_response(ctx) == -1)
- return -1;
+ else if(strcasecmp(CFG_BOUNCE, name) == 0)
+ {
+ if((g_clstate.bounce = strtob(value)) == -1)
+ errx(2, "invalid value for " CFG_BOUNCE);
+ return 1;
+ }
- if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1)
- return -1;
+ else if(strcasecmp(CFG_QUARANTINE, name) == 0)
+ {
+ if((g_clstate.quarantine = strtob(value)) == -1)
+ errx(2, "invalid value for " CFG_BOUNCE);
+ return 1;
+ }
+
+ else if(strcasecmp(CFG_DEBUGFILES, name) == 0)
+ {
+ if((g_clstate.debug_files = strtob(value)) == -1)
+ errx(2, "invalid value for " CFG_DEBUGFILES);
+ return 1;
+ }
return 0;
}
-static int read_server_response(clamsmtp_context_t* ctx)
+spctx_t* cb_new_context()
{
- /* Read response line from the server */
- if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1)
- return -1;
-
- if(ctx->linelen == 0)
+ clctx_t* ctx = (clctx_t*)calloc(1, sizeof(clctx_t));
+ if(!ctx)
{
- messagex(ctx, LOG_ERR, "server disconnected unexpectedly");
-
- /* Tell the client it went wrong */
- clio_write_data(ctx, &(ctx->client), SMTP_FAILED);
- return 0;
+ sp_messagex(NULL, LOG_CRIT, "out of memory");
+ return NULL;
}
- if(LINE_TOO_LONG(ctx))
- messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra");
-
- return 0;
+ /* Initial preparation of the structure */
+ spio_init(&(ctx->clam), "CLAMAV");
+ return &(ctx->sp);
}
+void cb_del_context(spctx_t* sp)
+{
+ clctx_t* ctx = (clctx_t*)sp;
+ ASSERT(sp);
+
+ disconnect_clam(ctx);
+ free(ctx);
+}
/* ----------------------------------------------------------------------------------
* CLAM AV
*/
-static int connect_clam(clamsmtp_context_t* ctx)
+static int connect_clam(clctx_t* ctx)
{
int ret = 0;
+ spctx_t* sp = &(ctx->sp);
ASSERT(ctx);
- ASSERT(!clio_valid(&(ctx->clam)));
+ ASSERT(!spio_valid(&(ctx->clam)));
- if(clio_connect(ctx, &(ctx->clam), &(g_state->clamaddr), g_state->clamname) == -1)
+ if(spio_connect(sp, &(ctx->clam), &(g_clstate.clamaddr), g_clstate.clamname) == -1)
RETURN(-1);
- read_junk(ctx, ctx->clam.fd);
+ spio_read_junk(sp, &(ctx->clam));
/* Send a session and a check header to ClamAV */
- if(clio_write_data(ctx, &(ctx->clam), "SESSION\n") == -1)
+ if(spio_write_data(sp, &(ctx->clam), "SESSION\n") == -1)
RETURN(-1);
- read_junk(ctx, ctx->clam.fd);
+ spio_read_junk(sp, &(ctx->clam));
+
/*
- if(clio_write_data(ctx, &(ctx->clam), "PING\n") == -1 ||
- clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1)
+ if(spio_write_data(sp, &(ctx->clam), "PING\n") == -1 ||
+ spio_read_line(sp, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1)
RETURN(-1);
- if(strcmp(ctx->line, CONNECT_RESPONSE) != 0)
+ if(strcmp(sp->line, CONNECT_RESPONSE) != 0)
{
- message(ctx, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line);
+ sp_message(sp, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line);
RETURN(-1);
}
*/
@@ -1171,89 +475,93 @@ static int connect_clam(clamsmtp_context_t* ctx)
cleanup:
if(ret < 0)
- clio_disconnect(ctx, &(ctx->clam));
+ spio_disconnect(sp, &(ctx->clam));
return ret;
}
-static int disconnect_clam(clamsmtp_context_t* ctx)
+static int disconnect_clam(clctx_t* ctx)
{
- if(!clio_valid(&(ctx->clam)))
+ spctx_t* sp = &(ctx->sp);
+
+ if(!spio_valid(&(ctx->clam)))
return 0;
- if(clio_write_data(ctx, &(ctx->clam), CLAM_DISCONNECT) != -1)
- read_junk(ctx, ctx->clam.fd);
+ if(spio_write_data(sp, &(ctx->clam), CLAM_DISCONNECT) != -1)
+ spio_read_junk(sp, &(ctx->clam));
- clio_disconnect(ctx, &(ctx->clam));
+ spio_disconnect(sp, &(ctx->clam));
return 0;
}
-static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline)
+static int clam_scan_file(clctx_t* ctx)
{
int len;
+ spctx_t* sp = &(ctx->sp);
- ASSERT(LINE_LENGTH > MAXPATHLEN + 32);
+ /* Needs to be long enough to hold path names */
+ ASSERT(SP_LINE_LENGTH > MAXPATHLEN + 32);
- strcpy(ctx->line, CLAM_SCAN);
- strcat(ctx->line, tempname);
- strcat(ctx->line, "\n");
+ strcpy(sp->line, CLAM_SCAN);
+ strcat(sp->line, sp->cachename);
+ strcat(sp->line, "\n");
- if(clio_write_data(ctx, &(ctx->clam), ctx->line) == -1)
+ if(spio_write_data(sp, &(ctx->clam), sp->line) == -1)
return -1;
- len = clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM);
+ len = spio_read_line(sp, &(ctx->clam), SPIO_DISCARD | SPIO_TRIM);
if(len == 0)
{
- messagex(ctx, LOG_ERR, "clamd disconnected unexpectedly");
+ sp_messagex(sp, LOG_ERR, "clamd disconnected unexpectedly");
return -1;
}
- if(is_last_word(ctx->line, CLAM_OK, KL(CLAM_OK)))
+ if(is_last_word(sp->line, CLAM_OK, KL(CLAM_OK)))
{
- add_to_logline(logline, "status=", "CLEAN");
- messagex(ctx, LOG_DEBUG, "no virus");
+ sp_add_log(sp, "status=", "CLEAN");
+ sp_messagex(sp, LOG_DEBUG, "no virus");
return 0;
}
- if(is_last_word(ctx->line, CLAM_FOUND, KL(CLAM_FOUND)))
+ if(is_last_word(sp->line, CLAM_FOUND, KL(CLAM_FOUND)))
{
- len = strlen(tempname);
+ len = strlen(sp->cachename);
- if(ctx->linelen > len)
- add_to_logline(logline, "status=VIRUS:", ctx->line + len + 1);
+ if(sp->linelen > len)
+ sp_add_log(sp, "status=VIRUS:", sp->line + len + 1);
else
- add_to_logline(logline, "status=", "VIRUS");
+ sp_add_log(sp, "status=", "VIRUS");
- messagex(ctx, LOG_DEBUG, "found virus");
+ sp_messagex(sp, LOG_DEBUG, "found virus");
return 1;
}
- if(is_last_word(ctx->line, CLAM_ERROR, KL(CLAM_ERROR)))
+ if(is_last_word(sp->line, CLAM_ERROR, KL(CLAM_ERROR)))
{
- messagex(ctx, LOG_ERR, "clamav error: %s", ctx->line);
- add_to_logline(logline, "status=", "CLAMAV-ERROR");
+ sp_messagex(sp, LOG_ERR, "clamav error: %s", sp->line);
+ sp_add_log(sp, "status=", "CLAMAV-ERROR");
return -1;
}
- add_to_logline(logline, "status=", "CLAMAV-ERROR");
- messagex(ctx, LOG_ERR, "unexepected response from clamd: %s", ctx->line);
+ sp_add_log(sp, "status=", "CLAMAV-ERROR");
+ sp_messagex(sp, LOG_ERR, "unexepected response from clamd: %s", sp->line);
return -1;
}
-
/* ----------------------------------------------------------------------------------
* TEMP FILE HANDLING
*/
-static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname)
+static int quarantine_virus(clctx_t* ctx)
{
char buf[MAXPATHLEN];
+ spctx_t* sp = &(ctx->sp);
char* t;
- if(!g_state->quarantine)
+ if(!g_clstate.quarantine)
return 0;
- strlcpy(buf, g_state->directory, MAXPATHLEN);
+ strlcpy(buf, g_clstate.directory, MAXPATHLEN);
strlcat(buf, "/virus.", MAXPATHLEN);
/* Points to null terminator */
@@ -1272,191 +580,29 @@ static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname)
if(!mktemp(buf))
{
- message(ctx, LOG_ERR, "couldn't create quarantine file name");
+ sp_message(sp, LOG_ERR, "couldn't create quarantine file name");
return -1;
}
/* Try to link the file over to the temp */
- if(link(tempname, buf) == -1)
+ if(link(sp->cachename, buf) == -1)
{
/* We don't want to allow race conditions */
if(errno == EEXIST)
{
- message(ctx, LOG_WARNING, "race condition when quarantining virus file: %s", buf);
+ sp_message(sp, LOG_WARNING, "race condition when quarantining virus file: %s", buf);
continue;
}
- message(ctx, LOG_ERR, "couldn't quarantine virus file");
+ sp_message(sp, LOG_ERR, "couldn't quarantine virus file");
return -1;
}
break;
}
- messagex(ctx, LOG_INFO, "quarantined virus file as: %s", buf);
+ sp_messagex(sp, LOG_INFO, "quarantined virus file as: %s", buf);
return 0;
}
-static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname)
-{
- FILE* tfile = NULL;
- int tfd = -1;
- int ended_crlf = 1; /* If the last line ended with a CRLF */
- int ret = 0;
- int count = 0;
-
- if((tfd = mkstemp(tempname)) == -1 ||
- (tfile = fdopen(tfd, "w")) == NULL)
- {
- message(ctx, LOG_ERR, "couldn't open temp file");
- RETURN(-1);
- }
-
- messagex(ctx, LOG_DEBUG, "created temporary file: %s", tempname);
-
- for(;;)
- {
- 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 already printed */
- RETURN(-1);
- };
-
- if(ended_crlf && strcmp(ctx->line, DATA_END_SIG) == 0)
- break;
-
- /* We check errors on this later */
- fwrite(ctx->line, 1, ctx->linelen, tfile);
- count += ctx->linelen;
-
- /* Check if this line ended with a CRLF */
- ended_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0);
- }
-
- if(ferror(tfile))
- {
- message(ctx, LOG_ERR, "error writing to temp file: %s", tempname);
- RETURN(-1);
- }
- ret = count;
- messagex(ctx, LOG_DEBUG, "wrote %d bytes to temp file", count);
-
-cleanup:
-
- if(tfile)
- fclose(tfile);
-
- if(tfd != -1)
- {
- /* Only close this if not opened as a stream */
- if(tfile == NULL)
- close(tfd);
-
- if(ret <= 0)
- {
- messagex(ctx, LOG_DEBUG, "discarding temporary file");
- unlink(tempname);
- }
- }
-
- return ret;
-}
-
-static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename)
-{
- FILE* file = NULL;
- int header = 0;
- int ret = 0;
-
- file = fopen(filename, "r");
- if(file == NULL)
- {
- message(ctx, LOG_ERR, "couldn't open temporary file: %s", filename);
- RETURN(-1);
- }
-
- messagex(ctx, LOG_DEBUG, "sending from temporary file: %s", filename);
-
- while(fgets(ctx->line, LINE_LENGTH, file) != NULL)
- {
- if(g_state->header && !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(clio_write_data_raw(ctx, &(ctx->server), (char*)g_state->header, strlen(g_state->header)) == -1 ||
- clio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1)
- RETURN(-1);
-
- header = 1;
- }
- }
-
- if(clio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1)
- RETURN(-1);
- }
-
- if(ferror(file))
- {
- message(ctx, LOG_ERR, "error reading temporary file: %s", filename);
- RETURN(-1);
- }
-
- if(clio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1)
- RETURN(-1);
-
- messagex(ctx, LOG_DEBUG, "sent email data");
-
-cleanup:
-
- if(file != NULL)
- fclose(file);
-
- return ret;
-}
-
-
-/* ----------------------------------------------------------------------------------
- * NETWORKING
- */
-
-static void read_junk(clamsmtp_context_t* ctx, int fd)
-{
- char buf[16];
- const char* t;
- int said = 0;
- int l;
-
- if(fd == -1)
- return;
-
- /* Make it non blocking */
- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
-
- for(;;)
- {
- l = read(fd, buf, sizeof(buf) - 1);
- if(l <= 0)
- break;
-
- buf[l] = 0;
- t = trim_start(buf);
-
- if(!said && *t)
- {
- messagex(ctx, LOG_DEBUG, "received junk data from daemon");
- said = 1;
- }
- }
-
- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
-}