From c493a11b449f5071aed9adb34faa3e3dd85ea762 Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)> Date: Thu, 8 Jul 2004 18:21:20 +0000 Subject: New repository initialized by cvs2svn. --- src/proxsmtpd.c | 904 -------------------------------------------------------- 1 file changed, 904 deletions(-) delete mode 100644 src/proxsmtpd.c (limited to 'src/proxsmtpd.c') diff --git a/src/proxsmtpd.c b/src/proxsmtpd.c deleted file mode 100644 index fbd5d25..0000000 --- a/src/proxsmtpd.c +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) 2004, Nate Nielsen - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * * Redistributions in binary form must reproduce the - * above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * * The names of contributors to this software may not be - * used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - * DAMAGE. - * - * - * CONTRIBUTORS - * Nate Nielsen - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "usuals.h" - -#include "compat.h" -#include "sock_any.h" -#include "stringx.h" -#include "smtppass.h" - -/* ----------------------------------------------------------------------- - * STRUCTURES - */ - -typedef struct pxstate -{ - /* Settings ------------------------------- */ - const char* command; /* The command to pipe email through */ - struct timeval timeout; /* The command timeout */ - int pipe_cmd; /* Whether command is a pipe or not */ - const char* directory; /* The directory for temp files */ -} -pxstate_t; - -/* ----------------------------------------------------------------------- - * STRINGS - */ - -#define REJECTED "Content Rejected" - -#define DEFAULT_CONFIG CONF_PREFIX "/proxsmtpd.conf" -#define DEFAULT_TIMEOUT 30 - -#define CFG_FILTERCMD "FilterCommand" -#define CFG_FILTERTYPE "FilterType" -#define CFG_DIRECTORY "TempDirectory" -#define CFG_DEBUGFILES "DebugFiles" -#define CFG_CMDTIMEOUT "FilterTimeout" - -#define TYPE_PIPE "pipe" -#define TYPE_FILE "file" - -/* Poll time for waiting operations in milli seconds */ -#define POLL_TIME 20 - -/* read & write ends of a pipe */ -#define READ_END 0 -#define WRITE_END 1 - -/* pre-set file descriptors */ -#define STDIN 0 -#define STDOUT 1 -#define STDERR 2 - -/* ----------------------------------------------------------------------- - * GLOBALS - */ - -pxstate_t g_pxstate; - -/* ----------------------------------------------------------------------- - * FORWARD DECLARATIONS - */ - -static void usage(); -static int process_file_command(spctx_t* sp); -static int process_pipe_command(spctx_t* sp); -static void final_reject_message(char* buf, int buflen); -static void buffer_reject_message(char* data, char* buf, int buflen); -static int kill_process(spctx_t* sp, pid_t pid); -static int wait_process(spctx_t* sp, pid_t pid, int* status); - -/* ---------------------------------------------------------------------------------- - * STARTUP ETC... - */ - -int main(int argc, char* argv[]) -{ - const char* configfile = DEFAULT_CONFIG; - const char* pidfile = NULL; - int dbg_level = -1; - int ch = 0; - int r; - char* t; - - /* Setup some defaults */ - memset(&g_pxstate, 0, sizeof(g_pxstate)); - g_pxstate.directory = _PATH_TMP; - g_pxstate.pipe_cmd = 1; - g_pxstate.timeout.tv_sec = DEFAULT_TIMEOUT; - - sp_init("proxsmtpd"); - - /* - * 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, "d:f:p:v")) != -1) - { - switch(ch) - { - /* Don't daemonize */ - case 'd': - dbg_level = strtol(optarg, &t, 10); - if(*t) /* parse error */ - errx(1, "invalid debug log level"); - dbg_level += LOG_ERR; - break; - - /* The configuration file */ - case 'f': - configfile = optarg; - break; - - /* Write out a pid file */ - case 'p': - pidfile = optarg; - break; - - /* Print version number */ - case 'v': - printf("clamsmtpd (version %s)\n", VERSION); - printf(" (config: %s)\n", DEFAULT_CONFIG); - exit(0); - break; - - /* Usage information */ - case '?': - default: - usage(); - break; - } - } - - argc -= optind; - argv += optind; - - if(argc > 0) - usage(); - - r = sp_run(configfile, pidfile, dbg_level); - - sp_done(); - - return r; -} - -static void usage() -{ - fprintf(stderr, "usage: proxsmtpd [-d debuglevel] [-f configfile] [-p pidfile]\n"); - fprintf(stderr, " proxsmtpd -v\n"); - exit(2); -} - -/* ---------------------------------------------------------------------------------- - * SP CALLBACKS - */ - -int cb_check_data(spctx_t* ctx) -{ - int r = 0; - - if(!g_pxstate.command) - { - sp_messagex(ctx, LOG_WARNING, "no filter command specified. passing message through"); - - if(sp_cache_data(ctx) == -1 || - sp_done_data(ctx) == -1) - return -1; /* Message already printed */ - - return 0; - } - - /* Cleanup any old filters hanging around */ - while(waitpid(-1, &r, WNOHANG) > 0) - ; - - if(g_pxstate.pipe_cmd) - r = process_pipe_command(ctx); - else - r = process_file_command(ctx); - - if(r == -1) - { - if(sp_fail_data(ctx, NULL) == -1) - return -1; - } - - return 0; -} - -int cb_parse_option(const char* name, const char* value) -{ - char* t; - - if(strcasecmp(CFG_FILTERCMD, name) == 0) - { - g_pxstate.command = value; - return 1; - } - - else if(strcasecmp(CFG_DIRECTORY, name) == 0) - { - g_pxstate.directory = value; - return 1; - } - - else if(strcasecmp(CFG_CMDTIMEOUT, name) == 0) - { - g_pxstate.timeout.tv_sec = strtol(value, &t, 10); - if(*t || g_pxstate.timeout.tv_sec <= 0) - errx(2, "invalid setting: " CFG_CMDTIMEOUT); - return 1; - } - - else if(strcasecmp(CFG_FILTERTYPE, name) == 0) - { - if(strcasecmp(value, TYPE_PIPE) == 0) - g_pxstate.pipe_cmd = 1; - else if(strcasecmp(value, TYPE_FILE) == 0) - g_pxstate.pipe_cmd = 0; - else - errx(2, "invalid value for " CFG_FILTERTYPE " (must specify 'pipe' or 'file')"); - return 1; - } - - return 0; -} - -spctx_t* cb_new_context() -{ - spctx_t* ctx = (spctx_t*)calloc(1, sizeof(spctx_t)); - if(!ctx) - sp_messagex(NULL, LOG_CRIT, "out of memory"); - return ctx; -} - -void cb_del_context(spctx_t* ctx) -{ - free(ctx); -} - -/* ----------------------------------------------------------------------------- - * IMPLEMENTATION - */ - -static pid_t fork_filter(spctx_t* sp, int* infd, int* outfd, int* errfd) -{ - pid_t pid; - int ret = 0; - int r = 0; - - /* Pipes for input, output, err */ - int pipe_i[2]; - int pipe_o[2]; - int pipe_e[2]; - - memset(pipe_i, ~0, sizeof(pipe_i)); - memset(pipe_o, ~0, sizeof(pipe_o)); - memset(pipe_e, ~0, sizeof(pipe_e)); - - ASSERT(g_pxstate.command); - - /* Create the pipes we need */ - if((infd && pipe(pipe_i) == -1) || - (outfd && pipe(pipe_o) == -1) || - (errfd && pipe(pipe_e) == -1)) - { - sp_message(sp, LOG_ERR, "couldn't create pipe for filter command"); - RETURN(-1); - } - - /* Now fork the pipes across processes */ - switch(pid = fork()) - { - case -1: - sp_message(sp, LOG_ERR, "couldn't fork for filter command"); - RETURN(-1); - - /* The child process */ - case 0: - - if(r >= 0 && infd) - { - close(pipe_i[WRITE_END]); - r = dup2(pipe_i[READ_END], STDIN); - } - - if(r >= 0 && outfd) - { - close(pipe_o[READ_END]); - r = dup2(pipe_o[WRITE_END], STDOUT); - } - - if(r >= 0 && errfd) - { - close(pipe_e[READ_END]); - r = dup2(pipe_e[WRITE_END], STDERR); - } - - if(r < 0) - { - sp_message(sp, LOG_ERR, "couldn't dup descriptors for filter command"); - _exit(1); - } - - /* All the necessary environment vars */ - sp_setup_forked(sp, 1); - - /* Now run the filter command */ - execl("/bin/sh", "sh", "-c", g_pxstate.command, NULL); - - /* If that returned then there was an error */ - sp_message(sp, LOG_ERR, "error executing the shell for filter command"); - _exit(1); - break; - }; - - /* The parent process */ - sp_messagex(sp, LOG_DEBUG, "executed filter command: %s (pid: %d)", g_pxstate.command, (int)pid); - - /* Setup all our return values */ - if(infd) - { - *infd = pipe_i[WRITE_END]; - pipe_i[WRITE_END] = -1; - fcntl(*infd, F_SETFL, fcntl(*infd, F_GETFL, 0) | O_NONBLOCK); - } - - if(outfd) - { - *outfd = pipe_o[READ_END]; - pipe_o[READ_END] = -1; - fcntl(*outfd, F_SETFL, fcntl(*outfd, F_GETFL, 0) | O_NONBLOCK); - } - - if(errfd) - { - *errfd = pipe_e[READ_END]; - pipe_e[READ_END] = -1; - fcntl(*errfd, F_SETFL, fcntl(*errfd, F_GETFL, 0) | O_NONBLOCK); - } - -cleanup: - if(pipe_i[READ_END] != -1) - close(pipe_i[READ_END]); - if(pipe_i[WRITE_END] != -1) - close(pipe_i[WRITE_END]); - if(pipe_o[READ_END] != -1) - close(pipe_o[READ_END]); - if(pipe_o[WRITE_END] != -1) - close(pipe_o[WRITE_END]); - if(pipe_e[READ_END] != -1) - close(pipe_e[READ_END]); - if(pipe_e[WRITE_END] != -1) - close(pipe_e[WRITE_END]); - - return ret >= 0 ? pid : (pid_t)-1; -} - -static int process_file_command(spctx_t* sp) -{ - pid_t pid; - int ret = 0, status, r; - struct timeval timeout; - - /* For reading data from the process */ - int errfd; - fd_set rmask; - char obuf[1024]; - char ebuf[256]; - - memset(ebuf, 0, sizeof(ebuf)); - - if(sp_cache_data(sp) == -1) - RETURN(-1); /* message already printed */ - - pid = fork_filter(sp, NULL, NULL, &errfd); - if(pid == (pid_t)-1) - RETURN(-1); - - /* Main read write loop */ - while(errfd != -1) - { - FD_ZERO(&rmask); - FD_SET(errfd, &rmask); - - /* Select can modify the timeout argument so we copy */ - memcpy(&timeout, &(g_pxstate.timeout), sizeof(timeout)); - - r = select(FD_SETSIZE, &rmask, NULL, NULL, &timeout); - - switch(r) - { - case -1: - sp_message(sp, LOG_ERR, "couldn't select while listening to filter command"); - RETURN(-1); - case 0: - sp_messagex(sp, LOG_ERR, "timeout while listening to filter command"); - RETURN(-1); - }; - - ASSERT(FD_ISSET(errfd, &rmask)); - - /* Note because we handle as string we save one byte for null-termination */ - r = read(errfd, obuf, sizeof(obuf) - 1); - if(r < 0) - { - if(errno != EINTR && errno != EAGAIN) - { - sp_message(sp, LOG_ERR, "couldn't read data from filter command"); - RETURN(-1); - } - - continue; - } - - if(r == 0) - { - close(errfd); - errfd = -1; - break; - } - - /* Null terminate */ - obuf[r] = 0; - - /* And process */ - buffer_reject_message(obuf, ebuf, sizeof(ebuf)); - - if(sp_is_quit()) - RETURN(-1); - } - - /* exit the process if not completed */ - if(wait_process(sp, pid, &status) == -1) - { - sp_messagex(sp, LOG_ERR, "timeout waiting for filter command to exit"); - RETURN(-1); - } - - pid = 0; - - /* We only trust well behaved programs */ - if(!WIFEXITED(status)) - { - sp_messagex(sp, LOG_ERR, "filter command terminated abnormally"); - RETURN(-1); - } - - sp_messagex(sp, LOG_DEBUG, "filter exit code: %d", (int)WEXITSTATUS(status)); - - /* A successful response */ - if(WEXITSTATUS(status) == 0) - { - if(sp_done_data(sp) == -1) - RETURN(-1); /* message already printed */ - - sp_add_log(sp, "status=", "FILTERED"); - } - - /* Check code and use stderr if bad code */ - else - { - final_reject_message(ebuf, sizeof(ebuf)); - - if(sp_fail_data(sp, ebuf) == -1) - RETURN(-1); /* message already printed */ - - sp_add_log(sp, "status=", ebuf); - } - - ret = 0; - -cleanup: - - if(pid != 0) - { - sp_messagex(sp, LOG_WARNING, "killing filter process (pid %d)", (int)pid); - kill_process(sp, pid); - } - - if(errfd != -1) - close(errfd); - - if(ret < 0) - sp_add_log(sp, "status=", "FILTER-ERROR"); - - return ret; -} - -static int process_pipe_command(spctx_t* sp) -{ - pid_t pid; - int ret = 0, status, r; - struct timeval timeout; - - /* For sending data to the process */ - const char* ibuf = NULL; - int ilen = 0; - int infd; - int icount = 0; - fd_set wmask; - - /* For reading data from the process */ - int outfd; - int errfd; - fd_set rmask; - char obuf[1024]; - char ebuf[256]; - int ocount = 0; - - ASSERT(g_pxstate.command); - - memset(ebuf, 0, sizeof(ebuf)); - - pid = fork_filter(sp, &infd, &outfd, &errfd); - if(pid == (pid_t)-1) - RETURN(-1); - - /* Opens cache file */ - if(sp_write_data(sp, obuf, 0) == -1) - RETURN(-1); /* message already printed */ - - /* Main read write loop */ - while(infd != -1 || outfd != -1 || errfd != -1) - { - FD_ZERO(&rmask); - FD_ZERO(&wmask); - - /* We only select on those that are still open */ - if(infd != -1) - FD_SET(infd, &wmask); - - if(outfd != -1) - FD_SET(outfd, &rmask); - - if(errfd != -1) - FD_SET(errfd, &rmask); - - /* Select can modify the timeout argument so we copy */ - memcpy(&timeout, &(g_pxstate.timeout), sizeof(timeout)); - - r = select(FD_SETSIZE, &rmask, &wmask, NULL, &timeout); - switch(r) - { - case -1: - sp_message(sp, LOG_ERR, "couldn't select while listening to filter command"); - RETURN(-1); - case 0: - sp_messagex(sp, LOG_WARNING, "timeout while listening to filter command"); - RETURN(-1); - }; - - /* Handling of process's stdin */ - if(infd != -1 && FD_ISSET(infd, &wmask)) - { - if(ilen <= 0) - { - /* Read some more data into buffer */ - switch(r = sp_read_data(sp, &ibuf)) - { - case -1: - RETURN(-1); /* Message already printed */ - case 0: - close(infd); /* Done with the input */ - infd = -1; - break; - default: - ASSERT(r > 0); - ilen = r; - break; - }; - } - - if(ilen > 0) - { - /* Write data from buffer */ - r = write(infd, ibuf, ilen); - if(r == -1) - { - if(errno == EPIPE) - { - sp_messagex(sp, LOG_WARNING, "filter command closed input early"); - - /* Eat up the rest of the data */ - while(sp_read_data(sp, &ibuf) > 0) - ; - - close(infd); - infd = -1; - break; - } - else if(errno != EAGAIN && errno != EINTR) - { - /* Otherwise it's a normal error */ - sp_message(sp, LOG_ERR, "couldn't write to filter command"); - RETURN(-1); - } - } - - /* A good normal write */ - else - { - icount += r; - ilen -= r; - ibuf += r; - } - } - } - - /* Handling of stdout, which should be email data */ - if(outfd != -1 && FD_ISSET(outfd, &rmask)) - { - r = read(outfd, obuf, sizeof(obuf)); - if(r > 0) - { - if(sp_write_data(sp, obuf, r) == -1) - RETURN(-1); /* message already printed */ - - ocount += r; - } - - else if(r == 0) - { - close(outfd); - outfd = -1; - } - - else if(r < 0) - { - if(errno != EINTR && errno != EAGAIN) - { - sp_message(sp, LOG_ERR, "couldn't read data from filter command"); - RETURN(-1); - } - } - } - - /* Handling of stderr, the last line of which we use as an err message*/ - if(errfd != -1 && FD_ISSET(errfd, &rmask)) - { - /* Note because we handle as string we save one byte for null-termination */ - r = read(errfd, obuf, sizeof(obuf) - 1); - if(r < 0) - { - if(errno != EINTR && errno != EAGAIN) - { - sp_message(sp, LOG_ERR, "couldn't read data from filter command"); - RETURN(-1); - } - } - - else if(r == 0) - { - close(errfd); - errfd = -1; - } - - else if(r > 0) - { - /* Null terminate */ - obuf[r] = 0; - - /* And process */ - buffer_reject_message(obuf, ebuf, sizeof(ebuf)); - } - } - - if(sp_is_quit()) - RETURN(-1); - } - - sp_messagex(sp, LOG_DEBUG, "wrote %d bytes to filter, read %d bytes", icount, ocount); - - /* Close the cache file */ - if(sp_write_data(sp, NULL, 0) == -1) - RETURN(-1); /* message already printed */ - - if(wait_process(sp, pid, &status) == -1) - { - sp_messagex(sp, LOG_ERR, "timeout waiting for filter command to exit"); - RETURN(-1); - } - - pid = 0; - - /* We only trust well behaved programs */ - if(!WIFEXITED(status)) - { - sp_messagex(sp, LOG_ERR, "filter command terminated abnormally"); - RETURN(-1); - } - - sp_messagex(sp, LOG_DEBUG, "filter exit code: %d", (int)WEXITSTATUS(status)); - - /* A successful response */ - if(WEXITSTATUS(status) == 0) - { - if(sp_done_data(sp) == -1) - RETURN(-1); /* message already printed */ - - sp_add_log(sp, "status=", "FILTERED"); - } - - /* Check code and use stderr if bad code */ - else - { - final_reject_message(ebuf, sizeof(ebuf)); - - if(sp_fail_data(sp, ebuf) == -1) - RETURN(-1); /* message already printed */ - - sp_add_log(sp, "status=", ebuf); - } - - ret = 0; - -cleanup: - - if(infd != -1) - close(infd); - if(outfd != -1) - close(outfd); - if(errfd != -1) - close(errfd); - - if(pid != 0) - { - sp_messagex(sp, LOG_WARNING, "killing filter process (pid %d)", (int)pid); - kill_process(sp, pid); - } - - if(ret < 0) - sp_add_log(sp, "status=", "FILTER-ERROR"); - - return ret; -} - -static void final_reject_message(char* buf, int buflen) -{ - if(buf[0] == 0) - strlcpy(buf, REJECTED, buflen); - else - trim_end(buf); -} - -static void buffer_reject_message(char* data, char* buf, int buflen) -{ - int len = strlen(data); - char* t = data + len; - int newline = 0; - - while(t > data && isspace(*(t - 1))) - { - t--; - - if(*t == '\n') - newline = 1; - } - - /* No valid line */ - if(t > data) - { - if(newline) - *t = 0; - - t = strrchr(data, '\n'); - if(t == NULL) - { - t = trim_start(data); - - /* - * Basically if we already have a newline at the end - * then we need to start a new line - */ - if(buf[strlen(buf)] == '\n') - buf[0] = 0; - } - else - { - t = trim_start(t); - - /* Start a new line */ - buf[0] = 0; - } - - /* t points to a valid line */ - strlcat(buf, t, buflen); - } - - /* Always append if we found a newline */ - if(newline) - strlcat(buf, "\n", buflen); -} - -static int wait_process(spctx_t* sp, pid_t pid, int* status) -{ - /* We poll x times a second */ - int waits = g_pxstate.timeout.tv_sec * (1000 / POLL_TIME); - - while(waits > 0) - { - switch(waitpid(pid, status, WNOHANG)) - { - case 0: - break; - case -1: - if(errno != ECHILD || errno != ESRCH) - { - sp_message(sp, LOG_CRIT, "error waiting on process"); - return -1; - } - /* fall through */ - default: - return 0; - } - - usleep(POLL_TIME * 1000); - waits--; - } - - return -1; -} - -static int kill_process(spctx_t* sp, pid_t pid) -{ - int status; - - if(kill(pid, SIGTERM) == -1) - { - if(errno == ESRCH) - return 0; - - sp_message(sp, LOG_ERR, "couldn't send signal to process"); - return -1; - } - - if(wait_process(sp, pid, &status) == -1) - { - if(kill(pid, SIGKILL) == -1) - { - if(errno == ESRCH) - return 0; - - sp_message(sp, LOG_ERR, "couldn't send signal to process"); - return -1; - } - - sp_messagex(sp, LOG_ERR, "process wouldn't quit. forced termination"); - } - - return 0; -} -- cgit v1.2.3