aboutsummaryrefslogtreecommitdiff
path: root/milter.c
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2010-03-09 16:37:48 +0000
committerDimitri Sokolyuk <demon@dim13.org>2010-03-09 16:37:48 +0000
commit335bd0d855dc2cd0b2b6c1f666fd7402afb591ca (patch)
tree8535af5c9378ac29bd8a4ef18bd335cd65c7edeb /milter.c
bogom 1.9.2 import
Diffstat (limited to 'milter.c')
-rw-r--r--milter.c1385
1 files changed, 1385 insertions, 0 deletions
diff --git a/milter.c b/milter.c
new file mode 100644
index 0000000..d319164
--- /dev/null
+++ b/milter.c
@@ -0,0 +1,1385 @@
+/* $Id$ */
+
+/*
+* bogom, simple sendmail milter to interface bogofilter
+* Copyright (C) 2004-2007 Juan J. Martinez <jjm*at*usebox*dot*net>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License Version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+*/
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <regex.h>
+#include <time.h>
+#include <netdb.h>
+
+#ifdef __sun__
+#include <fcntl.h>
+#endif
+
+#include "libmilter/mfapi.h"
+#include "conf.h"
+
+/* defaults */
+#ifndef DEF_USER
+#define DEF_USER "bogofilter"
+#endif
+#ifndef DEF_CONN
+#define DEF_CONN "unix:/var/spool/bogofilter/milter.sock"
+#endif
+#ifndef DEF_CONF
+#define DEF_CONF "/etc/bogom.conf"
+#endif
+#ifndef DEF_PIDFILE
+#define DEF_PIDFILE "/var/spool/bogofilter/bogom.pid"
+#endif
+
+struct mlfiPriv
+{
+ FILE *f;
+ char *fullpath;
+ char *filename;
+ char *subject;
+ int eom;
+ size_t bodylen;
+ int old_headers;
+};
+
+sfsistat mlfi_connect(SMFICTX *, char *, _SOCK_ADDR *);
+sfsistat mlfi_envfrom(SMFICTX *, char **);
+sfsistat mlfi_envrcpt(SMFICTX *, char **);
+sfsistat mlfi_header(SMFICTX *, char *, char *);
+sfsistat mlfi_eoh(SMFICTX *);
+sfsistat mlfi_body(SMFICTX *, unsigned char *, size_t);
+sfsistat mlfi_eom(SMFICTX *);
+sfsistat mlfi_abort(SMFICTX *);
+sfsistat mlfi_close(SMFICTX *);
+void mlfi_clean(SMFICTX *);
+void usage(const char *);
+int to_maildir(char *, char *);
+char *hostname_tmp();
+
+#ifdef __sun__
+int daemon(int, int);
+#endif
+
+struct smfiDesc smfilter=
+{
+ "bogom", /* filter name */
+ SMFI_VERSION, /* version code -- do not change */
+ SMFIF_ADDHDRS | SMFIF_CHGHDRS | /* flags -- add and modify headers */
+ SMFIF_ADDRCPT, /* -- add rcpt */
+ mlfi_connect, /* connection info filter */
+ NULL, /* SMTP HELO command filter */
+ mlfi_envfrom, /* envelope sender filter */
+ mlfi_envrcpt, /* envelope recipient filter */
+ mlfi_header, /* header filter */
+ mlfi_eoh, /* end of header */
+ mlfi_body, /* body block filter */
+ mlfi_eom, /* end of message */
+ mlfi_abort, /* message aborted */
+ mlfi_close /* connection cleanup */
+};
+
+struct re_list
+{
+ regex_t p;
+ const char *pat;
+ struct re_list *n;
+};
+
+#define new_re_list(x) do {\
+ x=(struct re_list *) \
+ malloc(sizeof(struct re_list));\
+ x->n=NULL;\
+ } while(0)
+
+static const char rcsid[]="$Id$";
+
+static int mode=SMFIS_CONTINUE;
+static int train=0;
+static int verbose=0;
+static int debug=0;
+static int spamicity=0;
+static size_t bodylimit=0;
+static const char *bogo="/usr/local/bin/bogofilter";
+static const char *exclude=NULL;
+static const char *subj_tag=NULL;
+static const char *forward_spam=NULL;
+static char *quarantine_mdir=NULL;
+
+static char *reject=NULL;
+
+static struct re_list *re_c=NULL; /* re connection */
+static struct re_list *re_f=NULL; /* re envfrom */
+static struct re_list *re_r=NULL; /* re envrcpt */
+
+#ifdef __sun__
+int
+daemon(int nochdir, int noclose)
+{
+ int fd;
+
+ switch(fork())
+ {
+ case 0:
+ break;
+
+ case -1:
+ return -1;
+
+ default:
+ _exit(0);
+ }
+
+ if(setsid()==-1)
+ return -1;
+
+ if(!nochdir && chdir("/"))
+ return -1;
+
+ if(!noclose)
+ {
+ fd=open("/dev/null", O_RDWR, 0);
+ if(fd==-1)
+ return -1;
+
+ dup2(fd, fileno(stdin));
+ dup2(fd, fileno(stdout));
+ dup2(fd, fileno(stderr));
+ }
+
+ return 0;
+}
+#endif
+
+sfsistat
+mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
+{
+ struct mlfiPriv *priv;
+ struct re_list *tre; /* temporal iterator */
+
+ const void *mysaddr=NULL;
+ char host[INET6_ADDRSTRLEN];
+
+ if(hostaddr)
+ {
+ switch(hostaddr->sa_family)
+ {
+ default:
+ syslog(LOG_ERR, "mlfi_connet: unsupported sa_family");
+ break;
+
+ case AF_INET:
+ mysaddr=(const void *)&((struct sockaddr_in *)hostaddr)
+ ->sin_addr.s_addr;
+ break;
+
+ case AF_INET6:
+ mysaddr=(const void *)&((struct sockaddr_in6 *)hostaddr)
+ ->sin6_addr;
+ break;
+ }
+
+ if(!inet_ntop(hostaddr->sa_family, mysaddr, host, sizeof(host)))
+ {
+ syslog(LOG_ERR, "mlfi_connect: inet_ntop failed");
+ strcpy(host, "*");
+ }
+ }
+ else
+ strcpy(host, "*");
+
+ if(debug)
+ syslog(LOG_DEBUG, "connection from %s [ %s ]", hostname, host);
+
+ for(tre=re_c; tre; tre=tre->n)
+ {
+ if(!regexec(&tre->p, hostname, 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (connect): %s",
+ tre->pat);
+
+ return SMFIS_ACCEPT;
+ }
+
+ if(!regexec(&tre->p, host, 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (connect): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+ }
+
+ priv=(struct mlfiPriv *)malloc(sizeof(struct mlfiPriv));
+ if(!priv)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->fullpath=NULL;
+ priv->filename=NULL;
+ priv->subject=NULL;
+ priv->f=NULL;
+ priv->eom=1;
+ priv->old_headers=0;
+
+ if(smfi_setpriv(ctx, priv)!=MI_SUCCESS)
+ {
+ syslog(LOG_ERR, "on mlfi_connect: smfi_setpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_envfrom(SMFICTX *ctx, char **argv)
+{
+ struct re_list *tre; /* temporal iterator */
+
+ if(debug)
+ syslog(LOG_DEBUG, "envfrom %s", argv[0]);
+
+ for(tre=re_f; tre; tre=tre->n)
+ if(!regexec(&tre->p, argv[0], 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (envfrom): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_envrcpt(SMFICTX *ctx, char **argv)
+{
+ struct mlfiPriv *priv;
+ struct re_list *tre; /* temporal iterator */
+ int fd=-1;
+ char *tmp=NULL;
+
+ if(debug)
+ syslog(LOG_DEBUG, "envrcpt %s", argv[0]);
+
+ for(tre=re_r; tre; tre=tre->n)
+ if(!regexec(&tre->p, argv[0], 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (envrcpt): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_header: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(priv->eom)
+ {
+ /* use tmp/ from quarantine maildir if available */
+ if(quarantine_mdir)
+ {
+ tmp=hostname_tmp();
+ if(!tmp)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->fullpath=(char *)calloc(strlen(quarantine_mdir)
+ +strlen(tmp)+6, sizeof(char));
+ }
+ else
+ priv->fullpath=strdup("/tmp/bogom-msg.XXXXXXXXXX");
+
+ if(!priv->fullpath)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ if(tmp)
+ free(tmp);
+ return SMFIS_TEMPFAIL;
+ }
+
+ if(quarantine_mdir)
+ {
+ snprintf(priv->fullpath, strlen(quarantine_mdir)+
+ strlen(tmp)+6, "%s/tmp/%s", quarantine_mdir,
+ tmp);
+ priv->filename=priv->fullpath+strlen(quarantine_mdir)
+ +5;
+ free(tmp);
+ }
+
+ fd=mkstemp(priv->fullpath);
+ if(fd==-1)
+ {
+ syslog(LOG_ERR, "Unable to create tmp file in %s: %s",
+ priv->fullpath, strerror(errno));
+
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+#ifdef __sun__
+ priv->f=fdopen(fd, "w+F");
+#else
+ priv->f=fdopen(fd, "w+");
+#endif
+ if(!priv->f)
+ {
+ syslog(LOG_ERR, "Unable to create tmp file in %s: %s",
+ priv->fullpath, strerror(errno));
+
+ if(fd!=-1)
+ close(fd);
+
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->eom=0;
+ priv->bodylen=0;
+
+ if(debug)
+ syslog(LOG_DEBUG, "message begin...");
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_header: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(exclude && headerv)
+ if(!strcasecmp(headerf, "Subject"))
+ if(strstr(headerv, exclude))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "exclude string found: '%s'",
+ headerv);
+ mlfi_clean(ctx);
+ return SMFIS_ACCEPT;
+ }
+
+ if(debug)
+ syslog(LOG_DEBUG, "header %s [%s]", headerf, headerv);
+
+ if(headerv && !strcasecmp(headerf, "X-Bogosity"))
+ priv->old_headers++;
+
+ if(subj_tag && headerv)
+ if(!strcasecmp(headerf, "Subject"))
+ {
+ if(priv->subject)
+ syslog(LOG_INFO,
+ "Subject header not unique");
+ else
+ {
+ priv->subject=strdup(headerv);
+ if(!priv->subject)
+ syslog(LOG_ERR,
+ "Unable to get memory (subject"
+ " tag): %s",
+ strerror(errno));
+ }
+ }
+
+ if(fprintf(priv->f, "%s: %s\n", headerf, headerv)==EOF)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eoh(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ if(debug)
+ syslog(LOG_DEBUG, "headers ok");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_eoh: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(fprintf(priv->f, "\n")==EOF)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t bodylen)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_body: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(bodylimit)
+ {
+ if(bodylimit==priv->bodylen)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "body_limit reached, "
+ " %d bytes discarded", bodylen);
+
+ bodylen=0;
+ }
+ else
+ if(priv->bodylen+bodylen>bodylimit)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "body_limit reached, "
+ " %d bytes discarded",
+ bodylen-(bodylimit-priv->bodylen));
+
+ bodylen=bodylimit-priv->bodylen;
+ }
+ }
+
+ if(bodylen>0)
+ {
+ if(fwrite(bodyp, bodylen, 1, priv->f)!=1)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+ else
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "%d body bytes written",
+ bodylen);
+
+ priv->bodylen+=bodylen;
+ }
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eom(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+ int status, i;
+ char *bogocl, header[64];
+ float spamicity_val;
+ char *tmp_subj;
+ FILE *proc;
+
+ if(debug)
+ syslog(LOG_DEBUG, "...end of message");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_eom: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ fclose(priv->f);
+ priv->f=NULL;
+
+ bogocl=(char *)malloc(strlen(bogo)+strlen(priv->fullpath)+16);
+ if(!bogocl)
+ {
+ syslog(LOG_ERR, "on mlfi_eom: %s", strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+
+ sprintf(bogocl, "%s -", bogo);
+
+ if(train)
+ strcat(bogocl, "u");
+
+ if(verbose)
+ strcat(bogocl, "l");
+
+ if(spamicity)
+ strcat(bogocl, "TT");
+
+ strcat(bogocl, "B ");
+ strcat(bogocl, priv->fullpath);
+
+#ifdef __sun__
+ proc=popen(bogocl, "rF");
+#else
+ proc=popen(bogocl, "r");
+#endif
+ if(!proc)
+ {
+ syslog(LOG_ERR, "failed to exec bogofilter: %s",
+ strerror(errno));
+ free(bogocl);
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+ free(bogocl);
+
+ if(spamicity)
+ {
+ /* FIXME: spaces in the path will cause trouble */
+ if(fscanf(proc, "%*[^ ] %f\n", &spamicity_val)!=1)
+ {
+ syslog(LOG_ERR, "failed to get bogofilter spamicity "
+ "value");
+ spamicity_val=-1;
+ }
+ else
+ if(debug)
+ syslog(LOG_DEBUG, "spamicity value: %f",
+ spamicity_val);
+ }
+
+ status=pclose(proc);
+
+ if(!WIFEXITED(status))
+ {
+ syslog(LOG_ERR, "bogofilter didn't exit normally");
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+
+ switch(WEXITSTATUS(status))
+ {
+ case 3:
+ case -1:
+ syslog(LOG_ERR, "bogofilter reply: I/O error");
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ case 0:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Spam, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "Yes, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(forward_spam)
+ {
+ if(smfi_addrcpt(ctx, (char *)forward_spam)
+ !=MI_SUCCESS)
+ syslog(LOG_ERR, "forward_spam failed:"
+ " '%s'", forward_spam);
+ else
+ if(debug)
+ syslog(LOG_DEBUG,
+ "forward_spam rcpt added: "
+ "'%s'", forward_spam);
+ }
+
+ if(subj_tag && priv->subject)
+ {
+ tmp_subj=(char *)calloc(strlen(subj_tag)+
+ strlen(priv->subject)+2, sizeof(char));
+
+ if(!tmp_subj)
+ syslog(LOG_ERR, "Unable to get memory:"
+ " %s", strerror(errno));
+ else
+ {
+ snprintf(tmp_subj, strlen(subj_tag)+
+ strlen(priv->subject)+2,
+ "%s %s", subj_tag,
+ priv->subject);
+
+ /* truncate if needed and be nice
+ with RFC */
+ if(strlen(tmp_subj)>998)
+ tmp_subj[998]=0;
+
+ if(smfi_chgheader(ctx, "Subject", 1,
+ tmp_subj)!=MI_SUCCESS)
+ syslog(LOG_ERR, "subject_tag"
+ " failed: '%s'",
+ tmp_subj);
+ else
+ if(debug)
+ syslog(LOG_DEBUG,
+ "subject_tag"
+ " added: '%s'",
+ tmp_subj);
+ free(tmp_subj);
+ }
+ }
+
+ if(verbose)
+ {
+ if(mode==SMFIS_CONTINUE)
+ syslog(LOG_NOTICE,
+ "bogofilter reply: spam");
+ else
+ if(mode==SMFIS_REJECT)
+ syslog(LOG_NOTICE,
+ "spam rejected");
+ else
+ syslog(LOG_NOTICE,
+ "spam discarded");
+ }
+
+ if(mode==SMFIS_REJECT && reject)
+ smfi_setreply(ctx, "554", "5.7.1", reject);
+
+ if(quarantine_mdir)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "copying message "
+ "to quarantine_mdir");
+
+ if(chdir(quarantine_mdir)==-1)
+ syslog(LOG_ERR, "failed to chdir to "
+ "quarantine_mdir: %s\n",
+ strerror(errno));
+ else
+ if(to_maildir(priv->fullpath,
+ priv->filename)==-1)
+ syslog(LOG_ERR, "failed to"
+ " copy message to "
+ "quarantine_mdir");
+ }
+
+ mlfi_clean(ctx);
+ return mode;
+ case 1:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Ham, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "No, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(verbose)
+ syslog(LOG_NOTICE, "bogofilter reply: ham");
+ break;
+ case 2:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Unsure, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "Unsure, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(verbose)
+ syslog(LOG_NOTICE, "bogofilter reply: unsure");
+ break;
+ default:
+ syslog(LOG_ERR, "bogofilter reply is unknown");
+ break;
+ }
+
+ if(priv->old_headers>1)
+ for(i=2, priv->old_headers++;i<priv->old_headers+1;i++)
+ {
+ smfi_chgheader(ctx, "X-Bogosity", i, NULL);
+ if(debug)
+ syslog(LOG_DEBUG, "previous header removed");
+ }
+
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_abort(SMFICTX *ctx)
+{
+ if(debug)
+ syslog(LOG_DEBUG, "message ABORTED");
+
+ mlfi_clean(ctx);
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_close(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ return SMFIS_CONTINUE;
+
+ if(!priv->eom)
+ mlfi_clean(ctx);
+
+ smfi_setpriv(ctx, NULL);
+ free(priv);
+
+ if(debug)
+ syslog(LOG_DEBUG, "connection closed");
+
+ return SMFIS_CONTINUE;
+}
+
+void
+mlfi_clean(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ if(debug)
+ syslog(LOG_DEBUG, "cleaning message...");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+
+ if(!priv)
+ return;
+
+ if(priv->f)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "closing tmp file");
+ fclose(priv->f);
+ priv->f=NULL;
+ }
+
+ if(priv->fullpath)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "removing tmp file");
+ unlink(priv->fullpath);
+ free(priv->fullpath);
+ priv->fullpath=NULL;
+ }
+
+ if(priv->subject)
+ {
+ free(priv->subject);
+ priv->subject=NULL;
+ }
+
+ priv->eom=1;
+ priv->old_headers=0;
+
+ if(debug)
+ syslog(LOG_DEBUG, "...cleaning done");
+
+ return;
+}
+
+char *
+hostname_tmp()
+{
+ char *p;
+ char myhostname[MAXHOSTNAMELEN+128];
+ struct timeval tp;
+
+ if(gettimeofday(&tp, NULL)==-1)
+ tp.tv_sec=time(NULL);
+
+ /* time + hostname to make a unique filename NFS friendly */
+ snprintf(myhostname, 117, "bogom_%lu.%lu.", tp.tv_sec, tp.tv_usec);
+
+ if(gethostname(myhostname+strlen(myhostname), MAXHOSTNAMELEN)==-1)
+ {
+ syslog(LOG_NOTICE, "failed to get my hostname");
+ strcpy(myhostname, "unknown_hostname");
+ }
+
+ p=myhostname;
+ while((p=strstr(p, "/")))
+ *p='\057';
+
+ p=myhostname;
+ while((p=strstr(p, ":")))
+ *p='\072';
+
+ strcat(myhostname, ".XXXXXXXXXX");
+
+ return strdup(myhostname);
+}
+
+int
+to_maildir(char *origin, char *filename)
+{
+ char *p;
+ struct stat st;
+
+ /* caller must chdir to quarantine_mdir */
+
+ if(stat("new", &st)==-1 && errno==ENOENT)
+ if(mkdir("new", 0700)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to "
+ "create new/: %s", strerror(errno));
+ return -1;
+ }
+
+ p=(char *)calloc(strlen(filename)+strlen(quarantine_mdir)+6,
+ sizeof(char));
+ if(!p)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to get memory: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ snprintf(p, strlen(filename)+strlen(quarantine_mdir)+6,
+ "%s/new/%s\n", quarantine_mdir, filename);
+
+ if(link(origin, p)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to link file: %s",
+ strerror(errno));
+ free(p);
+ return -1;
+ }
+
+ free(p);
+
+ return 0;
+}
+
+void
+usage(const char *argv0)
+{
+ fprintf(stderr, "usage: %s\t[-R | -D] [-t] [-v] [-u user] [-s conn]\n"
+ "\t\t[-b bogo_path ] [-x exclude_string] "
+ "[-c conf_file]\n\t\t[-l body_limit] [-p pidfile] "
+ "[-f forward_spam]\n"
+ "\t\t[-q quarantine_mdir] [-S] [-d]\n", argv0);
+
+ return;
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *user=DEF_USER;
+ const char *conn=DEF_CONN;
+ const char *pipe=NULL;
+ const char *conffile=DEF_CONF;
+ const char *pidfile=DEF_PIDFILE;
+
+ FILE *pidfile_fd;
+ int result;
+
+ struct re_list *tre;
+ struct string_list *tsl, *tsl2;
+
+ /* configuration tokens */
+ struct conftoken conf[]=
+ {
+ { "verbose", REQ_BOOL, NULL, -1, NULL },
+ { "training", REQ_BOOL, NULL, -1, NULL },
+ { "user", REQ_QSTRING, NULL, 0, NULL },
+ { "connection", REQ_QSTRING, NULL, 0, NULL },
+ { "exclude_string", REQ_QSTRING, NULL, 0, NULL },
+ { "re_envfrom", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "bogofilter", REQ_QSTRING, NULL, 0, NULL },
+ { "policy", REQ_STRING, NULL, 0, NULL },
+ { "reject", REQ_QSTRING, NULL, 0, NULL },
+ { "re_connection", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "re_envrcpt", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "body_limit", REQ_STRING, NULL, 0, NULL },
+ { "pidfile", REQ_QSTRING, NULL, 0, NULL },
+ { "subject_tag", REQ_QSTRING, NULL, 0, NULL },
+ { "forward_spam", REQ_QSTRING, NULL, 0, NULL },
+ { "quarantine_mdir", REQ_QSTRING, NULL, 0, NULL },
+ { "spamicity_header", REQ_BOOL, NULL, -1, NULL },
+ { NULL, 0, NULL, 0, NULL }
+ };
+
+ int opt;
+ const char *opts="hu:p:b:RDtvx:w:c:l:ds:f:q:S";
+
+ struct passwd *pw=NULL;
+ struct stat st;
+
+ while((opt=getopt(argc, argv, opts))!=-1)
+ switch(opt)
+ {
+ case 'h':
+ default:
+ usage(argv[0]);
+ exit(1);
+ break;
+
+ case 'u':
+ user=optarg;
+ break;
+
+ case 's':
+ conn=optarg;
+ break;
+
+ case 'b':
+ bogo=optarg;
+ break;
+
+ case 'R':
+ mode=SMFIS_REJECT;
+ break;
+
+ case 'D':
+ mode=SMFIS_DISCARD;
+ break;
+
+ case 't':
+ train=1;
+ break;
+
+ case 'v':
+ verbose=1;
+ break;
+
+ case 'x':
+ exclude=optarg;
+ break;
+
+ case 'c':
+ conffile=optarg;
+ break;
+
+ case 'l':
+ bodylimit=(size_t)atol(optarg);
+ break;
+
+ case 'd':
+ debug=1;
+ verbose=1;
+ break;
+
+ case 'p':
+ pidfile=optarg;
+ break;
+
+ case 'f':
+ forward_spam=optarg;
+ break;
+
+ case 'q':
+ quarantine_mdir=optarg;
+ break;
+
+ case 'S':
+ spamicity=1;
+ break;
+ }
+
+ /* read configuration file */
+ if(!read_conf(conffile, conf))
+ {
+ if(conf[0].bool!=-1)
+ verbose=conf[0].bool;
+
+ if(conf[1].bool!=-1)
+ train=conf[1].bool;
+
+ if(conf[2].str)
+ user=conf[2].str;
+
+ if(conf[3].str)
+ conn=conf[3].str;
+
+ if(conf[4].str)
+ exclude=conf[4].str;
+
+ if(conf[5].sl)
+ {
+ for(tsl=conf[5].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_f)
+ {
+ new_re_list(re_f);
+ if(!re_f)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_f;
+ re_f=tre;
+ }
+
+ if(regcomp(&(re_f->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_f->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[5].sl=NULL;
+ }
+
+ if(conf[6].str)
+ bogo=conf[6].str;
+
+ if(conf[7].str)
+ {
+ if(!strcmp(conf[7].str, "pass"))
+ mode=SMFIS_CONTINUE;
+ else
+ {
+ if(!strcmp(conf[7].str, "reject"))
+ mode=SMFIS_REJECT;
+ else
+ {
+ if(!strcmp(conf[7].str, "discard"))
+ mode=SMFIS_DISCARD;
+ else
+ {
+ fprintf(stderr, "conf error:"
+ " unknown policy\n");
+ return 1;
+ }
+ }
+ }
+ }
+
+ if(conf[8].str)
+ reject=conf[8].str;
+
+ if(conf[9].sl)
+ {
+ for(tsl=conf[9].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_c)
+ {
+ new_re_list(re_c);
+ if(!re_c)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_c;
+ re_c=tre;
+ }
+
+ if(regcomp(&(re_c->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_c->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[9].sl=NULL;
+ }
+
+ if(conf[10].sl)
+ {
+ for(tsl=conf[10].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_r)
+ {
+ new_re_list(re_r);
+ if(!re_r)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_r;
+ re_r=tre;
+ }
+
+ if(regcomp(&(re_r->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_r->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[10].sl=NULL;
+ }
+
+ if(conf[11].str)
+ {
+ bodylimit=atoi(conf[11].str);
+ if(bodylimit<=0)
+ {
+ fprintf(stderr, "Warning: body_length value"
+ "is invalid, ignored\n");
+ bodylimit=0;
+ }
+
+ /* parse units */
+ switch(conf[11].str[strlen(conf[11].str)-1])
+ {
+ default:
+ /* nothing, use bytes */
+ break;
+ case 'k':
+ case 'K':
+ bodylimit*=1024;
+ break;
+ case 'm':
+ case 'M':
+ bodylimit*=1024*1024;
+ break;
+ }
+ }
+
+ if(conf[12].str)
+ pidfile=conf[12].str;
+
+ if(conf[13].str)
+ subj_tag=conf[13].str;
+
+ if(conf[14].str)
+ forward_spam=conf[14].str;
+
+ if(conf[15].str)
+ quarantine_mdir=conf[15].str;
+
+ if(conf[16].bool!=-1)
+ spamicity=conf[16].bool;
+ }
+ else
+ return 1; /* error reading configuration */
+
+ if(access(pidfile, F_OK)!=-1)
+ {
+ fprintf(stderr, "pidfile '%s' exists, delete it if"
+ " the milter is not already running\n", pidfile);
+ return 1;
+ }
+
+ if(!strncmp(conn, "unix:", 5))
+ pipe=conn+5;
+ else
+ if(!strncmp(conn, "local:", 6))
+ pipe=conn+6;
+
+ if(pipe)
+ unlink(pipe);
+
+ /* if we're root, drop privileges */
+ if(!getuid())
+ {
+ /* ugly (and portable) setproctitle */
+ if(argc>1)
+ argv[1]=NULL;
+
+ pw=getpwnam(user);
+ if(!pw)
+ {
+ fprintf(stderr, "getpwnam %s failed: %s\n", user,
+ strerror(errno));
+ return 1;
+ }
+ if(setegid(pw->pw_gid) || setgid(pw->pw_gid))
+ {
+ fprintf(stderr, "setgid failed: %s\n", strerror(errno));
+ return 1;
+ }
+ if(setuid(pw->pw_uid) || seteuid(pw->pw_uid))
+ {
+ fprintf(stderr, "setuid failed: %s\n", strerror(errno));
+ return 1;
+ }
+ }
+
+ if(daemon(0, 0))
+ {
+ fprintf(stderr, "daemon failed: %s\n", strerror(errno));
+ unlink(pidfile);
+ return 1;
+ }
+
+ /* setup time to timezone */
+ tzset();
+
+ openlog("bogom", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ pidfile_fd=fopen(pidfile, "w");
+ if(!pidfile_fd)
+ {
+ syslog(LOG_ERR, "unable to open pidfile '%s': %s\n",
+ pidfile, strerror(errno));
+ return 1;
+ }
+
+ if(fprintf(pidfile_fd, "%li\n", (long)getpid())<=0 ||
+ fclose(pidfile_fd)!=0)
+ {
+ syslog(LOG_ERR, "unable to write into pidfile '%s': %s\n",
+ pidfile, strerror(errno));
+ unlink(pidfile);
+ return 1;
+ }
+
+ if(smfi_setconn((char *)conn)==MI_FAILURE)
+ {
+ syslog(LOG_ERR, "smfi_setconn %s failed\n", conn);
+ return 1;
+ }
+
+ if(smfi_register(smfilter)!=MI_SUCCESS)
+ {
+ syslog(LOG_ERR, "smfi_register failed\n");
+ return 1;
+ }
+
+ syslog(LOG_INFO, "started %s", rcsid);
+
+ if(quarantine_mdir)
+ {
+ if(quarantine_mdir[strlen(quarantine_mdir)]=='/')
+ quarantine_mdir[strlen(quarantine_mdir)]=0;
+
+ if(quarantine_mdir[0]!='/')
+ {
+ syslog(LOG_ERR, "quarantine_mdir path must be"
+ " absolute");
+ return 1;
+ }
+
+ if(chdir(quarantine_mdir)==-1)
+ {
+ syslog(LOG_ERR, "failed to chdir to quarantine_mdir: "
+ "%s", strerror(errno));
+ return 1;
+ }
+
+ if(stat("tmp/", &st)==-1 && errno==ENOENT)
+ {
+ if(mkdir("tmp", 0700)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to "
+ "create tmp/: %s", strerror(errno));
+ return 1;
+ }
+ }
+
+ }
+
+ if(quarantine_mdir && bodylimit)
+ {
+ syslog(LOG_ERR, "body_limit is incompatible with "
+ "quarantine_mdir and will be ignored");
+
+ bodylimit=0;
+ }
+
+ result=smfi_main();
+
+ unlink(pidfile);
+
+ return result;
+}
+
+/* EOF */
+