summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorStef Walter <stef@thewalter.net>2025-01-29 06:06:53 +0100
committerStef Walter <stef@thewalter.net>2025-01-29 06:27:38 +0100
commit9c21d3312ff12f6d3f56424875ddf13b0c165952 (patch)
tree42ed381d1c2a86a79fab61f2c8f00d8fb3fdf1a9 /common
parent65d3956a9befdca748cbbd2993bf0707af021886 (diff)
Fix up the build
This copies the last version of the 1.10 clamsmtp common/ files into this project. They used to be shared with the proxsmtp project but that project has since diverged.
Diffstat (limited to 'common')
-rw-r--r--common/compat.c532
-rw-r--r--common/compat.h143
-rw-r--r--common/smtppass.c2102
-rw-r--r--common/smtppass.h283
-rw-r--r--common/sock_any.c385
-rw-r--r--common/sock_any.h90
-rw-r--r--common/spio.c643
-rw-r--r--common/sppriv.h76
-rw-r--r--common/stringx.c174
-rw-r--r--common/stringx.h53
-rw-r--r--common/usuals.h78
11 files changed, 4559 insertions, 0 deletions
diff --git a/common/compat.c b/common/compat.c
new file mode 100644
index 0000000..dcd2bb8
--- /dev/null
+++ b/common/compat.c
@@ -0,0 +1,532 @@
+/*
+ * Copyright (c) 2004-2005, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ *
+ * PORTIONS FROM OPENBSD: -------------------------------------------------
+ *
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */
+
+
+
+#include "usuals.h"
+#include "compat.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#ifndef HAVE_REALLOCF
+
+void* reallocf(void* ptr, size_t size)
+{
+ void* ret = realloc(ptr, size);
+
+ if(!ret && size)
+ free(ptr);
+
+ return ret;
+}
+
+#endif
+
+#ifndef HAVE_STRLWR
+char* strlwr(char* s)
+{
+ char* t = s;
+ while(*t)
+ {
+ *t = tolower(*t);
+ t++;
+ }
+ return s;
+}
+#endif
+
+#ifndef HAVE_STRUPR
+char* strupr(char* s)
+{
+ char* t = s;
+ while(*t)
+ {
+ *t = toupper(*t);
+ t++;
+ }
+ return s;
+}
+#endif
+
+#ifndef HAVE_STRLCPY
+
+size_t strlcpy(char* dst, const char* src, size_t siz)
+{
+ char* d = dst;
+ const char* s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if(n != 0 && --n != 0)
+ {
+ do
+ {
+ if((*d++ = *s++) == 0)
+ break;
+ }
+ while(--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if(n == 0)
+ {
+ if(siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return s - src - 1; /* count does not include NUL */
+}
+
+#endif
+
+#ifndef HAVE_STRLCAT
+
+size_t strlcat(char* dst, const char* src, size_t siz)
+{
+ char* d = dst;
+ const char* s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while(n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if(n == 0)
+ return dlen + strlen(s);
+ while(*s != '\0')
+ {
+ if(n != 1)
+ {
+ *d++ = *s;
+ n--;
+ }
+
+ s++;
+ }
+
+ *d = '\0';
+
+ return dlen + (s - src); /* count does not include NUL */
+}
+
+#endif
+
+#ifndef HAVE_STRCASESTR
+
+const char* strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if((c = *find++) != 0)
+ {
+ c = tolower((unsigned char)c);
+ len = strlen(find);
+ do
+ {
+ do
+ {
+ if((sc = *s++) == 0)
+ return (NULL);
+ }
+ while((char)tolower((unsigned char)sc) != c);
+ }
+ while (strncasecmp(s, find, len) != 0);
+ s--;
+ }
+ return((const char*)s);
+}
+
+#endif
+
+#ifndef HAVE_SETENV
+
+#include <stdio.h>
+
+int setenv(const char* name, const char* value, int overwrite)
+{
+ char* t;
+ int r;
+ if(getenv(name) && !overwrite)
+ return 0;
+ t = (char*)malloc((strlen(name) + strlen(value) + 2) * sizeof(char));
+ if(!t) return -1;
+ sprintf(t, "%s=%s", name, value);
+ r = putenv(t);
+ if(r != 0)
+ free(t);
+ return r;
+}
+
+#endif
+
+#ifndef HAVE_DAEMON
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+
+int daemon(int nochdir, int noclose)
+{
+ struct sigaction osa, sa;
+ int oerrno, fd, osa_ok;
+ pid_t newgrp;
+
+ /* A SIGHUP may be thrown when the parent exits below. */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ osa_ok = sigaction(SIGHUP, &sa, &osa);
+
+ switch(fork())
+ {
+ case -1:
+ return -1;
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ newgrp = setsid();
+ oerrno = errno;
+ if(osa_ok != -1)
+ sigaction(SIGHUP, &osa, NULL);
+ if(newgrp == -1)
+ {
+ errno = oerrno;
+ return -1;
+ }
+ if(!nochdir)
+ chdir("/");
+ if(!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1)
+ {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if(fd > 2)
+ close(fd);
+ }
+ return 0;
+
+}
+
+#endif
+
+#ifndef HAVE_GETLINE
+
+ssize_t getline(char** lineptr, size_t* n, FILE* stream)
+{
+ return getdelim(lineptr, n, '\n', stream);
+}
+
+#endif
+
+#ifndef HAVE_GETDELIM
+
+ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream)
+{
+ size_t written = 0;
+ int allocated, ch;
+ ssize_t ret = -1;
+ char* p;
+
+ if(!n || !lineptr || !stream)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Track whether we allocated this or not */
+ allocated = !(*lineptr);
+
+#ifdef HAVE_FLOCKFILE
+ /* Affects performance on Solaris. */
+ flockfile(stream);
+#endif
+
+ for(;;)
+ {
+ if(!*lineptr)
+ *n = 0;
+
+ /* Reallocate if we need more space */
+ if(written == *n - 1)
+ {
+ *n = *n ? *n * 2 : 256;
+ if(!(p = (char*)realloc(*lineptr, *n)))
+ {
+ if(allocated && *lineptr)
+ free(*lineptr);
+ errno = ENOMEM;
+ ret = -1;
+ goto finally;
+ }
+ *lineptr = p;
+ }
+
+ while(written < *n - 1)
+ {
+#ifdef HAVE_FLOCKFILE
+ ch = getc_unlocked(stream);
+#else
+ ch = fgetc(stream);
+#endif
+
+ if(ferror(stream))
+ {
+ if(allocated && *lineptr)
+ free(*lineptr);
+ ret = -1;
+ goto finally;
+ }
+
+ if(ch != EOF)
+ (*lineptr)[written++] = ch;
+
+ /* Done, null terminate, return */
+ if(ch == EOF || ch == delim)
+ {
+ (*lineptr)[written] = 0;
+ ret = written ? written : -1;
+ goto finally;
+ }
+ }
+ }
+finally:
+
+#ifdef HAVE_FLOCKFILE
+ funlockfile(stream);
+#endif
+ return ret;
+}
+
+#endif
+
+#ifndef HAVE_ERR_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+extern char** __argv;
+
+static const char* calc_prog_name()
+{
+ static char prognamebuf[256];
+ static int prepared = 0;
+
+ if(!prepared)
+ {
+ const char* beg = strrchr(__argv[0], '\\');
+ const char* temp = strrchr(__argv[0], '/');
+ beg = (beg > temp) ? beg : temp;
+ beg = (beg) ? beg + 1 : __argv[0];
+
+ temp = strrchr(__argv[0], '.');
+ temp = (temp > beg) ? temp : __argv[0] + strlen(__argv[0]);
+
+ if((temp - beg) > 255)
+ temp = beg + 255;
+
+ strncpy(prognamebuf, beg, temp - beg);
+ prognamebuf[temp - beg] = 0;
+ prepared = 1;
+ }
+
+ return prognamebuf;
+}
+
+static FILE* err_file; /* file to use for error output */
+
+/*
+ * This is declared to take a `void *' so that the caller is not required
+ * to include <stdio.h> first. However, it is really a `FILE *', and the
+ * manual page documents it as such.
+ */
+void err_set_file(void *fp)
+{
+ if (fp)
+ err_file = fp;
+ else
+ err_file = stderr;
+}
+
+void err(int eval, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ verrc(eval, errno, fmt, ap);
+ va_end(ap);
+}
+
+void verr(int eval, const char *fmt, va_list ap)
+{
+ verrc(eval, errno, fmt, ap);
+}
+
+void errc(int eval, int code, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ verrc(eval, code, fmt, ap);
+ va_end(ap);
+}
+
+void verrc(int eval, int code, const char *fmt, va_list ap)
+{
+ if (err_file == 0)
+ err_set_file((FILE *)0);
+ fprintf(err_file, "%s: ", calc_prog_name());
+ if (fmt != NULL) {
+ vfprintf(err_file, fmt, ap);
+ fprintf(err_file, ": ");
+ }
+ fprintf(err_file, "%s\n", strerror(code));
+ exit(eval);
+}
+
+void errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ verrx(eval, fmt, ap);
+ va_end(ap);
+}
+
+void verrx(int eval, const char *fmt, va_list ap)
+{
+ if (err_file == 0)
+ err_set_file((FILE *)0);
+ fprintf(err_file, "%s: ", calc_prog_name());
+ if (fmt != NULL)
+ vfprintf(err_file, fmt, ap);
+ fprintf(err_file, "\n");
+ exit(eval);
+}
+
+void warn(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vwarnc(errno, fmt, ap);
+ va_end(ap);
+}
+
+void vwarn(const char *fmt, va_list ap)
+{
+ vwarnc(errno, fmt, ap);
+}
+
+void warnc(int code, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vwarnc(code, fmt, ap);
+ va_end(ap);
+}
+
+void vwarnc(int code, const char *fmt, va_list ap)
+{
+ if (err_file == 0)
+ err_set_file((FILE *)0);
+ fprintf(err_file, "%s: ", calc_prog_name());
+ if (fmt != NULL)
+ {
+ vfprintf(err_file, fmt, ap);
+ fprintf(err_file, ": ");
+ }
+ fprintf(err_file, "%s\n", strerror(code));
+}
+
+void warnx(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+}
+
+void vwarnx(const char *fmt, va_list ap)
+{
+ if(err_file == 0)
+ err_set_file((FILE*)0);
+ fprintf(err_file, "%s: ", calc_prog_name());
+ if(fmt != NULL)
+ vfprintf(err_file, fmt, ap);
+ fprintf(err_file, "\n");
+}
+
+#endif
+
diff --git a/common/compat.h b/common/compat.h
new file mode 100644
index 0000000..6675c77
--- /dev/null
+++ b/common/compat.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef _COMPAT_H_
+#define _COMPAT_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#ifndef HAVE_STDARG_H
+#error ERROR: Must have a working stdarg.h header
+#else
+#include <stdarg.h>
+#endif
+
+#ifdef WITH_DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef HAVE_REALLOCF
+void* reallocf(void* p, size_t sz);
+#endif
+
+#include <pthread.h>
+
+#if HAVE_ERR_MUTEX == 1
+# define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK_NP
+#else
+# if HAVE_ERR_MUTEX == 2
+# define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK
+# else
+# undef MUTEX_TYPE
+# endif
+#endif
+
+#ifndef HAVE_STRLWR
+char* strlwr(char* s);
+#endif
+
+#ifndef HAVE_STRUPR
+char* strupr(char* s);
+#endif
+
+#ifndef HAVE_STRLCAT
+size_t strlcat(char *dst, const char *src, size_t size);
+#endif
+
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *dst, const char *src, size_t size);
+#endif
+
+#ifndef HAVE_STRCASESTR
+const char* strcasestr(const char *s, const char *find);
+#endif
+
+#ifndef HAVE_SETENV
+int setenv(const char* name, const char* value, int overwrite);
+#endif
+
+#ifndef HAVE_DAEMON
+int daemon(int nochdir, int noclose);
+#endif
+
+#ifndef HAVE_GETLINE
+ssize_t getline(char** lineptr, size_t* n, FILE* stream);
+#endif
+
+#ifndef HAVE_GETDELIM
+ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream);
+#endif
+
+#ifdef HAVE_ERR_H
+#include <err.h>
+#else
+#include <stdarg.h>
+void err_set_file(void *fp);
+void err_set_exit(void (*ef)(int));
+void err(int eval, const char *fmt, ...);
+void verr(int eval, const char *fmt, va_list ap);
+void errc(int eval, int code, const char *fmt, ...);
+void verrc(int eval, int code, const char *fmt, va_list ap);
+void errx(int eval, const char *fmt, ...);
+void verrx(int eval, const char *fmt, va_list ap);
+void warn(const char *fmt, ...);
+void vwarn(const char *fmt, va_list ap);
+void warnc(int code, const char *fmt, ...);
+void vwarnc(int code, const char *fmt, va_list ap);
+void warnx(const char *fmt, ...);
+void vwarnx(const char *fmt, va_list ap);
+#endif
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifndef _PATH_DEVNULL
+#define _PATH_DEVNULL "/dev/null"
+#endif
+
+#ifndef _PATH_TMP
+#define _PATH_TMP "/tmp"
+#endif
+
+#endif /* _COMPAT_H_ */
diff --git a/common/smtppass.c b/common/smtppass.c
new file mode 100644
index 0000000..d9be1ba
--- /dev/null
+++ b/common/smtppass.c
@@ -0,0 +1,2102 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stefan Walter <stef@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_RSET "RSET" 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 XFORWARD_CMD "XFORWARD"
+
+#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_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 char* parse_xforward(char* line, const char* part);
+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 busy 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);
+ }
+
+ sp_messagex(NULL, LOG_DEBUG, "cleaning up completed thread");
+ 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;
+ }
+
+ if(ctx->xforwardaddr)
+ {
+ free(ctx->xforwardaddr);
+ ctx->xforwardaddr = NULL;
+ }
+
+ if(ctx->xforwardhelo)
+ {
+ free(ctx->xforwardhelo);
+ ctx->xforwardhelo = 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;
+ }
+
+ /* 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
+ /* IPv6 loopback? */
+ 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
+
+ /* Not transparent proxy or loopback */
+ else
+ {
+ /* Resolve any DNS name again */
+ if(sock_any_pton(g_state.outname, &addr, SANY_OPT_DEFPORT(25)) != -1)
+ memcpy(&(g_state.outaddr), &addr, sizeof(g_state.outaddr));
+ else
+ sp_messagex(ctx, LOG_WARNING, "couldn't resolve " CFG_OUTADDR ": %s", g_state.outname);
+ }
+
+ /* Reparse name if needed */
+ 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 scan 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 (that the server accepted) */
+ 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 (that the server accepted) */
+ 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);
+ }
+ }
+
+ /*
+ * If the client sends an XFORWARD, and the server accepted it,
+ * we store address for use in our forked process environment
+ * variables (see sp_setup_forked).
+ */
+ else if(is_first_word(C_LINE, XFORWARD_CMD, KL(XFORWARD_CMD)))
+ {
+ if((t = parse_xforward (C_LINE + KL(XFORWARD_CMD), "ADDR")))
+ {
+ ctx->xforwardaddr = (char*)reallocf(ctx->xforwardaddr, strlen(t) + 1);
+ if(ctx->xforwardaddr)
+ strcpy(ctx->xforwardaddr, t);
+ }
+
+ if((t = parse_xforward (C_LINE + KL(XFORWARD_CMD), "HELO")))
+ {
+ ctx->xforwardhelo = (char*)reallocf(ctx->xforwardhelo, strlen(t) + 1);
+ if(ctx->xforwardhelo)
+ strcpy(ctx->xforwardhelo, 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 char* parse_xforward(char* line, const char* part)
+{
+ char* t;
+ char* e;
+
+ t = strcasestr(line, part);
+ if(!t)
+ return NULL;
+
+ /* equals sign and surrounding */
+ t = trim_start(t + strlen(part));
+ if(*t != '=')
+ return NULL;
+ t = trim_start(t + 1);
+ if(!*t)
+ return NULL;
+
+ /* Find the end of the thingy */
+ if(*t == '[')
+ {
+ t++;
+ e = strchr(t, ']');
+ }
+ else
+ {
+ e = t + strcspn(t, " \t");
+ }
+
+ if(!e)
+ return NULL;
+ *e = 0;
+ return t;
+}
+
+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, const char *headertmpl)
+{
+ FILE* file = 0;
+ int ret = 0;
+ char *line;
+ char header[MAX_HEADER_LENGTH] = "";
+ size_t header_len, line_len;
+ int header_prepend = 0;
+ 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(headertmpl)
+ {
+ header_len = make_header(ctx, headertmpl, header);
+ if(is_first_word(RCVD_HEADER, header, KL(RCVD_HEADER)))
+ header_prepend = 1;
+ }
+
+
+ /* If we have to prepend the header, do it */
+ if(header[0] != '\0' && 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;
+
+ /* Tell the server to forget about the current message */
+ if(spio_write_data(ctx, &(ctx->server), SMTP_RSET) == -1 ||
+ read_server_response(ctx) == -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(ctx->xforwardaddr)
+ setenv("REMOTE", ctx->xforwardaddr, 1);
+
+ if(ctx->xforwardhelo)
+ setenv("REMOTE_HELO", ctx->xforwardhelo, 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 1024
+
+static void vmessage(spctx_t* ctx, int level, int err,
+ const char* msg, va_list ap)
+{
+ char buf[MAX_MSGLEN];
+ int e = errno;
+
+ if(g_state.daemonized)
+ {
+ if(level >= LOG_DEBUG)
+ return;
+ }
+ else
+ {
+ if(g_state.debug_level < level)
+ return;
+ }
+
+ ASSERT(msg);
+
+ if(ctx)
+ snprintf(buf, MAX_MSGLEN, "%06X: %s%s", ctx->id, msg, err ? ": " : "");
+ else
+ snprintf(buf, MAX_MSGLEN, "%s%s", msg, err ? ": " : "");
+
+ if(err)
+ {
+ /* strerror_r doesn't want to work for us for some reason
+ len = strlen(buf);
+ strerror_r(e, buf + len, MAX_MSGLEN - len); */
+
+ sp_lock();
+ strncat(buf, strerror(e), MAX_MSGLEN);
+ sp_unlock();
+ }
+
+ /* As a precaution */
+ buf[MAX_MSGLEN - 1] = 0;
+
+ /* Either to syslog or stderr */
+ if(g_state.daemonized)
+ vsyslog(level, buf, ap);
+ else
+ vwarnx(buf, 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;
+ }
+
+ /* 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;
+}
+
diff --git a/common/smtppass.h b/common/smtppass.h
new file mode 100644
index 0000000..9733447
--- /dev/null
+++ b/common/smtppass.h
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __SMTPPASS_H__
+#define __SMTPPASS_H__
+
+/* Forward declarations */
+struct sockaddr_any;
+struct spctx;
+
+/* -----------------------------------------------------------------------------
+ * BUFFERED MULTIPLEXING IO
+ *
+ * This isn't meant to be a replacement library for all sorts of IO
+ * only things that are currently used go here.
+ */
+
+/*
+ * A generous maximum line length. It needs to be longer than
+ * a full path on this system can be, because we pass the file
+ * name to clamd.
+ */
+
+#if 2000 > MAXPATHLEN
+ #define SP_LINE_LENGTH 2000
+#else
+ #define SP_LINE_LENGTH (MAXPATHLEN + 128)
+#endif
+
+typedef struct spio
+{
+ int fd; /* The file descriptor wrapped */
+ const char* name; /* The name for logging */
+ time_t last_action; /* Time of last action on descriptor */
+ char peername[MAXPATHLEN]; /* Name of the peer on other side of socket */
+ char localname[MAXPATHLEN]; /* Address where we accepted the connection */
+
+ /* Internal use only */
+ char line[SP_LINE_LENGTH];
+ char* _nx;
+ size_t _ln;
+}
+spio_t;
+
+#define spio_valid(io) ((io) && (io)->fd != -1)
+
+/* Setup the io structure (allocated elsewhere) */
+void spio_init(spio_t* io, const char* name);
+
+/* Attach an open descriptor to a socket, optionally returning the peer */
+void spio_attach(struct spctx* ctx, spio_t* io, int fd, struct sockaddr_any* peer);
+
+/* Connect and disconnect from sockets */
+int spio_connect(struct spctx* ctx, spio_t* io, const struct sockaddr_any* sany, const char* addrname);
+void spio_disconnect(struct spctx* ctx, spio_t* io);
+
+#define SPIO_TRIM 0x00000001
+#define SPIO_DISCARD 0x00000002
+#define SPIO_QUIET 0x00000004
+
+/* Read a line from a socket. Use options above. Line
+ * will be found in io->line */
+int spio_read_line(struct spctx* ctx, spio_t* io, int opts);
+
+/* Write data to socket (must supply line endings if needed).
+ * Guaranteed to accept all data or fail. */
+int spio_write_data(struct spctx* ctx, spio_t* io, const char* data);
+int spio_write_dataf(struct spctx* ctx, spio_t* io, const char* fmt, ...);
+int spio_write_data_raw(struct spctx* ctx, spio_t* io, const unsigned char* buf, int len);
+
+/* Empty the given socket */
+void spio_read_junk(struct spctx* sp, spio_t* io);
+
+/* Pass up to 31 spio_t*, followed by NULL. Returns bitmap of ready for reading */
+unsigned int spio_select(struct spctx* ctx, ...);
+
+
+/* -----------------------------------------------------------------------------
+ * SMTP PASS THROUGH FUNCTIONALITY
+ */
+
+/* Log lines have to be under roughly 900 chars otherwise
+ * they get truncated by syslog. */
+#define SP_LOG_LINE_LEN 768
+
+typedef struct spctx
+{
+ unsigned int id; /* Identifier for the connection */
+
+ spio_t client; /* Connection to client */
+ spio_t server; /* Connection to server */
+
+ FILE* cachefile; /* The file handle for the cached file */
+ char cachename[MAXPATHLEN]; /* The name of the file that we cache into */
+ char logline[SP_LOG_LINE_LEN]; /* Log line */
+
+ char* sender; /* The email of the sender */
+ char* recipients; /* The email of the recipients */
+ char* xforwardaddr; /* The IP address proxied for */
+ char* xforwardhelo; /* The HELO/EHLO proxied for */
+
+ int _crlf; /* Private data */
+}
+spctx_t;
+
+/*
+ * sp_init initializes the SMTP Pass-Through functionality
+ * The name passed is the name of the app
+ */
+void sp_init(const char* name);
+
+/*
+ * This starts up the smtp pass thru program. It will call
+ * the cb_* functions as appropriate.
+ */
+int sp_run(const char* configfile, const char* pidfile, int dbg_level);
+
+/*
+ * Mark the application as shutting down.
+ * A signal will interupt most IO.
+ */
+void sp_quit();
+
+/*
+ * Check if the application has been marked to quit.
+ * Useful for checking after interupted IO.
+ */
+int sp_is_quit();
+
+/*
+ * Called to cleanup SMTP Pass-Through functionality just
+ * before the application quits.
+ */
+void sp_done();
+
+/*
+ * clamsmtpd used to accept command line args. In order to
+ * process those args it needs to send the values to the
+ * config file routines. This is how it does that.
+ */
+#ifdef SP_LEGACY_OPTIONS
+int sp_parse_option(const char* name, const char* option);
+#endif
+
+
+/*
+ * The following functions are to be called from within
+ * the spc_check_data function.
+ */
+
+/*
+ * Adds a piece of info to the log line
+ */
+void sp_add_log(spctx_t* ctx, char* prefix, char* line);
+
+/*
+ * Reads a line of DATA from client. Or less than a line if
+ * line is longer than LINE_LENGTH. No trimming or anything
+ * is done on the read line. This will end automatically
+ * when <CRLF>.<CRLF> is detected (in which case 0 will
+ * be returned). The data is returned in data.
+ */
+int sp_read_data(spctx_t* ctx, const char** data);
+
+/*
+ * Writes a line (or piece) of data to a file buffer which is
+ * later sent to the client using sp_done_data. Calling it with
+ * a NULL buffer closes the cache file. Guaranteed to accept
+ * all data given to it or fail.
+ */
+int sp_write_data(spctx_t* ctx, const char* buf, int buflen);
+
+/*
+ * Sends all DATA from the client into the cache. The cache
+ * file is then available (in spctx_t->cachename) for use.
+ */
+int sp_cache_data(spctx_t* ctx);
+
+/*
+ * Sends the data in file buffer off to server. This is
+ * completes a successful mail transfer.
+ */
+int sp_done_data(spctx_t* ctx, const char *header);
+
+/*
+ * Fails the data, deletes any temp data, and sends given
+ * status to client or if NULL then SMTP_DATAFAILED
+ */
+int sp_fail_data(spctx_t* ctx, const char* smtp_status);
+
+/*
+ * Setup the environment with context info. This is useful
+ * if you're going to fork another process. Be sure to exec
+ * soon after to prevent the strings from going out of scope.
+ */
+void sp_setup_forked(spctx_t* ctx, int file);
+
+/*
+ * Log a message. levels are syslog levels. Syntax is just
+ * like printf etc.. Can specify a ctx of NULL in which case
+ * no connection prefix is prepended.
+ */
+void sp_message(spctx_t* ctx, int level, const char* msg, ...);
+void sp_messagex(spctx_t* ctx, int level, const char* msg, ...);
+
+/*
+ * Lock or unlock the main mutex around thread common
+ * functionality.
+ */
+void sp_lock();
+void sp_unlock();
+
+
+/* -----------------------------------------------------------------------------
+ * CALLBACKS IMPLMEMENTED BY PROGRAM
+ */
+
+/*
+ * The following functions create and destroy contexts for a new
+ * thread. Perform initialization in there and return a spctx
+ * structure for the thread to use. Return NULL failed. Be sure
+ * to log message.
+ */
+extern spctx_t* cb_new_context();
+extern void cb_del_context(spctx_t* ctx);
+
+/*
+ * Called when the data section of an email is being transferred.
+ * Once inside this function you can transfer files using
+ * sp_read_data, sp_write_data.
+ *
+ * After scanning or figuring out the status call either
+ * sp_done_data or sp_fail_data. Most failures should be handled
+ * internally using sp_fail_data, unless it's an out of memory
+ * condition, or sp_fail_data failed.
+ */
+extern int cb_check_data(spctx_t* ctx);
+
+/*
+ * Parse options from the config file. The memory for these
+ * options will stay around until sp_done is called. Return 0
+ * for unrecognized options, 1 for recognized, and quit the
+ * program for invalid.
+ */
+extern int cb_parse_option(const char* name, const char* value);
+
+
+#endif /* __SMTPPASS_H__ */
diff --git a/common/sock_any.c b/common/sock_any.c
new file mode 100644
index 0000000..82501c1
--- /dev/null
+++ b/common/sock_any.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "sock_any.h"
+
+#include <arpa/inet.h>
+
+#define LOCALHOST_ADDR 0x7F000001
+
+int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts)
+{
+ size_t l;
+ char buf[256];
+ char* t;
+ char* t2;
+ int defport = (opts & 0xFFFF);
+
+ memset(any, 0, sizeof(*any));
+
+ /* Just a port? */
+ do
+ {
+ #define PORT_CHARS "0123456789"
+ #define PORT_MIN 1
+ #define PORT_MAX 5
+
+ int port = 0;
+
+ l = strspn(addr, PORT_CHARS);
+ if(l < PORT_MIN || l > PORT_MAX || addr[l] != 0)
+ break;
+
+ port = strtol(addr, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+
+ any->s.in.sin_port = htons(port);
+
+ /* Fill in the type based on defaults */
+#ifdef HAVE_INET6
+ if(opts & SANY_OPT_DEFINET6)
+ any->s.in.sin_family = AF_INET6;
+ else
+#endif
+ any->s.in.sin_family = AF_INET;
+
+ /* Fill in the address based on defaults */
+ if(opts & SANY_OPT_DEFLOCAL)
+ {
+#ifdef HAVE_INET6
+ if(opts & SANY_OPT_DEFINET6)
+ memcpy(&(any->s.in.sin6_addr), &in6addr_loopback, sizeof(struct in6_addr));
+ else
+#endif
+ any->s.in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ }
+
+ /*
+ * Note the 'any' option is the default since we zero out
+ * the entire structure above.
+ */
+
+ any->namelen = sizeof(any->s.in);
+ return AF_INET;
+ }
+ while(0);
+
+ /* Look and see if we can parse an ipv4 address */
+ do
+ {
+ #define IPV4_PORT_CHARS
+ #define IPV4_CHARS "0123456789."
+ #define IPV4_MIN 3
+ #define IPV4_MAX 21
+
+ int port = 0;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l < IPV4_MIN || l > IPV4_MAX)
+ break;
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ l = strspn(buf, IPV4_CHARS);
+ if(l < IPV4_MIN)
+ break;
+
+ /* Either end of string or port */
+ if(buf[l] != 0 && buf[l] != ':')
+ break;
+
+ /* Get the port out */
+ if(buf[l] != 0)
+ {
+ t = buf + l + 1;
+ buf[l] = 0;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+ }
+
+ any->s.in.sin_family = AF_INET;
+ any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port));
+
+ if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0)
+ break;
+
+ any->namelen = sizeof(any->s.in);
+ return AF_INET;
+ }
+ while(0);
+
+#ifdef HAVE_INET6
+ do
+ {
+ #define IPV6_CHARS "0123456789:"
+ #define IPV6_MIN 3
+ #define IPV6_MAX 51
+
+ int port = -1;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l < IPV6_MIN || l > IPV6_MAX)
+ break;
+
+ /* If it starts with a '[' then we can get port */
+ if(buf[0] == '[')
+ {
+ port = 0;
+ addr++;
+ }
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ l = strspn(buf, IPV6_CHARS);
+ if(l < IPV6_MIN)
+ break;
+
+ /* Either end of string or port */
+ if(buf[l] != 0)
+ {
+ /* If had bracket, then needs to end with a bracket */
+ if(port != 0 || buf[l] != ']')
+ break;
+
+ /* Get the port out */
+ t = buf + l + 1;
+
+ if(*t = ':')
+ t++;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t, 10);
+ if(*t || port <= 0 || port >= 65536)
+ break;
+ }
+
+ any->s.in6.sin6_family = AF_INET6;
+ any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port);
+
+ if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0)
+ break;
+
+ any->namelen = sizeof(any->s.in6);
+ return AF_INET6;
+ }
+ while(0);
+#endif
+
+ /* A unix socket path */
+ do
+ {
+ /* No colon and must have a path component */
+ if(strchr(addr, ':') || !strchr(addr, '/'))
+ break;
+
+ l = strlen(addr);
+ if(l >= sizeof(any->s.un.sun_path))
+ break;
+
+ any->s.un.sun_family = AF_UNIX;
+ strcpy(any->s.un.sun_path, addr);
+
+ any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l);
+ return AF_UNIX;
+ }
+ while(0);
+
+ /* A DNS name and a port? */
+ do
+ {
+ struct addrinfo* res;
+ int port = 0;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l >= 255 || !isalpha(addr[0]))
+ break;
+
+ /* Some basic illegal character checks */
+ if(strcspn(addr, " /\\") != l)
+ break;
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ t = strchr(buf, ':');
+ if(t)
+ {
+ *t = 0;
+ t++;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+ }
+
+ /* Try and resolve the domain name */
+ if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res)
+ break;
+
+ memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr));
+ any->namelen = res->ai_addrlen;
+ freeaddrinfo(res);
+
+ port = htons((unsigned short)(port <= 0 ? defport : port));
+
+ switch(any->s.a.sa_family)
+ {
+ case PF_INET:
+ any->s.in.sin_port = port;
+ break;
+#ifdef HAVE_INET6
+ case PF_INET6:
+ any->s.in6.sin6_port = port;
+ break;
+#endif
+ };
+
+ return any->s.a.sa_family;
+ }
+ while(0);
+
+ return -1;
+}
+
+int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts)
+{
+ int len = 0;
+ int port = 0;
+
+ switch(any->s.a.sa_family)
+ {
+ case AF_UNIX:
+ len = strlen(any->s.un.sun_path);
+ if(addrlen < len + 1)
+ {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ strcpy(addr, any->s.un.sun_path);
+ break;
+
+ case AF_INET:
+ if(inet_ntop(any->s.a.sa_family, &(any->s.in.sin_addr), addr, addrlen) == NULL)
+ return -1;
+ port = ntohs(any->s.in.sin_port);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if(inet_ntop(any->s.a.sa_family, &(any->s.in6.sin6_addr), addr, addrlen) == NULL)
+ return -1;
+ port = ntohs(any->s.in6.sin6_port);
+ break;
+#endif
+
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if(!(opts & SANY_OPT_NOPORT) && port != 0)
+ {
+ strncat(addr, ":", addrlen);
+ addr[addrlen - 1] = 0;
+
+ len = strlen(addr);
+ addr += len;
+ addrlen -= len;
+
+ snprintf(addr, addrlen, "%d", port);
+ }
+
+ return 0;
+}
+
+int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts)
+{
+ if(a1->s.a.sa_family != a2->s.a.sa_family)
+ return -1;
+
+ switch(a1->s.a.sa_family)
+ {
+ case AF_UNIX:
+ return strcmp(a1->s.un.sun_path, a2->s.un.sun_path);
+
+ case AF_INET:
+ if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0)
+ return -1;
+ if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port)
+ return -1;
+ return 0;
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0)
+ return -1;
+ if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port)
+ return -1;
+ return 0;
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+}
diff --git a/common/sock_any.h b/common/sock_any.h
new file mode 100644
index 0000000..77c3841
--- /dev/null
+++ b/common/sock_any.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __SOCK_ANY_H__
+#define __SOCK_ANY_H__
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+
+struct sockaddr_any
+{
+ union _sockaddr_any
+ {
+ /* The header */
+ struct sockaddr a;
+
+ /* The different types */
+ struct sockaddr_un un;
+ struct sockaddr_in in;
+#ifdef HAVE_INET6
+ struct sockaddr_in6 in6;
+#endif
+ } s;
+ size_t namelen;
+};
+
+#define SANY_ADDR(any) ((any).s.a)
+#define SANY_LEN(any) ((any).namelen)
+#define SANY_TYPE(any) ((any).s.a.sa_family)
+
+int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts);
+
+/* The default port to fill in when no IP/IPv6 port specified */
+#define SANY_OPT_DEFPORT(p) (int)((p) & 0xFFFF)
+
+/* When only port specified default to IPANY */
+#define SANY_OPT_DEFANY 0x00000000
+
+/* When only port specified default to LOCALHOST */
+#define SANY_OPT_DEFLOCAL 0x00100000
+
+/* When only port specified default to IPv6 */
+#ifdef HAVE_INET6
+#define SANY_OPT_DEFINET6 0x00200000
+#endif
+
+int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts);
+
+/* Don't print or compare the port */
+#define SANY_OPT_NOPORT 0x01000000
+
+int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts);
+
+#endif /* __SOCK_ANY_H__ */
diff --git a/common/spio.c b/common/spio.c
new file mode 100644
index 0000000..e25ef3d
--- /dev/null
+++ b/common/spio.c
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ */
+
+/*
+ * select() and stdio are basically mutually exclusive.
+ * Hence all of this code to try to get some buffering
+ * along with select IO multiplexing.
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "compat.h"
+#include "usuals.h"
+#include "sock_any.h"
+#include "stringx.h"
+#include "sppriv.h"
+
+#define MAX_LOG_LINE 79
+#define GET_IO_NAME(io) ((io)->name ? (io)->name : "??? ")
+#define HAS_EXTRA(io) ((io)->_ln > 0)
+
+static void close_raw(int* fd)
+{
+ ASSERT(fd);
+ shutdown(*fd, SHUT_RDWR);
+ close(*fd);
+ *fd = -1;
+}
+
+static void log_io_data(spctx_t* ctx, spio_t* io, const char* data, int read)
+{
+ char buf[MAX_LOG_LINE + 1];
+ int pos, len;
+
+ ASSERT(ctx && io && data);
+
+ for(;;)
+ {
+ data += strspn(data, "\r\n");
+
+ if(!*data)
+ break;
+
+ pos = strcspn(data, "\r\n");
+
+ len = pos < MAX_LOG_LINE ? pos : MAX_LOG_LINE;
+ memcpy(buf, data, len);
+ buf[len] = 0;
+
+ sp_messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io),
+ read ? " < " : " > ", buf);
+
+ data += pos;
+ }
+}
+
+void spio_init(spio_t* io, const char* name)
+{
+ ASSERT(io && name);
+ memset(io, 0, sizeof(*io));
+ io->name = name;
+ io->fd = -1;
+}
+
+void spio_attach(spctx_t* ctx, spio_t* io, int fd, struct sockaddr_any* peer)
+{
+ struct sockaddr_any peeraddr;
+ struct sockaddr_any locaddr;
+
+ io->fd = fd;
+
+ /* Get the address on which we accepted the connection */
+ memset(&locaddr, 0, sizeof(locaddr));
+ SANY_LEN(locaddr) = sizeof(locaddr);
+
+ if(getsockname(fd, &SANY_ADDR(locaddr), &SANY_LEN(locaddr)) == -1 ||
+ sock_any_ntop(&locaddr, io->localname, MAXPATHLEN, SANY_OPT_NOPORT) == -1)
+ {
+ if (errno != EAFNOSUPPORT)
+ sp_message(ctx, LOG_WARNING, "%s: couldn't get socket address", GET_IO_NAME(io));
+ strlcpy(io->localname, "UNKNOWN", MAXPATHLEN);
+ }
+
+ /* If the caller doesn't want the peer then use our own */
+ if (peer == NULL)
+ peer = &peeraddr;
+
+ memset(peer, 0, sizeof(*peer));
+ SANY_LEN(*peer) = sizeof(*peer);
+
+ if(getpeername(fd, &SANY_ADDR(*peer), &SANY_LEN(*peer)) == -1 ||
+ sock_any_ntop(peer, io->peername, MAXPATHLEN, SANY_OPT_NOPORT) == -1)
+ {
+ if (errno != EAFNOSUPPORT)
+ sp_message(ctx, LOG_WARNING, "%s: couldn't get peer address", GET_IO_NAME(io));
+ strlcpy(io->peername, "UNKNOWN", MAXPATHLEN);
+ }
+
+ /* As a double check */
+ io->line[0] = 0;
+ io->_nx = NULL;
+ io->_ln = 0;
+}
+
+int spio_connect(spctx_t* ctx, spio_t* io, const struct sockaddr_any* sany,
+ const char* addrname)
+{
+ int ret = 0;
+ int fd;
+
+ ASSERT(ctx && io && sany && addrname);
+ ASSERT(io->fd == -1);
+
+ if((fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1)
+ RETURN(-1);
+
+ if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &(g_state.timeout), sizeof(g_state.timeout)) == -1 ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &(g_state.timeout), sizeof(g_state.timeout)) == -1)
+ sp_messagex(ctx, LOG_DEBUG, "%s: couldn't set timeouts on connection", GET_IO_NAME(io));
+
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
+
+ if(connect(fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1)
+ {
+ close_raw(&fd);
+ RETURN(-1);
+ }
+
+ spio_attach(ctx, io, fd, NULL);
+
+cleanup:
+ if(ret < 0)
+ {
+ if(spio_valid(io))
+ close_raw(&(io->fd));
+
+ sp_message(ctx, LOG_ERR, "%s: couldn't connect to: %s", GET_IO_NAME(io), addrname);
+ return -1;
+ }
+
+ ASSERT(io->fd != -1);
+ sp_messagex(ctx, LOG_DEBUG, "%s connected to: %s", GET_IO_NAME(io), io->peername);
+ return 0;
+}
+
+void spio_disconnect(spctx_t* ctx, spio_t* io)
+{
+ ASSERT(ctx && io);
+
+ if(spio_valid(io))
+ {
+ close_raw(&(io->fd));
+ sp_messagex(ctx, LOG_DEBUG, "%s connection closed", GET_IO_NAME(io));
+ }
+}
+
+unsigned int spio_select(spctx_t* ctx, ...)
+{
+ fd_set mask;
+ spio_t* io;
+ int ret = 0;
+ int have = 0;
+ int i = 0;
+ va_list ap;
+ struct timeval timeout;
+
+ ASSERT(ctx);
+ FD_ZERO(&mask);
+
+ va_start(ap, ctx);
+
+ while((io = va_arg(ap, spio_t*)) != NULL)
+ {
+ if(spio_valid(io))
+ {
+ /* We can't handle more than 31 args */
+ if(i > (sizeof(int) * 8) - 2)
+ break;
+
+ /* Check if the buffer has something in it */
+ if(HAS_EXTRA(io))
+ ret |= (1 << i);
+
+ /* Mark for select */
+ FD_SET(io->fd, &mask);
+ have = 1;
+ }
+
+ i++;
+ }
+
+ va_end(ap);
+
+ /* If any buffers had something present, then return */
+ if(ret != 0)
+ return ret;
+
+ /* No valid file descriptors */
+ if(!have)
+ return ~0;
+
+ for(;;)
+ {
+ /* Select can modify the timeout argument so we copy */
+ memcpy(&timeout, &(g_state.timeout), sizeof(timeout));
+
+ /* Otherwise wait on more data */
+ switch(select(FD_SETSIZE, &mask, NULL, NULL, &timeout))
+ {
+ case 0:
+ sp_messagex(ctx, LOG_ERR, "network operation timed out");
+ return ~0;
+
+ case -1:
+ if(errno == EINTR)
+ {
+ if(!sp_is_quit())
+ continue;
+ }
+
+ else
+ sp_message(ctx, LOG_ERR, "couldn't select on sockets");
+
+ return ~0;
+ };
+
+ break;
+ }
+
+ /* See what came in */
+ i = 0;
+
+ va_start(ap, ctx);
+
+ while((io = va_arg(ap, spio_t*)) != NULL)
+ {
+ if(spio_valid(io))
+ {
+ /* We can't handle more than 31 args */
+ if(i > (sizeof(int) * 8) - 2)
+ break;
+
+ /* We have data on the descriptor, which is an action */
+ io->last_action = time(NULL);
+
+ /* Check if the buffer has something in it */
+ if(FD_ISSET(io->fd, &mask))
+ ret |= (1 << i);
+ }
+
+ i++;
+ }
+
+ va_end(ap);
+
+ return ret;
+}
+
+int read_raw(spctx_t* ctx, spio_t* io, int opts)
+{
+ int len, x, count;
+ char* at;
+ char* p;
+
+ /*
+ * Just a refresher:
+ *
+ * _nx: Extra data read on last read.
+ * _ln: Length of that extra data.
+ *
+ * _nx should never be equal to line when entering this function.
+ * And _ln should always be less than a full buffer.
+ */
+
+ count = 0;
+ io->line[0] = 0;
+
+ /* Remaining data in the buffer */
+ if(io->_nx && io->_ln > 0)
+ {
+ ASSERT(!io->_nx || io->_nx > io->line);
+ ASSERT(io->_ln < SP_LINE_LENGTH);
+ ASSERT(io->_nx + io->_ln <= io->line + SP_LINE_LENGTH);
+
+ /* Check for a return in the current buffer */
+ if((p = (char*)memchr(io->_nx, '\n', io->_ln)) != NULL)
+ {
+ /* Move data to front */
+ x = (p - io->_nx) + 1;
+ ASSERT(x > 0);
+ memmove(io->line, io->_nx, x);
+
+ /* Null teriminate it */
+ io->line[x] = 0;
+
+ /* Do maintanence for next time around */
+ io->_ln -= x;
+ io->_nx += x;
+
+ /* A double check on the return value */
+ count += x;
+ return count;
+ }
+
+ /* Otherwise move all old data to front */
+ memmove(io->line, io->_nx, io->_ln);
+ count += io->_ln;
+
+ /* We always leave space for a null terminator */
+ len = (SP_LINE_LENGTH - io->_ln) - 1;
+ at = io->line + io->_ln;
+ }
+
+ /* No data at front just read straight in */
+ else
+ {
+ /* We always leave space for a null terminator */
+ len = SP_LINE_LENGTH - 1;
+ at = io->line;
+ }
+
+ for(;;)
+ {
+ /* Read a block of data */
+ ASSERT(io->fd != -1);
+ x = read(io->fd, at, sizeof(char) * len);
+
+ if(x == -1)
+ {
+ if(errno == EINTR)
+ {
+ /* When the application is quiting */
+ if(sp_is_quit())
+ return -1;
+
+ /* For any other signal we go again */
+ continue;
+ }
+
+ if(errno == ECONNRESET) /* Not usually a big deal so supresse the error */
+ sp_messagex(ctx, LOG_DEBUG, "%s: connection disconnected by peer", GET_IO_NAME(io));
+ else if(errno == EAGAIN)
+ sp_messagex(ctx, LOG_WARNING, "%s: network read operation timed out", GET_IO_NAME(io));
+ else
+ sp_message(ctx, LOG_ERR, "%s: couldn't read data from socket", GET_IO_NAME(io));
+
+ /*
+ * The basic logic here is that if we've had a fatal error
+ * reading from the socket once then we shut it down as it's
+ * no good trying to read from again later.
+ */
+ close_raw(&(io->fd));
+
+ return -1;
+ }
+
+ /* End of data */
+ else if(x == 0)
+ {
+ /* Maintenance for remaining data */
+ io->_nx = NULL;
+ io->_ln = 0;
+
+ return count;
+ }
+
+ /* Read data which is a descriptor action */
+ io->last_action = time(NULL);
+
+ /* Check for a new line */
+ p = (char*)memchr(at, '\n', x);
+ if(p != NULL)
+ {
+ p++;
+ count += (p - at);
+
+ /* Insert the null terminator */
+ len = x - (p - at);
+ memmove(p + 1, p, len);
+ *p = 0;
+
+ /* Do maintenence for remaining data */
+ io->_nx = p + 1;
+ io->_ln = len;
+
+ return count;
+ }
+
+ /* Move the buffer pointer along */
+ at += x;
+ len -= x;
+ count += x;
+
+ if(len <= 0)
+ {
+ /* Keep reading until we hit a new line */
+ if(opts & SPIO_DISCARD)
+ {
+ /*
+ * K, basically the logic is that we're discarding
+ * data and the data will be screwed up. So overwriting
+ * some valid data in order to flush the line and
+ * keep the buffering simple is a price we pay gladly :)
+ */
+
+ ASSERT(128 < SP_LINE_LENGTH);
+ at = (io->line + SP_LINE_LENGTH) - 128;
+ len = 128;
+
+ /* Go for next read */
+ continue;
+ }
+
+ io->_nx = NULL;
+ io->_ln = 0;
+
+ /* Null terminate */
+ io->line[SP_LINE_LENGTH] = 0;
+
+ /* A double check on the return value */
+ return count;
+ }
+ }
+}
+
+int spio_read_line(spctx_t* ctx, spio_t* io, int opts)
+{
+ int x, l;
+ char* t;
+
+ ASSERT(ctx && io);
+
+ if(!spio_valid(io))
+ {
+ sp_messagex(ctx, LOG_WARNING, "%s: tried to read from a closed connection", GET_IO_NAME(io));
+ return 0;
+ }
+
+ x = read_raw(ctx, io, opts);
+
+ if(x > 0)
+ {
+ if(opts & SPIO_TRIM)
+ {
+ t = io->line;
+
+ while(*t && isspace(*t))
+ t++;
+
+ /* Bump the entire line down */
+ l = t - io->line;
+ memmove(io->line, t, (x + 1) - l);
+ x -= l;
+
+ /* Now the end */
+ t = io->line + x;
+
+ while(t > io->line && isspace(*(t - 1)))
+ {
+ *(--t) = 0;
+ x--;
+ }
+ }
+
+ if(!(opts & SPIO_QUIET))
+ log_io_data(ctx, io, io->line, 1);
+ }
+
+ return x;
+}
+
+int spio_write_data(spctx_t* ctx, spio_t* io, const char* data)
+{
+ int len = strlen(data);
+ ASSERT(ctx && io && data);
+
+ if(!spio_valid(io))
+ {
+ sp_message(ctx, LOG_ERR, "%s: connection closed. can't write data", GET_IO_NAME(io));
+ return -1;
+ }
+
+ log_io_data(ctx, io, data, 0);
+ return spio_write_data_raw(ctx, io, (unsigned char*)data, len);
+}
+
+int spio_write_dataf(struct spctx* ctx, spio_t* io, const char* fmt, ...)
+{
+ char buf[SP_LINE_LENGTH];
+ va_list ap;
+ ASSERT(ctx && io && fmt);
+
+ buf[0] = 0;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, SP_LINE_LENGTH, fmt, ap);
+ va_end(ap);
+
+ buf[SP_LINE_LENGTH - 1] = 0;
+
+ return spio_write_data(ctx, io, buf);
+}
+
+int spio_write_data_raw(spctx_t* ctx, spio_t* io, const unsigned char* buf, int len)
+{
+ int r;
+
+ ASSERT(ctx && io && buf);
+
+ if(io->fd == -1)
+ return 0;
+
+ io->last_action = time(NULL);
+
+ while(len > 0)
+ {
+ r = write(io->fd, buf, len);
+
+ if(r > 0)
+ {
+ buf += r;
+ len -= r;
+ }
+
+ else if(r == -1)
+ {
+ if(errno == EINTR)
+ {
+ /* When the application is quiting */
+ if(sp_is_quit())
+ return -1;
+
+ /* For any other signal we go again */
+ continue;
+ }
+
+ /*
+ * The basic logic here is that if we've had a fatal error
+ * writing to the socket once then we shut it down as it's
+ * no good trying to write to it again later.
+ */
+ close_raw(&(io->fd));
+
+ if(errno == EAGAIN)
+ sp_messagex(ctx, LOG_WARNING, "%s: network write operation timed out", GET_IO_NAME(io));
+ else
+ sp_message(ctx, LOG_ERR, "%s: couldn't write data to socket", GET_IO_NAME(io));
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void spio_read_junk(spctx_t* ctx, spio_t* io)
+{
+ char buf[16];
+ const char* t;
+ int said = 0;
+ int l;
+
+ ASSERT(ctx);
+ ASSERT(io);
+
+ /* Truncate any data in buffer */
+ io->_ln = 0;
+ io->_nx = 0;
+
+ if(!spio_valid(io))
+ return;
+
+ /* Make it non blocking */
+ fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL, 0) | O_NONBLOCK);
+
+ for(;;)
+ {
+ l = read(io->fd, buf, sizeof(buf) - 1);
+ if(l <= 0)
+ break;
+
+ io->last_action = time(NULL);
+
+ buf[l] = 0;
+ t = trim_start(buf);
+
+ if(!said && *t)
+ {
+ sp_messagex(ctx, LOG_DEBUG, "%s: received junk data from daemon", GET_IO_NAME(io));
+ said = 1;
+ }
+ }
+
+ fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL, 0) & ~O_NONBLOCK);
+}
diff --git a/common/sppriv.h b/common/sppriv.h
new file mode 100644
index 0000000..28351cf
--- /dev/null
+++ b/common/sppriv.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __SPPRIV_H__
+#define __SPPRIV_H__
+
+#include "smtppass.h"
+
+typedef struct spstate
+{
+ /* Settings ------------------------------- */
+ int debug_level; /* The level to print stuff to console */
+ int max_threads; /* Maximum number of threads to process at once */
+ struct timeval timeout; /* Timeout for communication */
+ int keepalives; /* Send server keep alives at this interval */
+ int transparent; /* Transparent proxying */
+ int xclient; /* Send XFORWARD info */
+ const char* directory; /* The temp directory */
+ const char* user; /* User to run as */
+ const char* pidfile; /* The pid file for daemon */
+ const char* header; /* A header to include in the email */
+
+ struct sockaddr_any outaddr; /* The outgoing address */
+ const char* outname;
+ struct sockaddr_any listenaddr; /* Address to listen on */
+ const char* listenname;
+
+ /* State --------------------------------- */
+ const char* name; /* The name of the program */
+ int quit; /* Quit the process */
+ int daemonized; /* Whether process is daemonized or not */
+
+ /* Internal Use ------------------------- */
+ char* _p;
+}
+spstate_t;
+
+extern spstate_t g_state;
+
+#endif /* __SPPRIV_H__ */
+
diff --git a/common/stringx.c b/common/stringx.c
new file mode 100644
index 0000000..27bf06b
--- /dev/null
+++ b/common/stringx.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "usuals.h"
+#include "compat.h"
+#include "stringx.h"
+
+/* ----------------------------------------------------------------------------------
+ * Parsing
+ */
+
+int is_first_word(const char* line, const char* word, int len)
+{
+ ASSERT(line);
+ ASSERT(word);
+ ASSERT(len > 0);
+
+ while(*line && isspace(*line))
+ line++;
+
+ if(strncasecmp(line, word, len) != 0)
+ return 0;
+
+ line += len;
+ return !*line || isspace(*line);
+}
+
+int check_first_word(const char* line, const char* word, int len, char* delims)
+{
+ const char* t;
+ int found = 0;
+
+ ASSERT(line);
+ ASSERT(word);
+ ASSERT(len > 0);
+
+ t = line;
+
+ while(*t && strchr(delims, *t))
+ t++;
+
+ if(strncasecmp(t, word, len) != 0)
+ return 0;
+
+ t += len;
+
+ while(*t && strchr(delims, *t))
+ {
+ found = 1;
+ t++;
+ }
+
+ return (!*t || found) ? t - line : 0;
+}
+
+int is_last_word(const char* line, const char* word, int len)
+{
+ const char* t;
+
+ ASSERT(line);
+ ASSERT(word);
+ ASSERT(len > 0);
+
+ t = line + strlen(line);
+
+ while(t > line && isspace(*(t - 1)))
+ --t;
+
+ if(t - len < line)
+ return 0;
+
+ return strncasecmp(t - len, word, len) == 0;
+}
+
+int is_blank_line(const char* line)
+{
+ /* Small optimization */
+ if(!*line)
+ return 1;
+
+ while(*line && isspace(*line))
+ line++;
+
+ return *line == 0;
+}
+
+char* trim_start(const char* data)
+{
+ while(*data && isspace(*data))
+ ++data;
+ return (char*)data;
+}
+
+char* trim_end(char* data)
+{
+ char* t = data + strlen(data);
+
+ while(t > data && isspace(*(t - 1)))
+ {
+ t--;
+ *t = 0;
+ }
+
+ return data;
+}
+
+char* trim_space(char* data)
+{
+ data = (char*)trim_start(data);
+ return trim_end(data);
+}
+
+/* String to bool helper function */
+int strtob(const char* str)
+{
+ if(strcasecmp(str, "0") == 0 ||
+ strcasecmp(str, "no") == 0 ||
+ strcasecmp(str, "false") == 0 ||
+ strcasecmp(str, "f") == 0 ||
+ strcasecmp(str, "off") == 0)
+ return 0;
+
+ if(strcasecmp(str, "1") == 0 ||
+ strcasecmp(str, "yes") == 0 ||
+ strcasecmp(str, "true") == 0 ||
+ strcasecmp(str, "t") == 0 ||
+ strcasecmp(str, "on") == 0)
+ return 1;
+
+ return -1;
+}
diff --git a/common/stringx.h b/common/stringx.h
new file mode 100644
index 0000000..fb98d02
--- /dev/null
+++ b/common/stringx.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __STRINGX_H__
+#define __STRINGX_H__
+
+int check_first_word(const char* line, const char* word, int len, char* delims);
+int is_first_word(const char* line, const char* word, int len);
+int is_last_word(const char* line, const char* word, int len);
+int is_blank_line(const char* line);
+
+char* trim_start(const char* data);
+char* trim_end(char* data);
+char* trim_space(char* data);
+
+int strtob(const char* str);
+
+#endif /* __STRINGX_H__ */
diff --git a/common/usuals.h b/common/usuals.h
new file mode 100644
index 0000000..a32d6e9
--- /dev/null
+++ b/common/usuals.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2004, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __USUALS_H__
+#define __USUALS_H__
+
+#include <sys/types.h>
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "compat.h"
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#define countof(x) (sizeof(x) / sizeof(x[0]))
+
+#ifdef _DEBUG
+ #include "assert.h"
+ #define ASSERT(x) assert(x)
+#else
+ #define ASSERT(x)
+#endif
+
+#define KL(s) ((sizeof(s) - 1) / sizeof(char))
+#define RETURN(x) { ret = (x); goto cleanup; }
+
+
+#endif /* __USUALS_H__ */