summaryrefslogtreecommitdiff
path: root/common/smtppass.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/smtppass.c')
-rw-r--r--common/smtppass.c2021
1 files changed, 0 insertions, 2021 deletions
diff --git a/common/smtppass.c b/common/smtppass.c
deleted file mode 100644
index f8129ed..0000000
--- a/common/smtppass.c
+++ /dev/null
@@ -1,2021 +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 <nielsen@memberwebs.com>
- * Andreas Steinmetz <ast@domdv.de>
- * Rubio Vaughan <rubio@passim.net>
- * Olivier Beyssac <ob@r14.freenix.org>
- */
-
-#define _GNU_SOURCE
-
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/param.h>
-#include <sys/stat.h>
-
-#include <ctype.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <syslog.h>
-#include <signal.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <pwd.h>
-#include <time.h>
-
-#include "usuals.h"
-
-#ifdef LINUX_TRANSPARENT_PROXY
-#include <linux/netfilter_ipv4.h>
-#endif
-
-#include "compat.h"
-#include "sock_any.h"
-#include "stringx.h"
-#include "sppriv.h"
-
-/* -----------------------------------------------------------------------
- * STRUCTURES
- */
-
-typedef struct spthread
-{
- pthread_t tid; /* Written to by the main thread */
- int fd; /* The file descriptor or -1 */
-}
-spthread_t;
-
-/* -----------------------------------------------------------------------
- * DATA
- */
-
-#define CRLF "\r\n"
-
-#define SMTP_TOOLONG "500 Line too long" CRLF
-#define SMTP_STARTBUSY "421 Server busy, too many connections" CRLF
-#define SMTP_STARTFAILED "421 Local Error, cannot start thread" 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_NOTAUTH "554 Insufficient authorization" CRLF
-#define SMTP_OK "250 Ok" CRLF
-#define SMTP_REJPREFIX "550 Content Rejected; "
-
-#define SMTP_DATA "DATA" CRLF
-#define SMTP_NOOP "NOOP" CRLF
-#define SMTP_XCLIENT "XCLIENT ADDR=%s" CRLF
-#define SMTP_BANNER "220 smtp.passthru" CRLF
-#define SMTP_HELO_RSP "250 smtp.passthru" CRLF
-#define SMTP_EHLO_RSP "250-smtp.passthru" CRLF
-#define SMTP_FEAT_RSP "250 XFILTERED" 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 ESMTP_XCLIENT "XCLIENT"
-#define ESMTP_XEXCH50 "XEXCH50"
-
-#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 XCLIENT_CMD "XCLIENT"
-
-#define DATA_END_SIG "." CRLF
-
-#define DATA_RSP "354"
-#define OK_RSP "250"
-#define START_RSP "220"
-
-#define RCVD_HEADER "Received:"
-
-/* The set of delimiters that can be present between config and value */
-#define CFG_DELIMS ": \t"
-
-/* Maximum length of the header argument */
-#define MAX_HEADER_LENGTH 1024
-
-/*
- * asctime_r manpage: "stores the string in a user-supplied buffer of
- * length at least 26". We'll need some more bytes to put timezone
- * information behind
- */
-#define MAX_DATE_LENGTH 64
-
-#define LINE_TOO_LONG(l) ((l) >= (SP_LINE_LENGTH - 2))
-
-/* -----------------------------------------------------------------------
- * CONFIGURATION OPTIONS
- *
- * - Be sure that your configuration option needs to go into this
- * file. More likely it'll go into clamsmtpd.c
- * - When adding configuration options follow the instructions in
- * clamsmtpd.c, except add option to spstate_t (sppriv.h) and parse in
- * sp_parse_option (below)
- */
-
-#define CFG_MAXTHREADS "MaxConnections"
-#define CFG_TIMEOUT "TimeOut"
-#define CFG_OUTADDR "OutAddress"
-#define CFG_LISTENADDR "Listen"
-#define CFG_HEADER "Header"
-#define CFG_TRANSPARENT "TransparentProxy"
-#define CFG_DIRECTORY "TempDirectory"
-#define CFG_KEEPALIVES "KeepAlives"
-#define CFG_USER "User"
-#define CFG_PIDFILE "PidFile"
-#define CFG_XCLIENT "XClient"
-
-/* -----------------------------------------------------------------------
- * DEFAULT SETTINGS
- */
-
-#define DEFAULT_SOCKET "10025"
-#define DEFAULT_PORT 10025
-#define DEFAULT_MAXTHREADS 64
-#define DEFAULT_TIMEOUT 180
-#define DEFAULT_KEEPALIVES 0
-
-/* -----------------------------------------------------------------------
- * GLOBALS
- */
-
-spstate_t g_state; /* The state and configuration of the daemon */
-unsigned int g_unique_id = 0x00100000; /* For connection ids */
-pthread_mutex_t g_mutex; /* The main mutex */
-pthread_mutexattr_t g_mtxattr;
-
-/* -----------------------------------------------------------------------
- * FORWARD DECLARATIONS
- */
-
-static void on_quit(int signal);
-static void drop_privileges();
-static void pid_file(int write);
-static void connection_loop(int sock);
-static void* thread_main(void* arg);
-static int smtp_passthru(spctx_t* ctx);
-static int make_connections(spctx_t* ctx, int client);
-static int read_server_response(spctx_t* ctx);
-static int parse_config_file(const char* configfile);
-static char* parse_address(char* line);
-static const char* get_successful_rsp(const char* line, int* cont);
-static void do_server_noop(spctx_t* ctx);
-
-/* Used externally in some cases */
-int sp_parse_option(const char* name, const char* option);
-
-/* ----------------------------------------------------------------------------------
- * BASIC RUN FUNCTIONALITY
- */
-
-void sp_init(const char* name)
-{
- int r;
-
- ASSERT(name);
-
- memset(&g_state, 0, sizeof(g_state));
-
- sp_message(NULL, LOG_DEBUG, "%s (%s)", name, VERSION);
-
- /* Setup the defaults */
- g_state.debug_level = -1;
- g_state.max_threads = DEFAULT_MAXTHREADS;
- g_state.timeout.tv_sec = DEFAULT_TIMEOUT;
- g_state.keepalives = DEFAULT_KEEPALIVES;
- g_state.directory = _PATH_TMP;
- g_state.name = name;
-
- /* We need the default to parse into a useable form, so we do this: */
- r = sp_parse_option(CFG_LISTENADDR, DEFAULT_SOCKET);
- ASSERT(r == 1);
-
- /* Create the main mutex and condition variable */
- if(pthread_mutexattr_init(&g_mtxattr) != 0 ||
-#ifdef HAVE_ERR_MUTEX
- pthread_mutexattr_settype(&g_mtxattr, MUTEX_TYPE) ||
-#endif
- pthread_mutex_init(&g_mutex, &g_mtxattr) != 0)
- errx(1, "threading problem. can't create mutex or condition var");
-}
-
-int sp_run(const char* configfile, const char* pidfile, int dbg_level)
-{
- int sock;
- int true = 1;
-
- ASSERT(configfile);
- ASSERT(g_state.name);
-
- if(!(dbg_level == -1 || dbg_level <= LOG_DEBUG))
- errx(2, "invalid debug log level (must be between 1 and 4)");
- g_state.debug_level = dbg_level;
- g_state.pidfile = pidfile;
-
- /* Now parse the configuration file */
- if(parse_config_file(configfile) == -1)
- {
- /*
- * We used to do a check here before whether it was the default
- * configuration file or not, but we can't do that any longer
- * as it comes from the app. Usually lack of a configuration
- * file will cause the following checks to fail
- */
- warnx("configuration file not found: %s", configfile);
- }
-
- /* This option has no default, but is required ... */
- if(g_state.outname == NULL && !g_state.transparent)
- errx(2, "no " CFG_OUTADDR " specified.");
-
- /* ... unless we're in transparent proxy mode */
- else if(g_state.outname != NULL && g_state.transparent)
- warnx("the " CFG_OUTADDR " option will be ignored when " CFG_TRANSPARENT " is enabled");
-
- sp_messagex(NULL, LOG_DEBUG, "starting up (%s)...", VERSION);
-
- /* Drop privileges before daemonizing */
- drop_privileges();
-
- /* When set to this we daemonize */
- if(g_state.debug_level == -1)
- {
- /* Fork a daemon nicely here */
- if(daemon(0, 0) == -1)
- {
- sp_message(NULL, LOG_ERR, "couldn't run as daemon");
- exit(1);
- }
-
- sp_messagex(NULL, LOG_DEBUG, "running as a daemon");
- g_state.daemonized = 1;
-
- /* Open the system log */
- openlog(g_state.name, 0, LOG_MAIL);
- }
-
- /* 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);
-
- /* Create the socket */
- sock = socket(SANY_TYPE(g_state.listenaddr), SOCK_STREAM, 0);
- if(sock < 0)
- {
- sp_message(NULL, LOG_CRIT, "couldn't open socket");
- exit(1);
- }
-
- fcntl(sock, F_SETFD, fcntl(sock, F_GETFD, 0) | FD_CLOEXEC);
- 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)
- {
- sp_message(NULL, LOG_CRIT, "couldn't bind to address: %s", g_state.listenname);
- exit(1);
- }
-
- sp_messagex(NULL, LOG_DEBUG, "created socket: %s", g_state.listenname);
-
- /* Let 5 connections queue up */
- if(listen(sock, 5) != 0)
- {
- sp_message(NULL, LOG_CRIT, "couldn't listen on socket");
- exit(1);
- }
-
- pid_file(1);
-
- sp_messagex(NULL, LOG_DEBUG, "accepting connections");
-
- connection_loop(sock);
-
- pid_file(0);
-
- /* Our listen socket */
- close(sock);
-
- sp_messagex(NULL, LOG_DEBUG, "stopped processing");
- return 0;
-}
-
-void sp_quit()
-{
- /* The handler sets the flag and this also interrupts io */
- kill(getpid(), SIGTERM);
-}
-
-int sp_is_quit()
-{
- return g_state.quit ? 1 : 0;
-}
-
-void sp_done()
-{
- /* Close the mutex */
- pthread_mutex_destroy(&g_mutex);
- pthread_mutexattr_destroy(&g_mtxattr);
-
- if(g_state._p)
- free(g_state._p);
-
- memset(&g_state, 0, sizeof(g_state));
-}
-
-static void on_quit(int signal)
-{
- g_state.quit = 1;
-}
-
-static void drop_privileges()
-{
- char* t;
- struct passwd* pw;
- uid_t uid;
-
- if(g_state.user)
- {
- if(geteuid() != 0)
- {
- sp_messagex(NULL, LOG_WARNING, "must be started as root to switch to user: %s", g_state.user);
- return;
- }
-
- uid = strtol(g_state.user, &t, 10);
- if(!t[0]) /* successful parse */
- pw = getpwuid(uid);
- else /* must be a name */
- pw = getpwnam(g_state.user);
-
- if(pw == NULL)
- errx(1, "couldn't look up user: %s", g_state.user);
-
- if(setgid(pw->pw_gid) == -1 ||
- setuid(pw->pw_uid) == -1)
- err(1, "unable to switch to user: %s (uid %d, gid %d)", g_state.user, pw->pw_uid, pw->pw_gid);
-
- /* A paranoia check */
- if(setreuid(-1, 0) == 0)
- err(1, "unable to completely drop privileges");
-
- sp_messagex(NULL, LOG_DEBUG, "switched to user %s (uid %d, gid %d)", g_state.user, pw->pw_uid, pw->pw_gid);
- }
-
- if(geteuid() == 0)
- sp_messagex(NULL, LOG_WARNING, "running as root is NOT recommended");
-}
-
-
-static void pid_file(int write)
-{
- if(!g_state.pidfile)
- return;
-
- if(write)
- {
- FILE* f = fopen(g_state.pidfile, "w");
- if(f == NULL)
- {
- sp_message(NULL, LOG_ERR, "couldn't open pid file: %s", g_state.pidfile);
- }
- else
- {
- fprintf(f, "%d\n", (int)getpid());
-
- if(ferror(f))
- sp_message(NULL, LOG_ERR, "couldn't write to pid file: %s", g_state.pidfile);
- if(fclose(f) == EOF)
- sp_message(NULL, LOG_ERR, "couldn't write to pid file: %s", g_state.pidfile);
-
- }
-
- sp_messagex(NULL, LOG_DEBUG, "wrote pid file: %s", g_state.pidfile);
- }
-
- else
- {
- unlink(g_state.pidfile);
- sp_messagex(NULL, LOG_DEBUG, "removed pid file: %s", g_state.pidfile);
- }
-}
-
-static void connection_loop(int sock)
-{
- spthread_t* threads = NULL;
- int fd, i, x, r;
-
- /* Create the thread buffers */
- threads = (spthread_t*)calloc(g_state.max_threads, sizeof(spthread_t));
- if(!threads)
- {
- sp_messagex(NULL, LOG_CRIT, "out of memory");
- return;
- }
-
- /* Now loop and accept the connections */
- while(!sp_is_quit())
- {
- fd = accept(sock, NULL, NULL);
- if(fd == -1)
- {
- switch(errno)
- {
- case EINTR:
- case EAGAIN:
- break;
-
- case ECONNABORTED:
- sp_message(NULL, LOG_ERR, "couldn't accept a connection");
- break;
-
- default:
- sp_message(NULL, LOG_ERR, "couldn't accept a connection");
- break;
- };
-
- if(sp_is_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)
- sp_message(NULL, LOG_DEBUG, "couldn't set timeouts on incoming connection");
-
- fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
-
- /* 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)
- {
- sp_lock();
- x = threads[i].fd;
- sp_unlock();
-
- if(x == -1)
- {
- sp_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: */
- sp_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)
- {
- errno = r;
- sp_message(NULL, LOG_ERR, "couldn't create thread");
-
- write(fd, SMTP_STARTFAILED, KL(SMTP_STARTFAILED));
- shutdown(fd, SHUT_RDWR);
- close(fd);
- fd = -1;
- break;
- }
-
- sp_messagex(NULL, LOG_DEBUG, "created thread for connection");
- fd = -1;
- break;
- }
- }
-
- /* Check to make sure we have a thread */
- if(fd != -1)
- {
- sp_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;
- }
- }
-
- sp_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)
- {
- sp_lock();
- fd = threads[i].fd;
- threads[i].fd = -1;
- sp_unlock();
-
- shutdown(fd, SHUT_RDWR);
- close(fd);
- }
-
- pthread_join(threads[i].tid, NULL);
- threads[i].tid = 0;
- }
- }
-
- free(threads);
-}
-
-static spctx_t* init_thread(int fd)
-{
- spctx_t* ctx;
-
- ctx = cb_new_context();
- if(ctx)
- {
- memset(ctx, 0, sizeof(*ctx));
-
- spio_init(&(ctx->server), "SERVER");
- spio_init(&(ctx->client), "CLIENT");
-
- sp_lock();
- /* 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++;
- sp_unlock();
-
- sp_messagex(ctx, LOG_DEBUG, "processing %d on thread %x", fd, (int)pthread_self());
-
- /* Connect to the outgoing server ... */
- if(make_connections(ctx, fd) == -1)
- {
- cb_del_context(ctx);
- ctx = NULL;
- }
- }
-
- return ctx;
-}
-
-static void cleanup_context(spctx_t* ctx)
-{
- ASSERT(ctx);
-
- if(ctx->cachefile)
- {
- fclose(ctx->cachefile);
- ctx->cachefile = NULL;
- }
-
- if(ctx->cachename[0])
- {
- unlink(ctx->cachename);
- ctx->cachename[0] = 0;
- }
-
- if(ctx->recipients)
- {
- free(ctx->recipients);
- ctx->recipients = NULL;
- }
-
- if(ctx->sender)
- {
- free(ctx->sender);
- ctx->sender = NULL;
- }
-
- ctx->logline[0] = 0;
-}
-
-
-static void done_thread(spctx_t* ctx)
-{
- ASSERT(ctx);
-
- spio_disconnect(ctx, &(ctx->client));
- spio_disconnect(ctx, &(ctx->server));
-
- /* Clean up file stuff */
- cleanup_context(ctx);
- cb_del_context(ctx);
-}
-
-static void* thread_main(void* arg)
-{
- spthread_t* thread = (spthread_t*)arg;
- spctx_t* ctx = NULL;
- int processing = 0;
- int ret = 0;
- int fd;
-
- ASSERT(thread);
-
- siginterrupt(SIGINT, 1);
- siginterrupt(SIGTERM, 1);
-
- sp_lock();
- /* Get the client socket */
- fd = thread->fd;
- sp_unlock();
-
- /* Sometimes we get to this point and then quit is noted */
- if(sp_is_quit() || (ctx = init_thread(fd)) == NULL)
- {
- /* Special case. We don't have a context so clean up descriptor */
- close(fd);
-
- /* new_context() should have already logged reason */
- RETURN(-1);
- }
-
- /* call the processor */
- processing = 1;
- ret = smtp_passthru(ctx);
-
-cleanup:
-
- if(ctx)
- {
- /* Let the client know about fatal errors */
- if(!processing && ret == -1 && spio_valid(&(ctx->client)))
- spio_write_data(ctx, &(ctx->client), SMTP_STARTFAILED);
-
- done_thread(ctx);
- }
-
- /* mark this as done */
- sp_lock();
- thread->fd = -1;
- sp_unlock();
-
- return (void*)(ret == 0 ? 0 : 1);
-}
-
-static int make_connections(spctx_t* ctx, int client)
-{
- struct sockaddr_any peeraddr;
- struct sockaddr_any addr;
- struct sockaddr_any* outaddr;
- char buf[MAXPATHLEN];
- const char* outname;
-
- ASSERT(client != -1);
-
- /* Setup the incoming connection. This also fills in peeraddr for us */
- spio_attach(ctx, &(ctx->client), client, &peeraddr);
- sp_messagex(ctx, LOG_INFO, "accepted connection from: %s", ctx->client.peername);
-
- /* 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
- {
- sp_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)
- {
- sp_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(spio_connect(ctx, &(ctx->server), outaddr, outname) == -1)
- return -1;
-
- return 0;
-}
-
-/* ----------------------------------------------------------------------------------
- * SMTP HANDLING
- */
-
-static int smtp_passthru(spctx_t* ctx)
-{
- char* t;
- const char* p;
- int r, cont, ret = 0;
- unsigned int mask;
- int neterror = 0;
-
- int first_rsp = 1; /* The first 220 response from server to be filtered */
- int filter_host = 0; /* Next response is 250 hostname, which we change */
-
- /* XCLIENT is for use in access control */
- int xclient_sup = 0; /* Is XCLIENT supported? */
- int xclient_sent = 0; /* Have we sent an XCLIENT command? */
-
- ASSERT(spio_valid(&(ctx->client)) &&
- spio_valid(&(ctx->server)));
-
- #define C_LINE ctx->client.line
- #define S_LINE ctx->server.line
-
- while(!sp_is_quit())
- {
- mask = spio_select(ctx, &(ctx->client), &(ctx->server), NULL);
-
- if(mask == ~0)
- {
- neterror = 1;
- RETURN(-1);
- }
-
- /* Client has data available, read a line and process */
- if(mask & 1)
- {
- if((r = spio_read_line(ctx, &(ctx->client), SPIO_DISCARD)) == -1)
- RETURN(-1);
-
- /* Client disconnected, we're done */
- if(r == 0)
- RETURN(0);
-
- /* We don't let clients send really long lines */
- if(LINE_TOO_LONG(r))
- {
- if(spio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1)
- RETURN(-1);
-
- continue;
- }
-
- /* Only valid after EHLO or HELO commands */
- filter_host = 0;
-
- /*
- * At this point we may want to send our XCLIENT. This is a per
- * connection command.
- */
- if(xclient_sup && !xclient_sent && g_state.xclient)
- {
- sp_messagex(ctx, LOG_DEBUG, "sending XCLIENT");
-
- if(spio_write_dataf(ctx, &(ctx->server), SMTP_XCLIENT, ctx->client.peername) == -1)
- RETURN(-1);
-
- if(read_server_response(ctx) == -1)
- RETURN(-1);
-
- if(!get_successful_rsp(S_LINE, NULL))
- sp_messagex(ctx, LOG_WARNING, "server didn't accept XCLIENT");
-
- xclient_sent = 1;
- }
-
- /* Handle the DATA section via our AV checker */
- if(is_first_word(C_LINE, DATA_CMD, KL(DATA_CMD)))
- {
- /* Send back the intermediate response to the client */
- if(spio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1)
- 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(cb_check_data(ctx) == -1)
- RETURN(-1);
-
- /* Print the log out for this email */
- sp_messagex(ctx, LOG_INFO, "%s", ctx->logline);
-
- /* Done with that email */
- cleanup_context(ctx);
-
- /* Command handled */
- continue;
- }
-
- /*
- * We need our response to HELO and EHLO to be modified in order
- * to prevent complaints about mail loops
- */
- else if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)))
- {
- /* EHLO can have multline responses so we set a flag */
- filter_host = 1;
- }
-
- /*
- * We always support XCLIENT on a HELO type connection. We do this
- * for security reasons, so that a client can't get around filtering
- * by backing up one on the protocol.
- */
- else if(is_first_word(C_LINE, HELO_CMD, KL(HELO_CMD)))
- {
- sp_messagex(ctx, LOG_DEBUG, "XCLIENT support assumed");
- xclient_sup = 1;
-
- /* Filter host as with EHLO above */
- filter_host = 1;
- }
-
- /*
- * 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(C_LINE, STARTTLS_CMD, KL(STARTTLS_CMD)) ||
- is_first_word(C_LINE, BDAT_CMD, KL(BDAT_CMD)))
- {
- sp_messagex(ctx, LOG_DEBUG, "ESMTP feature not supported");
-
- if(spio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1)
- RETURN(-1);
-
- /* Command handled */
- continue;
- }
-
- /*
- * For security reasons we're not about to forward any XCLIENTs
- * from our client through. This could lead to a client using our
- * privileged IP address to change an audit trail or relay etc...
- */
- else if(is_first_word(C_LINE, XCLIENT_CMD, KL(XCLIENT_CMD)))
- {
- sp_messagex(ctx, LOG_WARNING, "client attempted use of privileged XCLIENT feature");
-
- if(spio_write_data(ctx, &(ctx->client), SMTP_NOTAUTH) == -1)
- RETURN(-1);
-
- /* Command handled */
- continue;
- }
-
- /* All other commands just get passed through to server */
- if(spio_write_data(ctx, &(ctx->server), C_LINE) == -1)
- RETURN(-1);
-
- continue;
- }
-
- /* Server has data available, read a line and forward */
- if(mask & 2)
- {
- if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1)
- RETURN(-1);
-
- if(r == 0)
- RETURN(0);
-
- if(LINE_TOO_LONG(r))
- sp_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(S_LINE, START_RSP, KL(START_RSP)))
- {
- sp_messagex(ctx, LOG_DEBUG, "intercepting initial response");
-
- if(spio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1)
- RETURN(-1);
-
- /* Command handled */
- continue;
- }
- }
-
- if((p = get_successful_rsp(S_LINE, &cont)) != NULL)
- {
- /*
- * 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)
- {
- /* Can have multi-line responses, and we want to be
- * sure to only replace the first one. */
- filter_host = 0;
-
- sp_messagex(ctx, LOG_DEBUG, "intercepting host response");
-
- if(spio_write_data(ctx, &(ctx->client),
- cont ? SMTP_EHLO_RSP : SMTP_HELO_RSP) == -1)
- RETURN(-1);
-
- /* A new email so cleanup */
- cleanup_context(ctx);
-
- continue;
- }
-
- /*
- * Filter out any EHLO responses that we can't or don't want
- * to support. For example pipelining or TLS.
- */
- if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)))
- {
- /*
- * On ESMTP connections we let the server tell us whether it
- * wants XCLIENTs or not. (In contrast to old SMTP above).
- */
- if(is_first_word(p, ESMTP_XCLIENT, KL(ESMTP_XCLIENT)))
- {
- sp_messagex(ctx, LOG_DEBUG, "XCLIENT supported");
- xclient_sup = 1;
- }
-
- 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)) ||
- is_first_word(p, ESMTP_XCLIENT, KL(ESMTP_XCLIENT)) ||
- is_first_word(p, ESMTP_XEXCH50, KL(ESMTP_XEXCH50)))
- {
- sp_messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space((char*)p));
-
- /*
- * If this is the last line in the EHLO response we need
- * to replace it with something else
- */
- if(!cont)
- {
- if(spio_write_data(ctx, &(ctx->client), SMTP_FEAT_RSP) == -1)
- RETURN(-1);
- }
-
- continue;
- }
- }
-
- /* MAIL FROM */
- if((r = check_first_word(C_LINE, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0)
- {
- t = parse_address(C_LINE + r);
- sp_add_log(ctx, "from=", t);
-
- /* Make note of the sender for later */
- ctx->sender = (char*)reallocf(ctx->sender, strlen(t) + 1);
- if(ctx->sender)
- strcpy(ctx->sender, t);
- }
-
- /* RCPT TO */
- else if((r = check_first_word(C_LINE, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0)
- {
- t = parse_address(C_LINE + r);
- sp_add_log(ctx, "to=", t);
-
- /* Make note of the recipient for later */
- r = ctx->recipients ? strlen(ctx->recipients) : 0;
- ctx->recipients = (char*)reallocf(ctx->recipients, r + strlen(t) + 2);
- if(ctx->recipients)
- {
- /* Recipients are separated by lines */
- if(r != 0)
- strcat(ctx->recipients, "\n");
- else
- ctx->recipients[0] = 0;
-
- strcat(ctx->recipients, t);
- }
- }
-
- /* RSET */
- else if(is_first_word(C_LINE, RSET_CMD, KL(RSET_CMD)))
- {
- cleanup_context(ctx);
- }
- }
-
- if(spio_write_data(ctx, &(ctx->client), S_LINE) == -1)
- RETURN(-1);
-
- continue;
- }
- }
-
-cleanup:
-
- if(!neterror && ret == -1 && spio_valid(&(ctx->client)))
- spio_write_data(ctx, &(ctx->client), SMTP_FAILED);
-
- return ret;
-}
-
-/* -----------------------------------------------------------------------------
- * SMTP PASSTHRU FUNCTIONS FOR DATA CHECK
- */
-
-static char* parse_address(char* line)
-{
- char* t;
- line = trim_start(line);
-
- /*
- * We parse out emails in the form of <blah@blah.com>
- * as well as accept other addresses.
- */
-
- if(strncmp(line, "<>", 2) == 0)
- return("<>");
-
- if(line[0] == '<')
- {
- if((t = strchr(line, '>')) != NULL)
- {
- *t = 0;
- line++;
- return line;
- }
- }
-
- return trim_end(line);
-}
-
-static const char* get_successful_rsp(const char* line, int* cont)
-{
- /*
- * We check for both '250 xxx' type replies
- * and the continued response '250-xxxx' type
- */
-
- line = trim_start(line);
-
- if(line[0] == '2' && isdigit(line[1]) && isdigit(line[2]) &&
- (line[3] == ' ' || line[3] == '-'))
- {
- if(cont)
- *cont = (line[3] == '-');
- return line + 4;
- }
-
- return NULL;
-}
-
-void sp_add_log(spctx_t* ctx, char* prefix, char* line)
-{
- char* t = ctx->logline;
- int l = strlen(t);
- int x;
-
- ASSERT(l <= SP_LOG_LINE_LEN);
-
- /* Add up necessary lengths */
- x = 2 + strlen(prefix) + strlen(line) + 1;
-
- if(l + x >= SP_LOG_LINE_LEN)
- l = SP_LOG_LINE_LEN - x;
-
- t += l;
- l = SP_LOG_LINE_LEN - l;
-
- *t = 0;
-
- if(ctx->logline[0] != 0)
- strlcat(t, ", ", l);
-
- strlcat(t, prefix, l);
-
- /* Skip initial white space */
- line = trim_start(line);
-
- strlcat(t, line, l);
-
- /* Skip later white space */
- trim_end(t);
-}
-
-int sp_read_data(spctx_t* ctx, const char** data)
-{
- int r;
-
- ASSERT(ctx);
- ASSERT(data);
-
- *data = NULL;
-
- switch(r = spio_read_line(ctx, &(ctx->client), SPIO_QUIET))
- {
- case 0:
- sp_messagex(ctx, LOG_ERR, "unexpected end of data from client");
- return -1;
- case -1:
- /* Message already printed */
- return -1;
- };
-
- if(g_state.keepalives > 0)
- {
- /*
- * During this time we're just reading from the client. If we haven't
- * had any interaction with the server recently then send something
- * to let it know we're still around.
- */
- if((ctx->server.last_action + g_state.keepalives) < time(NULL))
- do_server_noop(ctx);
- }
-
- if(ctx->_crlf && strcmp(ctx->client.line, DATA_END_SIG) == 0)
- return 0;
-
- /* Check if this line ended with a CRLF */
- ctx->_crlf = (strcmp(CRLF, ctx->client.line + (r - KL(CRLF))) == 0);
- *data = ctx->client.line;
- return r;
-}
-
-int sp_write_data(spctx_t* ctx, const char* buf, int len)
-{
- int r = 0;
-
- ASSERT(ctx);
-
- /* When a null buffer close the cache file */
- if(!buf)
- {
- if(ctx->cachefile)
- {
- if(fclose(ctx->cachefile) == EOF)
- {
- sp_message(ctx, LOG_ERR, "couldn't write to cache file: %s", ctx->cachename);
- r = -1;
- }
-
- ctx->cachefile = NULL;
- }
-
- return r;
- }
-
- /* Make sure we have a file open */
- if(!ctx->cachefile)
- {
- int tfd;
-
- /* Make sure afore mentioned file is gone */
- if(ctx->cachename[0])
- unlink(ctx->cachename);
-
- snprintf(ctx->cachename, MAXPATHLEN, "%s/%s.XXXXXX",
- g_state.directory, g_state.name);
-
- if((tfd = mkstemp(ctx->cachename)) == -1 ||
- (ctx->cachefile = fdopen(tfd, "w")) == NULL)
- {
- if(tfd != -1)
- close(tfd);
-
- sp_message(ctx, LOG_ERR, "couldn't open cache file");
- return -1;
- }
-
- fcntl(tfd, F_SETFD, fcntl(tfd, F_GETFD, 0) | FD_CLOEXEC);
- sp_messagex(ctx, LOG_DEBUG, "created cache file: %s", ctx->cachename);
- }
-
- fwrite(buf, 1, len, ctx->cachefile);
-
- if(ferror(ctx->cachefile))
- {
- sp_message(ctx, LOG_ERR, "couldn't write to cache file: %s", ctx->cachename);
- return -1;
- }
-
- return len;
-}
-
-int sp_cache_data(spctx_t* ctx)
-{
- int r, count = 0;
- const char* data;
-
- while((r = sp_read_data(ctx, &data)) != 0)
- {
- if(r < 0)
- return -1; /* Message already printed */
-
- count += r;
-
- if((r = sp_write_data(ctx, data, r)) < 0)
- return -1; /* Message already printed */
- }
-
- /* End the caching */
- if(sp_write_data(ctx, NULL, 0) < 0)
- return -1;
-
- sp_messagex(ctx, LOG_DEBUG, "wrote %d bytes to cache", count);
- return count;
-}
-
-/* Important: |date| should be at least MAX_DATE_LENGTH long */
-static void make_date(spctx_t* ctx, char* date)
-{
- size_t date_len;
- struct tm t2;
- time_t t;
-
- /* Get a basic date like: 'Wed Jun 30 21:49:08 1993' */
- if(time(&t) == (time_t)-1 ||
- !localtime_r(&t, &t2) ||
- !asctime_r(&t2, date))
- {
- sp_message(ctx, LOG_WARNING, "unable to get date for header");
- date[0] = 0;
- return;
- }
-
- trim_end(date);
- date_len = strlen(date);
-
- {
-#ifdef HAVE_TM_GMTOFF
- time_t timezone = t2.tm_gmtoff;
- const char *tzname[2] = { t2.tm_zone, t2.tm_zone };
-
- snprintf(date + date_len, MAX_DATE_LENGTH - date_len, " %+03d%02d (%s)",
- (int)(timezone / 3600), (int)(timezone % 3600),
- tzname[t2.tm_isdst ? 1 : 0]);
-#else
- /* Apparently Solaris needs this nasty hack.... */
- #define DAY_MIN (24 * HOUR_MIN)
- #define HOUR_MIN 60
- #define MIN_SEC 60
-
- struct tm gmt;
- struct tm *lt;
- int off;
-
- gmt = *gmtime(&t);
- lt = localtime(&t);
- off = (lt->tm_hour - gmt.tm_hour) * HOUR_MIN + lt->tm_min - gmt.tm_min;
-
- if (lt->tm_year < gmt.tm_year)
- off -= DAY_MIN;
- else if (lt->tm_year > gmt.tm_year)
- off += DAY_MIN;
- else if (lt->tm_yday < gmt.tm_yday)
- off -= DAY_MIN;
- else if (lt->tm_yday > gmt.tm_yday)
- off += DAY_MIN;
- if (lt->tm_sec <= gmt.tm_sec - MIN_SEC)
- off -= 1;
- else if (lt->tm_sec >= gmt.tm_sec + MIN_SEC)
- off += 1;
-
- snprintf(date + date_len, MAX_DATE_LENGTH - date_len,
- " %+03d%02d (%s)", (int)(off / HOUR_MIN), (int)(abs(off) % HOUR_MIN),
- tzname[lt->tm_isdst ? 1 : 0]);
-#endif
- }
-
- /* Break it off just in case */
- date[MAX_DATE_LENGTH - 1] = 0;
-}
-
-/* Important: |header| should be a buffer of MAX_HEADER_LENGTH */
-static int make_header(spctx_t* ctx, const char* format_str, char* header)
-{
- char date[MAX_DATE_LENGTH];
- int remaining, l;
- const char* f;
- char* p;
-
- date[0] = 0;
- remaining = MAX_HEADER_LENGTH - 1;
- p = header;
-
- /* Parse the format string and replace special characters with our data */
- for(f = format_str; *f && remaining > 0; f++)
- {
- /* A backslash escapes certain characters */
- if(f[0] == '\\' && f[1] != 0)
- {
- switch(*(++f))
- {
- case 'r':
- *p = '\r';
- break;
- case 'n':
- *p = '\n';
- break;
- case 't':
- *p = '\t';
- break;
- default:
- *p = *f;
- break;
- }
-
- ++p;
- --remaining;
- }
-
- /*
- * Special symbols:
- * %i: client's IP
- * %l: server's IP
- * %d: date
- */
- else if(f[0] == '%' && f[1] != 0)
- {
- switch(*(++f))
- {
- case 'i':
- l = strlen(ctx->client.peername);
- strncpy(p, ctx->client.peername, remaining);
- remaining -= l;
- p += l;
- break;
- case 'l':
- l = strlen(ctx->client.localname);
- strncpy(p, ctx->client.localname, remaining);
- remaining -= l;
- p += l;
- break;
- case 'd':
- if(date[0] == 0)
- make_date(ctx, date);
- l = strlen(date);
- strncpy(p, date, remaining);
- remaining -= l;
- p += l;
- break;
- case '%':
- *p = '%';
- ++p;
- break;
- default:
- sp_messagex(ctx, LOG_WARNING, "invalid header symbol: %%%c", *f);
- break;
- };
- }
-
- else
- {
- *(p++) = *f;
- remaining--;
- }
- }
-
- if((p + 1) < (header + MAX_HEADER_LENGTH))
- p[1] = 0;
- header[MAX_HEADER_LENGTH - 1] = 0;
- l = p - header;
- return l >= MAX_HEADER_LENGTH ? MAX_HEADER_LENGTH - 1 : l;
-}
-
-int sp_done_data(spctx_t* ctx)
-{
- FILE* file = 0;
- int ret = 0;
- char *line;
- char header[MAX_HEADER_LENGTH] = "";
- size_t header_len, line_len;
- ssize_t rc;
-
- ASSERT(ctx->cachename[0]); /* Must still be around */
- ASSERT(!ctx->cachefile); /* File must be closed */
-
- memset(header, 0, sizeof(header));
-
- /* Alloc line buffer */
- line_len = SP_LINE_LENGTH;
- if((line = (char *)malloc(line_len)) == NULL)
- RETURN(-1);
-
- /* Open the file */
- file = fopen(ctx->cachename, "r");
- if(file == NULL)
- {
- sp_message(ctx, LOG_ERR, "couldn't open cache file: %s", ctx->cachename);
- RETURN(-1);
- }
-
- /* Ask the server for permission to send data */
- if(spio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1)
- RETURN(-1);
-
- if(read_server_response(ctx) == -1)
- RETURN(-1);
-
- /* If server returns an error then tell the client */
- if(!is_first_word(ctx->server.line, DATA_RSP, KL(DATA_RSP)))
- {
- if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1)
- RETURN(-1);
-
- sp_messagex(ctx, LOG_DEBUG, "server refused data transfer");
-
- RETURN(0);
- }
-
- sp_messagex(ctx, LOG_DEBUG, "sending from cache file: %s", ctx->cachename);
-
- if(g_state.header)
- header_len = make_header(ctx, g_state.header, header);
-
- /* If we have to prepend the header, do it */
- if(header[0] != '\0' && g_state.header_prepend)
- {
- if(spio_write_data_raw(ctx, &(ctx->server), (unsigned char*)header, header_len) == -1 ||
- spio_write_data_raw(ctx, &(ctx->server), (unsigned char*)CRLF, KL(CRLF)) == -1)
- RETURN(-1);
- header[0] = '\0';
- }
-
- /* Transfer actual file data */
- while((rc = getline(&line, &line_len, file)) != -1)
- {
- /*
- * If the line is <CRLF>.<CRLF> we need to change it so that
- * it doesn't end the email. We do this by adding a space.
- * This won't occur much in clamsmtpd, but proxsmtpd might
- * have filters that accidentally put this in.
- */
- if(strcmp(line, "." CRLF) == 0)
- strncpy(line, ". " CRLF, SP_LINE_LENGTH);
-
- if(header[0] != '\0')
- {
- /*
- * 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(line))
- {
- if(spio_write_data_raw(ctx, &(ctx->server), (unsigned char*)header, header_len) == -1 ||
- spio_write_data_raw(ctx, &(ctx->server), (unsigned char*)CRLF, KL(CRLF)) == -1)
- RETURN(-1);
- header[0] = '\0';
- }
- }
-
- if(spio_write_data_raw(ctx, &(ctx->server), (unsigned char*)line, rc) == -1)
- RETURN(-1);
- }
-
- if(ferror(file))
- sp_message(ctx, LOG_ERR, "error reading cache file: %s", ctx->cachename);
-
- if(ferror(file) || spio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1)
- {
- /* Tell the client it went wrong */
- spio_write_data(ctx, &(ctx->client), SMTP_FAILED);
- RETURN(-1);
- }
-
- sp_messagex(ctx, LOG_DEBUG, "sent email data");
-
- /* Okay read the response from the server and echo it to the client */
- if(read_server_response(ctx) == -1)
- RETURN(-1);
-
- if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1)
- RETURN(-1);
-
-cleanup:
-
- if(line)
- free(line);
- if(file)
- fclose(file); /* read-only so no error check */
-
- return ret;
-}
-
-int sp_fail_data(spctx_t* ctx, const char* smtp_status)
-{
- char buf[256 + KL(SMTP_REJPREFIX) + KL(CRLF) + 1];
- char* t = NULL;
- int len, x;
- int pref = 0;
- int crlf = 0;
-
- if(smtp_status == NULL)
- smtp_status = SMTP_FAILED;
-
- x = strtol(smtp_status, &t, 10);
- len = strlen(smtp_status);
-
- /* We need 3 digits and CRLF at the end for a premade SMTP message */
- if(x == 0 || t != smtp_status + 3)
- pref = 1;
-
- /* We need a CRLF at the end */
- if(strcmp(smtp_status + (len - KL(CRLF)), CRLF) != 0)
- crlf = 1;
-
- if(pref || crlf)
- {
- /* Note that we truncate long lines */
- snprintf(buf, sizeof(buf), "%s%.256s%s", pref ? SMTP_REJPREFIX : "",
- smtp_status, crlf ? CRLF : "");
- buf[sizeof(buf) - 1] = 0;
- smtp_status = buf;
- }
-
- if(spio_write_data(ctx, &(ctx->client), smtp_status) == -1)
- return -1;
-
- return 0;
-}
-
-static int read_server_response(spctx_t* ctx)
-{
- int r;
-
- /* Read response line from the server */
- if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1)
- return -1;
-
- if(r == 0)
- {
- sp_messagex(ctx, LOG_ERR, "server disconnected unexpectedly");
-
- /* Tell the client it went wrong */
- spio_write_data(ctx, &(ctx->client), SMTP_FAILED);
- return 0;
- }
-
- if(LINE_TOO_LONG(r))
- sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra");
-
- return 0;
-}
-
-static void do_server_noop(spctx_t* ctx)
-{
- if(spio_valid(&(ctx->server)))
- {
- if(spio_write_data(ctx, &(ctx->server), SMTP_NOOP) != -1)
- spio_read_line(ctx, &(ctx->server), SPIO_DISCARD);
- }
-}
-
-void sp_setup_forked(spctx_t* ctx, int file)
-{
- /* Signals we've messed with */
- signal(SIGPIPE, SIG_DFL);
- signal(SIGHUP, SIG_DFL);
- signal(SIGINT, SIG_DFL);
- signal(SIGTERM, SIG_DFL);
-
- siginterrupt(SIGINT, 0);
- siginterrupt(SIGTERM, 0);
-
- if(ctx->sender)
- setenv("SENDER", ctx->sender, 1);
-
- if(ctx->recipients)
- setenv("RECIPIENTS", ctx->recipients, 1);
-
- if(file && ctx->cachename[0])
- setenv("EMAIL", ctx->cachename, 1);
-
- if(spio_valid(&(ctx->client)))
- setenv("CLIENT", ctx->client.peername, 1);
-
- if(spio_valid(&(ctx->server)))
- setenv("SERVER", ctx->server.peername, 1);
-
- setenv("TMPDIR", g_state.directory, 1);
-}
-
-
-/* ----------------------------------------------------------------------------------
- * LOGGING
- */
-
-const char kMsgDelimiter[] = ": ";
-#define MAX_MSGLEN 256
-
-static void vmessage(spctx_t* ctx, int level, int err,
- const char* msg, va_list ap)
-{
- size_t len;
- char* m;
- int e = errno;
-
- if(g_state.daemonized)
- {
- if(level >= LOG_DEBUG)
- return;
- }
- else
- {
- if(g_state.debug_level < level)
- return;
- }
-
- ASSERT(msg);
-
- len = strlen(msg) + 20 + MAX_MSGLEN;
- m = (char*)alloca(len);
-
- if(m)
- {
- if(ctx)
- snprintf(m, len, "%06X: %s%s", ctx->id, msg, err ? ": " : "");
- else
- snprintf(m, len, "%s%s", msg, err ? ": " : "");
-
- if(err)
- {
- /* strerror_r doesn't want to work for us for some reason
- strerror_r(e, m + strlen(m), MAX_MSGLEN); */
-
- sp_lock();
- strncat(m, strerror(e), len);
- sp_unlock();
- }
-
- m[len - 1] = 0;
- msg = m;
- }
-
- /* Either to syslog or stderr */
- if(g_state.daemonized)
- vsyslog(level, msg, ap);
- else
- vwarnx(msg, ap);
-}
-
-void sp_messagex(spctx_t* ctx, int level, const char* msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- vmessage(ctx, level, 0, msg, ap);
- va_end(ap);
-}
-
-void sp_message(spctx_t* ctx, int level, const char* msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- vmessage(ctx, level, 1, msg, ap);
- va_end(ap);
-}
-
-
-/* -----------------------------------------------------------------------
- * LOCKING
- */
-
-void sp_lock()
-{
- int r;
-
-#ifdef _DEBUG
- int wait = 0;
-#endif
-
-#ifdef _DEBUG
- r = pthread_mutex_trylock(&g_mutex);
- if(r == EBUSY)
- {
- wait = 1;
- sp_message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self());
- r = pthread_mutex_lock(&g_mutex);
- }
-
-#else
- r = pthread_mutex_lock(&g_mutex);
-
-#endif
-
- if(r != 0)
- {
- errno = r;
- sp_message(NULL, LOG_CRIT, "threading problem. couldn't lock mutex");
- }
-
-#ifdef _DEBUG
- else if(wait)
- {
- sp_message(NULL, LOG_DEBUG, "thread unblocked: %d", pthread_self());
- }
-#endif
-}
-
-void sp_unlock()
-{
- int r = pthread_mutex_unlock(&g_mutex);
- if(r != 0)
- {
- errno = r;
- sp_message(NULL, LOG_CRIT, "threading problem. couldn't unlock mutex");
- }
-}
-
-/* -----------------------------------------------------------------------------
- * CONFIG FILE
- */
-
-int sp_parse_option(const char* name, const char* value)
-{
- char* t;
- int ret = 0;
-
- if(strcasecmp(CFG_MAXTHREADS, name) == 0)
- {
- g_state.max_threads = strtol(value, &t, 10);
- if(*t || g_state.max_threads <= 1 || g_state.max_threads >= 1024)
- errx(2, "invalid setting: " CFG_MAXTHREADS " (must be between 1 and 1024)");
- ret = 1;
- }
-
- else if(strcasecmp(CFG_TIMEOUT, name) == 0)
- {
- g_state.timeout.tv_sec = strtol(value, &t, 10);
- if(*t || g_state.timeout.tv_sec <= 0)
- errx(2, "invalid setting: " CFG_TIMEOUT);
- ret = 1;
- }
-
- else if(strcasecmp(CFG_KEEPALIVES, name) == 0)
- {
- g_state.keepalives = strtol(value, &t, 10);
- if(*t || g_state.keepalives < 0)
- errx(2, "invalid setting: " CFG_KEEPALIVES);
- ret = 1;
- }
-
- else if(strcasecmp(CFG_XCLIENT, name) == 0)
- {
- if((g_state.xclient = strtob(value)) == -1)
- errx(2, "invalid value for " CFG_XCLIENT);
- ret = 1;
- }
-
- else if(strcasecmp(CFG_OUTADDR, name) == 0)
- {
- if(sock_any_pton(value, &(g_state.outaddr), SANY_OPT_DEFPORT(25)) == -1)
- errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", value);
- g_state.outname = value;
- ret = 1;
- }
-
- else if(strcasecmp(CFG_LISTENADDR, name) == 0)
- {
- if(sock_any_pton(value, &(g_state.listenaddr), SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1)
- errx(2, "invalid " CFG_LISTENADDR " socket name or ip: %s", value);
- g_state.listenname = value;
- ret = 1;
- }
-
- else if(strcasecmp(CFG_TRANSPARENT, name) == 0)
- {
- if((g_state.transparent = strtob(value)) == -1)
- errx(2, "invalid value for " CFG_TRANSPARENT);
- ret = 1;
- }
-
- else if(strcasecmp(CFG_DIRECTORY, name) == 0)
- {
- if(strlen(value) == 0)
- errx(2, "invalid setting: " CFG_DIRECTORY);
- g_state.directory = value;
- ret = 1;
- }
-
- else if(strcasecmp(CFG_USER, name) == 0)
- {
- if(strlen(value) == 0)
- errx(2, "invalid setting: " CFG_USER);
- g_state.user = value;
- ret = 1;
- }
-
- else if(strcasecmp(CFG_PIDFILE, name) == 0)
- {
- if(g_state.pidfile != NULL)
- sp_messagex(NULL, LOG_WARNING, "ignoring pid file specified on the command line. ");
-
- if(strlen(value) == 0)
- g_state.pidfile = NULL;
- else
- g_state.pidfile = value;
- ret = 1;
- }
-
- else if(strcasecmp(CFG_HEADER, name) == 0)
- {
- g_state.header = trim_start(value);
- if(strlen(g_state.header) == 0)
- g_state.header = NULL;
- else if(is_first_word(RCVD_HEADER, g_state.header, KL(RCVD_HEADER)))
- g_state.header_prepend = 1;
- ret = 1;
- }
-
- /* Always pass through to program */
- if(cb_parse_option(name, value) == 1)
- ret = 1;
-
- return ret;
-}
-
-static int parse_config_file(const char* configfile)
-{
- FILE* f = NULL;
- long len;
- char* p;
- char* t;
- char* n;
-
- ASSERT(configfile);
- ASSERT(!g_state._p);
-
- f = fopen(configfile, "r");
- if(f == NULL)
- {
- /* Soft errors when default config file and not found */
- if((errno == ENOENT || errno == ENOTDIR))
- return -1;
- else
- err(1, "couldn't open config file: %s", configfile);
- }
-
- /* Figure out size */
- if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1)
- err(1, "couldn't seek config file: %s", configfile);
-
- if((g_state._p = (char*)malloc(len + 2)) == NULL)
- errx(1, "out of memory");
-
- /* And read in one block */
- if(fread(g_state._p, 1, len, f) != len)
- err(1, "couldn't read config file: %s", configfile);
-
- fclose(f);
- sp_messagex(NULL, LOG_DEBUG, "read config file: %s", configfile);
-
- /* Double null terminate the data */
- p = g_state._p;
- p[len] = '\n';
- p[len + 1] = 0;
-
- n = g_state._p;
-
- /* Go through lines and process them */
- while((t = strchr(n, '\n')) != NULL)
- {
- *t = 0;
- p = n; /* Do this before cleaning below */
- n = t + 1;
-
- p = trim_start(p);
-
- /* Comments and empty lines */
- if(*p == 0 || *p == '#')
- continue;
-
- /* Look for the break between name: value */
- t = strchr(p, ':');
- if(t == NULL)
- errx(2, "invalid config line: %s", p);
-
- /* Null terminate and split value part */
- *t = 0;
- t++;
-
- t = trim_space(t);
- p = trim_space(p);
-
- /* Pass it through our options parsers */
- if(sp_parse_option(p, t) == 0)
-
- /* If not recognized then it's invalid */
- errx(2, "invalid config line: %s", p);
-
- sp_messagex(NULL, LOG_DEBUG, "parsed option: %s: %s", p, t);
- }
-
- return 0;
-}
-