/* $Id: milter.c,v 1.34 2006/10/22 10:35:26 reidrac Exp reidrac $ */

/*
* bogom, simple sendmail milter to interface bogofilter
* Copyright (C) 2004, 2005 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 <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: milter.c,v 1.34 2006/10/22 10:35:26 reidrac Exp reidrac $";

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];

	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, "*");
	}

	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;
		}

		priv->f=fdopen(fd, "w+");
		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);

	proc=popen(bogocl, "r");
	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 */
		fscanf(proc, "%*[^ ] %f\n", &spamicity_val);

		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)
				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)
				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)
				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 */



syntax highlighted by Code2HTML, v. 0.9.1