From 335bd0d855dc2cd0b2b6c1f666fd7402afb591ca Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 9 Mar 2010 16:37:48 +0000 Subject: bogom 1.9.2 import --- milter.c | 1385 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1385 insertions(+) create mode 100644 milter.c (limited to 'milter.c') 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 +* +* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __sun__ +#include +#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++;iold_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 */ + -- cgit v1.2.3