#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <libmilter/mfapi.h>
#include <pcreposix.h>

#define SMF_VERSION	"1.7b"

#define MAX_STRING_SIZE	4096
#define MAX_BUFFER_SIZE	32768

#define MAX_PARSE_TXT_DEFAULT	1048576

#define C_GLOBAL	0
#define C_ACTIONS	1
#define C_HOSTS		2
#define C_HEADERS	3
#define C_ATTACHMENTS	4
#define C_MIMETYPES	5
#define C_BODY		6
#define C_NUMBER	7

#define MTYPE_TEXT	0
#define MTYPE_HTML	1
#define MTYPE_MULTIPART	2
#define MTYPE_OTHER	3

#define A_NULL		0
#define A_ACCEPT	1
#define A_REJECT	2
#define A_DISCARD	4
#define A_ADDRCPT	8
#define A_DELRCPT	16
#define A_CHGRCPT	32
#define A_ADDHDR	64
#define A_DELHDR	128
#define A_CHGHDR	256
#define A_QUARANTINE	512
#define A_PROGRAM	1024
#define A_STRIP		2048
#define A_RBL		4096
#define A_RESOLV	8192
#define A_ACL		16384

#define MAX_REGMATCH_NUMBER	100
#define MAX_PROGRAM_ARGUMENTS	100

#define REGEX_OPT_NEGATIVE	1
#define REGEX_OPT_STRIPTAG	2
#define REGEX_OPT_DECODEHTML	4

#define E_MATCHED	0
#define E_TRIGGERED	1
#define E_PROCESSED	2
#define E_WAITING	3

#define ENC_BASE64	0
#define ENC_QPRINTABLE	1
#define ENC_BINARY	2

#define NEW_PHASE	0
#define EOH_PHASE	1
#define EOM_PHASE	2

#define MAX_CONF_NDX	20

#ifndef MI_SOMAXCONN
#  if SOMAXCONN > 20
#     define MI_SOMAXCONN  SOMAXCONN
#  else /* SOMAXCONN */
#     define MI_SOMAXCONN  20
#  endif /* SOMAXCONN */
#endif /* ! MI_SOMAXCONN */

#define INCLUDE_DIRECTIVE	"INCLUDE"

static char *actions[] = {
    "null", "accept", "reject", "discard", "addrcpt", "delrcpt", "chgrcpt",
    "addhdr", "delhdr", "chghdr", "quarantine", "program", "strip", "rbl", "resolv", "acl", NULL
};

static int actions_id[] = {
    A_NULL, A_ACCEPT, A_REJECT, A_DISCARD, A_ADDRCPT, A_DELRCPT, A_CHGRCPT,
    A_ADDHDR, A_DELHDR, A_CHGHDR, A_QUARANTINE, A_PROGRAM, A_STRIP, A_RBL, A_RESOLV, A_ACL
};

static char *smfis_codes[] = {
    "SMFIS_CONTINUE", "SMFIS_REJECT", "SMFIS_DISCARD", "SMFIS_ACCEPT", "SMFIS_TEMPFAIL"
};

static char *regex_collacting[] = {
    "[:alnum:]_", "[:space:]", "[:digit:]", "\r", "\n", "\t"
};

static char *regex_perl[] = {
    "w", "s", "d", "r", "n", "t", NULL
};

typedef struct chapter {
    char *name;
    int type;
} CHAPTER;

CHAPTER chapter_list[C_NUMBER] = {
    {"[global]", C_GLOBAL},
    {"[actions]", C_ACTIONS},
    {"[hosts]", C_HOSTS},
    {"[headers]", C_HEADERS},
    {"[attachments]", C_ATTACHMENTS},
    {"[mime types]", C_MIMETYPES},
    {"[body]", C_BODY}
};

typedef struct smfAction {
    char *name;
    int opcode;
    char *value;
    struct smfList *list;
    struct smfEvent *event;
    struct smfAction *final;
} SMFACTION;

typedef struct smfEvent {
    char *name;
    char *pattern;
    int regexmod;
    int regexopt;
    int chapter;
    regex_t preg;
    struct smfEventList *pos_event;
    struct smfEventList *neg_event;
    struct smfActionList *action;
} SMFEVENT;

typedef struct smfList {
    char *item;
    struct smfList *next;
} SMFLIST;

typedef struct smfActionList {
    struct smfAction *action;
    struct smfActionList *next;
} SMFACTIONLIST;

typedef struct smfEventList {
    struct smfEvent *event;
    struct smfEventList *next;
} SMFEVENTLIST;

typedef struct smfHeaderList {
    char *name;
    struct smfEventList *entr;
    struct smfHeaderList *next;
} SMFHEADERLIST;

typedef struct msgHeader {
    char *name;
    int snumber;
    unsigned char *value;
    struct msgHeader *next;
} MSGHEADER;

typedef struct msgPart {
    int type;
    int encoding;
    char *mimename;
    char *filename;
    char *mtype;
    unsigned char *headers;
    unsigned char *body;
    unsigned char *body_decoded;
    unsigned char *body_stripped;
    size_t bodylen;
    size_t bodylen_decoded;
    size_t bodylen_stripped;
    struct msgPart *next;
} MSGPART;

typedef struct msgEvent {
    SMFEVENT *event;
    MSGHEADER *header;
    MSGPART *part;
    char *hostaddr;
    int status;
    struct msgEvent *next;
} MSGEVENT;

typedef struct Message {
    char buf[MAX_STRING_SIZE + 1];
    char *queueID;
    char *log;
    char *boundary;
    char *mimename;
    char *filename;
    char *mtype;
    unsigned char *body;
    MSGHEADER *header;
    MSGEVENT *event;
    MSGEVENT *trigger;
    MSGPART *mpart;
    pid_t program;
    int type;
    int encoding;
    int phase;
    int amask;
    int rc;
    int cfgNdx;
    size_t bodylen;
} SMFMESSAGE;

typedef struct Connection {
    char *hostaddr;
    MSGEVENT *event;
    SMFMESSAGE *message;
} SMFCONNECTION;

typedef struct Config {
    FILE *fp;
    char *name;
    int str_num;
    struct Config *next;
} SMFCONFIG;

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_close(SMFICTX*);
sfsistat mlfi_abort(SMFICTX*);
void mlfi_log(SMFMESSAGE*);

void smfUSR1handler(int);

struct smfiDesc smfilter = {
    "Sentinel",		/* filter name */
    SMFI_VERSION,	/* version code -- do not change */
    0,			/* flags */
    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 */
};

SMFACTIONLIST *smfAction_list[MAX_CONF_NDX];
SMFHEADERLIST *smfHeader_list[MAX_CONF_NDX];
SMFEVENTLIST *smfHost_list[MAX_CONF_NDX];
SMFEVENTLIST *smfAttachment_list[MAX_CONF_NDX];
SMFEVENTLIST *smfMimeType_list[MAX_CONF_NDX];
SMFEVENTLIST *smfBody_list[MAX_CONF_NDX];
int smfConfNdx = 0;

static char *EnvelopeFrom = "EnvelopeFrom";
static char *EnvelopeRcpt = "EnvelopeRcpt";
static char *EmptyEnvFrom = "<>";
static char *ALOG_REJECTED = "rejected";
static char *ALOG_DISCARDED = "discarded";
static char *ExecName = "sentinel";
static char *DefaultConfig = "/etc/mail/sentinel.cf";
static char *StripCType = "Content-Type: text/plain\r\n\r\n";

int lenStripCType = 0, debug_level = 0, runDaemon = 0;
int cfgTest = 0, logTOsyslog = 0, debugTOsyslog = 0;
rlim_t max_nofiles = 0;
int max_soconn = MI_SOMAXCONN;
size_t max_parse_txt = MAX_PARSE_TXT_DEFAULT;
char *confFile;
char *tmpDir = NULL;
char *logFile = NULL;
char *debugFile = NULL;
char *globalQuarantineDir = NULL;
char *setUser = NULL;
char *setGroup = NULL;
char Buf[MAX_STRING_SIZE + 1];
char ErrBuf[MAX_STRING_SIZE + 1];
unsigned char B64[256];
unsigned char HEX[256];
unsigned char DEC[256];

void smfError(int code, const char *fmt, ...) {
    va_list ap;

    va_start(ap, fmt);
    vsnprintf(ErrBuf, sizeof(ErrBuf), fmt, ap);
    va_end(ap);
    if (runDaemon)
	syslog(LOG_ERR, ErrBuf);
    else
	fprintf(stderr, "%s\n", ErrBuf);
    if (code > 0)
	exit(code);
}

void smfPrintDebug(int debug, char *dbuf, const char *fmt, ...) {
    va_list ap;
    time_t now;
    FILE *fp = NULL;

    if (debug <= debug_level) {
	now = time(0);
	strftime(dbuf, 20, "%m/%d/%Y %H:%M:%S", localtime(&now));
	if (!cfgTest) {
	    if (!debugTOsyslog)
		fp = fopen(debugFile, "a");
	    else
		if (!runDaemon)
		    fp = stdout;
	}
	else
	    fp = stdout;
	va_start(ap, fmt);
	vsnprintf(dbuf + 32, sizeof(ErrBuf), fmt, ap);
	va_end(ap);
	if (fp) {
	    fprintf(fp, "%s %s\n", dbuf, dbuf + 32);
	    if (!cfgTest && !debugTOsyslog)
		fclose(fp);
	    else
		fflush(stdout);
	}
	else
	    syslog(LOG_DEBUG, dbuf + 32);
    }
}

char* smfGetLine(char* buf, int *len) {
    char *ptr = buf;

    while (*ptr && *ptr != '\n' && *ptr != '\r' && (ptr - buf) < MAX_STRING_SIZE)
	ptr++;
    *len = ptr - buf;
    if (*ptr && *ptr++ == '\r' && *ptr == '\n')
	ptr++;
    return ptr;
}

void smfLog(SMFMESSAGE *message, const char *fmt, ...) {
    int logSize, msgSize;
    va_list ap;

    va_start(ap, fmt);
    msgSize = vsnprintf(message->buf, MAX_STRING_SIZE, fmt, ap);
    va_end(ap);
    if (msgSize > 0) {
	logSize = message->log == NULL ? 0 : strlen(message->log);
	if ((message->log = (char*) realloc(message->log, logSize + msgSize + 2))) {
	    strncpy(message->log + logSize, message->buf, msgSize);
	    *(message->log + logSize + msgSize) = '\n';
	    *(message->log + logSize + msgSize + 1) = '\0';
	}
	else
	    syslog(LOG_ERR, "Memory exhausted");
    }
}

void smfFlushLog(SMFMESSAGE *message) {
    char *ptr1, *ptr2;
    time_t now;
    int logLen, len, str_len;
    FILE *fp = NULL;

    /* if we have what to say */
    if (message->log) {
	now = time(0);
	strftime(message->buf, 20, "%m/%d/%Y %H:%M:%S", localtime(&now));
	if (!cfgTest) {
	    if (!logTOsyslog)
		fp = fopen(logFile, "a");
	}
	else {
	    fp = stdout;
	    if (message->queueID)
		fprintf(fp, "smfFlushLog[%s]:\n--\n", message->queueID);
	    else
		fprintf(fp, "smfFlushLog:\n--\n");
	}
	if (fp) {
	    if (message->queueID)
		fprintf(fp, "%s sentinel[%s]:\n", message->buf, message->queueID);
	    else
		fprintf(fp, "%s sentinel:\n", message->buf);
	    logLen = strlen(message->log);
	    for (len = 0, ptr1 = message->log; len < logLen; ptr1 = ptr2) {
		ptr2 = smfGetLine(ptr1, &str_len);
		*(ptr1 + str_len) = '\0';
		fprintf(fp, "\t%s\n", ptr1);
		len += ptr2 - ptr1;
	    }
	    if (!cfgTest)
		fclose(fp);
	    else
		fprintf(fp, "--\n");
	}
	else {
	    if (message->queueID)
		syslog(LOG_INFO, "%s: %s", message->queueID, message->log);
	    else
		syslog(LOG_INFO, message->log);
	}
	free(message->log);
	message->log = NULL;
    }
}

int smfAddAction(SMFEVENTLIST *smfel, char *action, int ndx, SMFCONFIG *config) {
    SMFACTIONLIST *smfal, *smfal2;
    char *p, *lasts;

    for (p = strtok_r(action, ", ", &lasts); p && *p != '\0'; p = strtok_r(NULL, ", ", &lasts)) {
	for (smfal = smfAction_list[ndx]; smfal; smfal = smfal->next) {
	    if (!strcmp(p, smfal->action->name)) {
		if (!(smfal2 = (SMFACTIONLIST*) malloc(sizeof(SMFACTIONLIST)))) {
		    smfError(0, "smfconfig: memory exhausted");
		    return -1;
		}
		smfal2->action = smfal->action;
		smfal2->next = smfel->event->action;
		smfel->event->action = smfal2;
		break;
	    }
	}
	if (!smfal) {
	    smfError(0, "smfconfig: unknown action %s (string %d in %s)", p, config->str_num, config->name);
	    return -1;
	}
    }
    return 0;
}

int smfAddACL(SMFACTION *smfa, SMFCONFIG *config) {
    char *buf, *p;
    int i;
    FILE *fp;
    SMFLIST *smfl;

    if (!(buf = (char*) malloc(MAX_STRING_SIZE + 1))) {
	smfError(0, "smfconfig: memory exhausted, smfAddACL(%s), string %d in %s", smfa->name, config->str_num, config->name);
	return -1;
    }
    if ((p = strchr(smfa->value, ':')))
	*(p++) = 0;
    else
	p = smfa->value;
    if (!(fp = fopen(p, "r"))) {
	smfError(0, "smfconfig: file %s is inaccessible (string %d in %s)", p, config->str_num, config->name);
	return -1;
    }
    *p = 0;
    while (fgets(buf, MAX_STRING_SIZE, fp)) {
	if (*buf == '#') continue; /* skip comments */
	for (i = 0; *(buf + i); i++) {
	    if (*(buf + i) == '\n')
		*(buf + i) = 0;
	}
	if (*buf == '\0') continue; /* skip empty strings */
	if (!(smfl = (SMFLIST*) malloc(sizeof(SMFLIST))) || !(smfl->item = strdup(buf))) {
	    smfError(0, "smfconfig: memory exhausted, smfAddACL(%s), string %d in %s", smfa->name, config->str_num, config->name);
	    return -1;
	}
	smfl->next = smfa->list;
	smfa->list = smfl;
    }
    fclose(fp);
    free(buf);
    return 0;
}

void smfClearACL(SMFACTION *smfa) {
    SMFLIST *lprev, *lnext;

    for (lprev = smfa->list, lnext = smfa->list; lnext; lprev = lnext) {
	lnext = lprev->next;
	free(lprev->item);
	free(lprev);
    }
}

int smfAddEvent(SMFEVENTLIST *smfel, char *event, int ndx, SMFCONFIG *config) {
    SMFHEADERLIST *smfhl;
    SMFEVENT *smfe;
    SMFEVENTLIST *smfel2, *smfel3;
    SMFACTIONLIST *smfal;
    char *p, *p2, *lasts;

    for (p = strtok_r(event, ", ", &lasts); p && *p != '\0'; p = strtok_r(NULL, ", ", &lasts)) {
	if (!(smfel3 = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
	    smfError(0, "smfconfig: memory exhausted");
	    return -1;
	}
	smfel3->event = NULL;
	for (smfhl = smfHeader_list[ndx]; smfhl && !smfel3->event; smfhl = smfhl->next) {
	    for (smfel2 = smfhl->entr; smfel2 && !smfel3->event; smfel2 = smfel2->next) {
		if (*p == '!') {
		    if (!strcmp(smfel2->event->name, (p + 1))) {
			smfel3->event = smfel2->event;
			smfel3->next = smfel->event->neg_event;
			smfel->event->neg_event = smfel3;
		    }
		}
		else {
		    if (!strcmp(smfel2->event->name, p)) {
			smfel3->event = smfel2->event;
			smfel3->next = smfel->event->pos_event;
			smfel->event->pos_event = smfel3;
		    }
		}
	    }
	}
	for (smfel2 = smfAttachment_list[ndx]; smfel2 && !smfel3->event; smfel2 = smfel2->next) {
	    if (*p == '!') {
		if (!strcmp(smfel2->event->name, (p + 1))) {
		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->neg_event;
		    smfel->event->neg_event = smfel3;
		}
	    }
	    else {
		if (!strcmp(smfel2->event->name, p)) {
		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->pos_event;
		    smfel->event->pos_event = smfel3;
		}
	    }
	}
	for (smfel2 = smfMimeType_list[ndx]; smfel2 && !smfel3->event; smfel2 = smfel2->next) {
	    if (*p == '!') {
		if (!strcmp(smfel2->event->name, (p + 1))) {
		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->neg_event;
		    smfel->event->neg_event = smfel3;
		}
	    }
	    else {
		if (!strcmp(smfel2->event->name, p)) {
		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->pos_event;
		    smfel->event->pos_event = smfel3;
		}
	    }
	}
	for (smfel2 = smfHost_list[ndx]; smfel2 && !smfel3->event; smfel2 = smfel2->next) {
	    if (*p == '!') {
    		if (!strcmp(smfel2->event->name, (p + 1))) {
    		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->neg_event;
		    smfel->event->neg_event = smfel3;
		}
	    }
	    else {
    		if (!strcmp(smfel2->event->name, p)) {
    		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->pos_event;
		    smfel->event->pos_event = smfel3;
		}
	    }
	}
	for (smfel2 = smfBody_list[ndx]; smfel2 && !smfel3->event; smfel2 = smfel2->next) {
	    if (*p == '!') {
    		if (!strcmp(smfel2->event->name, (p + 1))) {
    		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->neg_event;
		    smfel->event->neg_event = smfel3;
		}
	    }
	    else {
    		if (!strcmp(smfel2->event->name, p)) {
    		    smfel3->event = smfel2->event;
		    smfel3->next = smfel->event->pos_event;
		    smfel->event->pos_event = smfel3;
		}
	    }
	}
	for (smfal = smfAction_list[ndx]; smfal && !smfel3->event; smfal = smfal->next) {
    	    if (smfal->action->event) {
		if (*p == '!') {
		    if (!strcmp(smfal->action->event->name, (p + 1)) ||
			((p2 = strchr(smfal->action->event->name, '(')) &&
		        !strncmp(smfal->action->event->name, (p + 1), p2 - smfal->action->event->name))) {
    			smfel3->event = smfal->action->event;
			smfel3->next = smfel->event->neg_event;
			smfel->event->neg_event = smfel3;
		    }
		}
		else {
		    if (!strcmp(smfal->action->event->name, p) ||
			((p2 = strchr(smfal->action->event->name, '(')) &&
		        !strncmp(smfal->action->event->name, p, p2 - smfal->action->event->name))) {
    			smfel3->event = smfal->action->event;
			smfel3->next = smfel->event->pos_event;
			smfel->event->pos_event = smfel3;
		    }
		}
	    }
	}
	if (!smfel3->event) {
	    smfError(0, "smfconfig: unknown event %s (string %d in %s)", p, config->str_num, config->name);
	    return -1;
	}
    }
    return 0;
}

char* smfPerlRegex(char *pattern, char *buf) {
    char *pi, *pp, *po;
    int bracket_open = 0, i;

    for (pi = pattern, pp = pattern, po = buf; *pi; pp = pi++) {
	*po++ = *pi;
	/* unescaped left-bracket, that means begin of bracket expresion */
	if (*pi == '[' && *pp != '\\')
	    bracket_open = 1;
	else
	/* unescaped right-bracket, that means end of bracket expression */
	if (*pi == ']' && *pp != '\\')
	    bracket_open = 0;
	else
	for (i = 0; regex_perl[i]; i++) {
	    if (*pi == '\\' && *(pi + 1) == *(regex_perl[i])) {
		po--;
		pi++;
		/* the collacting classes must be bracket-enclosed */
		if (!bracket_open && *(regex_collacting[i]) == '[')
		    *po++ = '[';
		strcpy(po, regex_collacting[i]);
		po += strlen(regex_collacting[i]);
		/* the collacting classes must be bracket-enclosed */
		if (!bracket_open && *(regex_collacting[i]) == '[')
		    *(po++) = ']';
		break;
	    }
	}
    }
    *po = '\0';
    return strdup(buf);
}

int smfAddPattern(SMFEVENT *smfe, char *pattern, SMFCONFIG *config) {
    int regexmod = 0, regexopt = 0, rc;
    char *options, *perr;

    options = strrchr(pattern, '/');
    if (*pattern == '/' && options && options > pattern) {
	*options = '\0';
	smfe->pattern = smfPerlRegex(pattern + 1, Buf);
	while (*(++options)) {
	    switch (*options) {
		case 'i':
		    regexmod |= REG_ICASE;
		    break;
		case 'n':
		    regexopt |= REGEX_OPT_NEGATIVE;
		    break;
		case 's':
		    regexopt |= REGEX_OPT_STRIPTAG;
		    break;
		case 'd':
		    regexopt |= REGEX_OPT_DECODEHTML;
		    break;
		default:
		    smfError(0, "smfconfig: unknown option '%c' for regular expression (string %d in %s)", *options, config->str_num, config->name);
		    return -1;
	    }
	}
    }
    else
	smfe->pattern = smfPerlRegex(pattern, Buf);
    smfe->regexmod = regexmod;
    smfe->regexopt = regexopt;
    /* try to compile just to make sure this pattaern is correct */
    rc = regcomp(&(smfe->preg), smfe->pattern, (smfe->regexmod | REG_NOSUB | REG_EXTENDED));
    if (rc != 0) {
	if ((perr = malloc(MAX_STRING_SIZE))) {
	    regerror(rc, &(smfe->preg), perr, MAX_STRING_SIZE);
	    smfError(0, "smfconfig: regular expression error: %s (string %d in %s)", perr, config->str_num, config->name);
	}
	else
	    smfError(0, "smfconfig: memory exhausted");
	return -1;
    }
    return 0;
}

void smfClearConfig(int ndx) {
    SMFACTIONLIST *aprev, *anext;
    SMFEVENTLIST *eprev, *enext, *eeprev, *eenext;
    SMFHEADERLIST *hprev, *hnext;
    SMFEVENTLIST *headList[5];
    int i;

    headList[0] = smfHost_list[ndx];
    headList[1] = smfAttachment_list[ndx];
    headList[2] = smfMimeType_list[ndx];
    headList[3] = smfBody_list[ndx];
    for (aprev = smfAction_list[ndx], anext = smfAction_list[ndx]; anext; aprev = anext) {
	anext = anext->next;
	free(aprev->action->name);
	if (aprev->action->value)
	    free(aprev->action->value);
	if (aprev->action->list)
	    smfClearACL(aprev->action);
	if (aprev->action->event)
	    free(aprev->action->event);
	free(aprev->action);
	free(aprev);
    }
    for (hprev = smfHeader_list[ndx], hnext = smfHeader_list[ndx]; hnext; hprev = hnext) {
	hnext = hnext->next;
	free(hprev->name);
	for (eprev = hprev->entr, enext = hprev->entr; enext; eprev = enext) {
	    enext = enext->next;
	    free(eprev->event->name);
	    free(eprev->event->pattern);
	    regfree(&(eprev->event->preg));
	    for (eeprev = eprev->event->pos_event, eenext = eprev->event->pos_event; eenext; eeprev = eenext) {
		eenext = eenext->next;
		free(eeprev);
	    }
	    for (eeprev = eprev->event->neg_event, eenext = eprev->event->neg_event; eenext; eeprev = eenext) {
		eenext = eenext->next;
		free(eeprev);
	    }
	    for (aprev = eprev->event->action, anext = eprev->event->action; anext; aprev = anext) {
		anext = anext->next;
		free(aprev);
	    }
	    free(eprev->event);
	    free(eprev);
	}
	free(hprev);
    }
    for (i = 0; i < 4; i++)
	for (eprev = headList[i], enext = headList[i]; enext; eprev = enext) {
	    enext = enext->next;
	    free(eprev->event->name);
	    free(eprev->event->pattern);
	    regfree(&(eprev->event->preg));
	    for (eeprev = eprev->event->pos_event, eenext = eprev->event->pos_event; eenext; eeprev = eenext) {
		eenext = eenext->next;
		free(eeprev);
	    }
	    for (aprev = eprev->event->action, anext = eprev->event->action; anext; aprev = anext) {
		anext = anext->next;
		free(aprev);
	    }
	    free(eprev->event);
	    free(eprev);
	}
    smfAction_list[ndx] = NULL;
    smfHeader_list[ndx] = NULL;
    smfHost_list[ndx] = NULL;
    smfAttachment_list[ndx] = NULL;
    smfMimeType_list[ndx] = NULL;
    smfBody_list[ndx] = NULL;
}

int smfParseConfig(char* cfile, int ndx) {
    char *p, *p2, *p3, *p4, *pattern, *event, *action, *buf, *lasts;
    int i, chapter;
    struct rlimit rlp;
    FILE *fp;
    SMFCONFIG *config, *config_prev, *config_next;
    SMFACTION *smfa;
    SMFACTIONLIST *smfal, *smfal2;
    SMFHEADERLIST *smfhl, *smfhl2;
    SMFEVENT *smfe;
    SMFEVENTLIST *smfel, *smfel2, *smfel3;

    /* just init appr. pointers */
    smfAction_list[ndx] = NULL;
    smfHeader_list[ndx] = NULL;
    smfHost_list[ndx] = NULL;
    smfAttachment_list[ndx] = NULL;
    smfMimeType_list[ndx] = NULL;
    smfBody_list[ndx] = NULL;
    if (globalQuarantineDir) {
	free(globalQuarantineDir);
	globalQuarantineDir = NULL;
    }
    if (!(buf = (char*) malloc(MAX_STRING_SIZE + 1))) {
	smfError(0, "smfconfig: memory exhausted");
	return -1;
    }
    if (!(fp = fopen(cfile, "r"))) {
	smfError(0, "smfconfig: config file %s is inaccessible.", cfile);
	return -1;
    }
    if (!(config = (SMFCONFIG*) malloc(sizeof(SMFCONFIG)))) {
	smfError(0, "smfconfig: memory exhausted");
	return -1;
    }
    config->fp = fp;
    config->name = strdup(cfile);
    config->str_num = 0;
    config->next = NULL;
    /* first pass, just get all actions and collect names for further links */
    while (config) {
	if (fgets(buf, MAX_STRING_SIZE, config->fp))
	    config->str_num++;
	else {
	    free(config->name);
	    if (config->fp != fp)
		fclose(config->fp);
	    config_prev = config;
	    config = config->next;
	    free(config_prev);
	    continue;
	}
	p = buf;
	for (i = 0; *(buf + i); i++) {
	    if (*(buf + i) == '\n')
		*(buf + i) = 0;
	    if (p == (buf + i) && (*(buf + i) == '\t' || *(buf + i) == ' '))
		p++; /* skip all spaces in a front */
	}
	if (*p == '#') continue; /* skip comments */
	if ((*buf)) {
	    if (!strncmp(p, INCLUDE_DIRECTIVE, sizeof(INCLUDE_DIRECTIVE) - 1)) {
		if ((p = strtok_r(p, " \t", &lasts)) && (p2 = strtok_r(NULL, "", &lasts))) {
		    if (!(config_next = (SMFCONFIG*) malloc(sizeof(SMFCONFIG)))) {
			smfError(0, "smfconfig: memory exhausted");
			return -1;
		    }
		    if (!(config_next->fp = fopen(p2, "r"))) {
			smfError(0, "smfconfig: INCLUDE file %s is inaccessible (string %d in %s).", p2, config->str_num, config->name);
			free(config_next);
			return -1;
		    }
		    config_next->name = strdup(p2);
		    config_next->str_num = 0;
		    config_next->next = config;
		    config = config_next;
		    continue;
		} else {
		    smfError(0, "smfconfig: incomplete INCLUDE directive (string %d in %s).", config->str_num, config->name);
		    return -1;
		}
	    }
	    for (i = 0; i < C_NUMBER; i++)
		if (!strcasecmp(buf, chapter_list[i].name)) {
		    chapter = chapter_list[i].type;
		    break;
		}
	    if (i == C_NUMBER) {
		switch (chapter) {
		    case C_GLOBAL:
			if ((p = strtok_r(buf, ":", &lasts)) && (p2 = strtok_r(NULL, "", &lasts))) {
			    if (!strcmp(p, "tmp")) {
				if (tmpDir)
				    free(tmpDir);
				tmpDir = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "log")) {
				if (logFile)
				    free(logFile);
				logFile = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "debug")) {
				if (debugFile)
				    free(debugFile);
				debugFile = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "quarantine")) {
				if (globalQuarantineDir)
				    free(globalQuarantineDir);
				globalQuarantineDir = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "user")) {
				if (setUser)
				    free(setUser);
				setUser = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "group")) {
				if (setGroup)
				    free(setGroup);
				setGroup = strdup(p2);
			    }
			    else
			    if (!strcmp(p, "max_nofiles")) {
				max_nofiles = atoi(p2);
				if (max_nofiles < 0) {
				    smfError(0, "smfconfig: max_nofiles parameter must be positive (string %d in %s)", config->str_num, config->name);
				    return -1;
				}
				if (getrlimit(RLIMIT_NOFILE, &rlp)) {
				    smfError(0, "smfconfig: getrlimit() error (string %d in %s)", config->str_num, config->name);
				    return -1;
				}
				if (max_nofiles > rlp.rlim_max) {
				    smfError(0, "smfconfig: max_nofiles > OS limit (%d) (string %d in %s)", rlp.rlim_max, config->str_num, config->name);
				    return -1;
				}
			    }
			    else
			    if (!strcmp(p, "max_soconn")) {
				max_soconn = atoi(p2);
				if (max_soconn < MI_SOMAXCONN) {
				    smfError(0, "smfconfig: max_soconn parameter must be at least %d (string %d in %s)", MI_SOMAXCONN, config->str_num, config->name);
				    return -1;
				}
			    }
			    else
			    if (!strcmp(p, "max_parse_txt")) {
				max_parse_txt = atoi(p2);
			    }
			    else {
				smfError(0, "smfconfig: unknown parameter %s (string %d in %s)", p, config->str_num, config->name);
				return -1;
			    }
			}
			else {
			    smfError(0, "smfconfig: wrong parameter definition (string %d in %s)", config->str_num, config->name);
			    return -1;
			}
			break;
		    case C_HEADERS:
			if (isalpha((int) *(p2 = buf)) &&
			    (p = strtok_r(buf, ":", &lasts))) { /* new header */
			    for (smfhl = smfHeader_list[ndx]; smfhl; smfhl = smfhl->next) {
				if (!strcasecmp(smfhl->name, p)) {
				    smfError(0, "smfconfig: duplicated header %s (string %d in %s)", p, config->str_num, config->name);
				    return -1;
				}
			    }
			    if (!(smfhl = (SMFHEADERLIST*) malloc(sizeof(SMFHEADERLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfhl->name = strdup(p);
			    smfhl->entr = NULL;
			    smfhl->next = smfHeader_list[ndx];
			    smfHeader_list[ndx] = smfhl;
			    if (!(p2 = strtok_r(NULL, "", &lasts)))
				break;
    			}
			if (!(smfhl = smfHeader_list[ndx])) {
			    smfError(0, "smfconfig: undefined header (string %d in %s)", config->str_num, config->name);
			    return -1;
			}
			while (*p2 != '\0' && (*p2 == ' ' || *p2 == '\t')) p2++;
			if ((p = strtok_r(p2, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern  = strtok_r(NULL, "", &lasts))) {
			    if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT))) ||
				!(smfel = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfe->name = strdup(p);
			    if (smfAddPattern(smfe, pattern, config) == -1)
				return -1;
			    smfe->action = NULL;
			    smfe->pos_event = NULL;
			    smfe->neg_event = NULL;
			    smfe->chapter = C_HEADERS;
			    smfel->event = smfe;
			    smfel->next = smfhl->entr;
			    smfhl->entr = smfel;
			}
			break;
		    case C_ATTACHMENTS:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT))) ||
				!(smfel = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfe->name = strdup(p);
			    if (smfAddPattern(smfe, pattern, config) == -1)
				return -1;
			    smfe->action = NULL;
			    smfe->pos_event = NULL;
			    smfe->neg_event = NULL;
			    smfe->chapter = C_ATTACHMENTS;
			    smfel->event = smfe;
			    smfel->next = smfAttachment_list[ndx];
			    smfAttachment_list[ndx] = smfel;
			}
			break;
		    case C_MIMETYPES:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT))) ||
				!(smfel = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfe->name = strdup(p);
			    if (smfAddPattern(smfe, pattern, config) == -1)
				return -1;
			    smfe->action = NULL;
			    smfe->pos_event = NULL;
			    smfe->neg_event = NULL;
			    smfe->chapter = C_MIMETYPES;
			    smfel->event = smfe;
			    smfel->next = smfMimeType_list[ndx];
			    smfMimeType_list[ndx] = smfel;
			}
			break;
		    case C_HOSTS:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT))) ||
				!(smfel = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfe->name = strdup(p);
			    if (smfAddPattern(smfe, pattern, config) == -1)
				return -1;
			    smfe->action = NULL;
			    smfe->pos_event = NULL;
			    smfe->neg_event = NULL;
			    smfe->chapter = C_HOSTS;
			    smfel->event = smfe;
			    smfel->next = smfHost_list[ndx];
			    smfHost_list[ndx] = smfel;
			}
			break;
		    case C_BODY:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT))) ||
				!(smfel = (SMFEVENTLIST*) malloc(sizeof(SMFEVENTLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    smfe->name = strdup(p);
			    if (smfAddPattern(smfe, pattern, config) == -1)
				return -1;
			    smfe->action = NULL;
			    smfe->pos_event = NULL;
			    smfe->neg_event = NULL;
			    smfe->chapter = C_BODY;
			    smfel->event = smfe;
			    smfel->next = smfBody_list[ndx];
			    smfBody_list[ndx] = smfel;
			}
			break;
		    case C_ACTIONS:
			if ((p = strtok_r(buf, ":", &lasts)) && (p2 = strtok_r(NULL, ":", &lasts))) {
			    if (!(smfa = (SMFACTION*) malloc(sizeof(SMFACTION))) ||
				!(smfal = (SMFACTIONLIST*) malloc(sizeof(SMFACTIONLIST)))) {
				smfError(0, "smfconfig: memory exhausted");
				return -1;
			    }
			    for (i = 0; actions[i]; i++)
				if (!strcmp(p2, actions[i])) {
				    smfa->name = strdup(p);
				    smfa->opcode = actions_id[i];
				    break;
				}
			    if (!actions[i]) {
				smfError(0, "smfconfig: unknown action %s (string %d in %s)", p2, config->str_num, config->name);
				return -1;
			    }
			    if ((p = strtok_r(NULL, "", &lasts)))
				smfa->value = strdup(p);
			    else
				smfa->value = NULL;
			    smfa->list = NULL;
			    if (smfa->opcode == A_ACL && smfa->value)
				if (smfAddACL(smfa, config) == -1)
				    return -1;
			    smfa->final = NULL;
			    smfa->event = NULL;
			    smfal->action = smfa;
			    smfal->next = smfAction_list[ndx];
			    smfAction_list[ndx] = smfal;
			    if (smfa->opcode == A_PROGRAM || smfa->opcode == A_RBL || smfa->opcode == A_RESOLV || A_ACL) {
				if (!(smfe = (SMFEVENT*) malloc(sizeof(SMFEVENT)))) {
				    smfError(0, "smfconfig: memory exhausted");
				    return -1;
				}
				smfe->name = smfa->name;
				smfe->pattern = NULL;
				smfe->action = NULL;
				smfe->pos_event = NULL;
				smfe->neg_event = NULL;
				smfa->event = smfe;
			    }
			} else {
			    smfError(0, "smfconfig: wrong action entry %s (string %d in %s)", buf, config->str_num, config->name);
			    return -1;
			}
		}
	    }
	}
    }
    /* second pass, just verify and link all names */
    if (!(config = (SMFCONFIG*) malloc(sizeof(SMFCONFIG)))) {
	smfError(0, "smfconfig: memory exhausted");
	return -1;
    }
    fseek(fp, 0, SEEK_SET);
    config->fp = fp;
    config->name = strdup(cfile);
    config->str_num = 0;
    config->next = NULL;
    smfhl = NULL;
    while (config) {
	if (fgets(buf, MAX_STRING_SIZE, config->fp))
	    config->str_num++;
	else {
	    free(config->name);
	    fclose(config->fp);
	    config_prev = config;
	    config = config->next;
	    free(config_prev);
	    continue;
	}
	p = buf;
	for (i = 0; *(buf + i); i++) {
	    if (*(buf + i) == '\n')
		*(buf + i) = 0;
	    if (p == (buf + i) && (*(buf + i) == '\t' || *(buf + i) == ' '))
		p++; /* skip all spaces in a front */
	}
	if (*p == '#') continue; /* skip comments */
	if ((*buf)) {
	    if (!strncmp(p, INCLUDE_DIRECTIVE, sizeof(INCLUDE_DIRECTIVE) - 1)) {
		if ((p = strtok_r(p, " \t", &lasts)) && (p2 = strtok_r(NULL, "", &lasts))) {
		    if (!(config_next = (SMFCONFIG*) malloc(sizeof(SMFCONFIG)))) {
			smfError(0, "smfconfig: memory exhausted");
			return -1;
		    }
		    if (!(config_next->fp = fopen(p2, "r"))) {
			smfError(0, "smfconfig: INCLUDE file %s is inaccessible (string %d in %s).", p2, config->str_num, config->name);
			free(config_next);
			return -1;
		    }
		    config_next->name = strdup(p2);
		    config_next->str_num = 0;
		    config_next->next = config;
		    config = config_next;
		    continue;
		} else {
		    smfError(0, "smfconfig: incomplete INCLUDE directive (string %d in %s).", config->str_num, config->name);
		    return -1;
		}
	    }
	    for (i = 0; i < C_NUMBER; i++)
		if (!strcasecmp(buf, chapter_list[i].name)) {
		    chapter = chapter_list[i].type;
		    break;
		}
	    if (i == C_NUMBER) {
		switch (chapter) {
		    /* skip "global" chapter on the second pass */
		    case C_GLOBAL:
		    case C_ACTIONS:
			if ((p = strtok_r(buf, ":", &lasts)) && (p2 = strtok_r(NULL, ":", &lasts))) {
			    if ((p3 = strchr(p, '(')) && (p4 = strrchr(p, ')'))) {
				for (smfal = smfAction_list[ndx]; smfal; smfal = smfal->next) {
				    if (!strcmp(p, smfal->action->name)) {
					if (smfal->action->opcode == A_PROGRAM ||
					    smfal->action->opcode == A_RBL ||
					    smfal->action->opcode == A_RESOLV ||
					    smfal->action->opcode == A_ACL ||
					    smfal->action->opcode == A_QUARANTINE) {
					    *p4 = '\0';
					    p3++;
					    for (smfal2 = smfAction_list[ndx]; smfal2; smfal2 = smfal2->next) {
						if (!strcmp(p3, smfal2->action->name)) {
						    if ((smfal->action->opcode == A_PROGRAM &&
							 smfal2->action->opcode != A_PROGRAM) ||
							(smfal->action->opcode == A_RBL &&
							 smfal2->action->opcode != A_RBL) ||
							(smfal->action->opcode == A_RESOLV &&
							 smfal2->action->opcode != A_RESOLV) ||
							(smfal->action->opcode == A_ACL &&
							 smfal2->action->opcode != A_ACL) ||
							(smfal->action->opcode == A_QUARANTINE &&
							 (smfal2->action->opcode == A_DISCARD ||
							  smfal2->action->opcode == A_REJECT ||
							  smfal2->action->opcode == A_NULL))) {
							smfal->action->final = smfal2->action;
							break;
						    }
						    else {
							smfError(0, "smfconfig: unappropriate action for %s (string %d in %s)", p, config->str_num, config->name);
							return -1;
						    }
						}
					    }
					    break;
					}
					else {
					    smfError(0, "smfconfig: you cannot use an action %s with an argument (string %d in %s)", p, config->str_num, config->name);
					    return -1;
					}
				    }
				}
			    }
			}
			break;
		    case C_HEADERS:
			if (isalpha((int) *(p2 = buf)) &&
			    (p = strtok_r(buf, ":", &lasts))) {
			    for (smfhl = smfHeader_list[ndx]; smfhl; smfhl = smfhl->next)
				if (!strcmp(smfhl->name, p))
				    break;
			    if (!(p2 = strtok_r(NULL, "", &lasts)))
				break;
    			}
			while (*p2 != '\0' && (*p2 == ' ' || *p2 == '\t')) p2++;
			if (smfhl && (p = strtok_r(p2, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern  = strtok_r(NULL, "", &lasts))) {
			    for (smfel = smfhl->entr; smfel; smfel = smfel->next)
				if (!strcmp(smfel->event->name, p)) {
				    if (smfAddEvent(smfel, event, ndx, config) == -1 ||
					smfAddAction(smfel, action, ndx, config) == -1)
					return -1;
				    break;
				}
			}
			break;
		    case C_HOSTS:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    for (smfel = smfHost_list[ndx]; smfel; smfel = smfel->next)
				if (!strcmp(smfel->event->name, p)) {
				    if (smfAddEvent(smfel, event, ndx, config) == -1 ||
					smfAddAction(smfel, action, ndx, config) == -1)
					return -1;
				    break;
				}
			}
			break;
		    case C_ATTACHMENTS:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    for (smfel = smfAttachment_list[ndx]; smfel; smfel = smfel->next)
				if (!strcmp(smfel->event->name, p)) {
				    if (smfAddEvent(smfel, event, ndx, config) == -1 ||
					smfAddAction(smfel, action, ndx, config) == -1)
					return -1;
				    break;
				}
			}
			break;
		    case C_MIMETYPES:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    for (smfel = smfMimeType_list[ndx]; smfel; smfel = smfel->next)
				if (!strcmp(smfel->event->name, p)) {
				    if (smfAddEvent(smfel, event, ndx, config) == -1 ||
					smfAddAction(smfel, action, ndx, config) == -1)
					return -1;
				    break;
				}
			}
			break;
		    case C_BODY:
			if ((p = strtok_r(buf, ":", &lasts)) && (event = strtok_r(NULL, ":", &lasts)) &&
			    (action  = strtok_r(NULL, ":", &lasts)) && (pattern = strtok_r(NULL, "", &lasts))) {
			    for (smfel = smfBody_list[ndx]; smfel; smfel = smfel->next)
				if (!strcmp(smfel->event->name, p)) {
				    if (smfAddEvent(smfel, event, ndx, config) == -1 ||
					smfAddAction(smfel, action, ndx, config) == -1)
					return -1;
				    break;
				}
			}
		}
	    }
	}
    }
    free(buf);
    if (!tmpDir) {
	smfError(0, "smfconfig: parameter tmp is not defined");
	return -1;
    }
    if (!logFile)
	logFile = "syslog";
    else
	if (!strcmp(logFile, "syslog"))
	    logTOsyslog = 1;
    if (!debugFile)
	debugFile = "syslog";
    if (!strcmp(debugFile, "syslog"))
	debugTOsyslog = 1;
    /* just specify xxfi_flags by necessity */
    for (smfal = smfAction_list[ndx]; smfal; smfal = smfal->next) {
	switch (smfal->action->opcode) {
	    case A_CHGRCPT:
		smfilter.xxfi_flags |= SMFIF_DELRCPT;
	    case A_ADDRCPT:
		smfilter.xxfi_flags |= SMFIF_ADDRCPT;
		break;
	    case A_DELRCPT:
		smfilter.xxfi_flags |= SMFIF_DELRCPT;
		break;
	    case A_ADDHDR:
		smfilter.xxfi_flags |= SMFIF_ADDHDRS;
		break;
	    case A_STRIP:
		smfilter.xxfi_flags |= SMFIF_CHGBODY;
	    case A_DELHDR:
	    case A_CHGHDR:
		smfilter.xxfi_flags |= SMFIF_CHGHDRS;
	}
    }
    return 0;
}

void smfUsage(char *pname) {
    fprintf(stderr, "Usage: %s -p port [-c config] [-d] [-v[1-3]]\n\tor\n", pname);
    fprintf(stderr, "       %s -t [-f message] [-c config] [-v[1-3]]\n", pname);
}

void smfInit() {
    int i;

    smfConfNdx = 0;
    lenStripCType = strlen(StripCType);
    /*  array initializing for configuration lists */
    for (i = 0; i < MAX_CONF_NDX; i++) {
	smfAction_list[i] = NULL;
	smfHeader_list[i] = NULL;
	smfHost_list[i] = NULL;
	smfAttachment_list[i] = NULL;
	smfMimeType_list[i] = NULL;
	smfBody_list[i] = NULL;
    }
    /* array initializing for Base64 decoding */
    memset(B64, 0x80, 256);
    /* array initializing for hex converting */
    memset(HEX, '\0', 256);
    memset(DEC, '\0', 256);
    for (i = 'A'; i <= 'Z'; i++)
	B64[i] = 0 + (i - 'A');
    for (i = 'a'; i <= 'z'; i++)
	B64[i] = 26 + (i - 'a');
    for (i = '0'; i <= '9'; i++) {
	B64[i] = 52 + (i - '0');
	HEX[i] = i - '0';
	DEC[i] = i - '0';
    }
    B64['+'] = 62;
    B64['/'] = 63;
    B64['='] = 0;
    for (i = 'a'; i <= 'f'; i++)
	HEX[i] = i - 'a' + 10;
    for (i = 'A'; i <= 'F'; i++)
	HEX[i] = i - 'A' + 10;
}

/* set effective UID and GIU */
int smfSetUGID(char *user, char *group) {
    struct passwd *uentr = NULL;
    struct group *gentr = NULL;

    if (getuid() && geteuid())
	return 0;
    if (group) {
	if (!(gentr = getgrnam(group))) {
	    fprintf(stderr,"Error: getgrnam(%s): %s\n", group, strerror(errno));
	    return -1;
	}
	if (setregid(gentr->gr_gid, gentr->gr_gid)) {
	    fprintf(stderr,"Error: setregid(%d, %d): %s\n", gentr->gr_gid, gentr->gr_gid, strerror(errno));
	    return -1;
	}
    }
    if (user) {
	if (!(uentr = getpwnam(user))) {
	    fprintf(stderr,"Error: getpwnam(%s): %s\n", user, strerror(errno));
	    return -1;
	}
	if (setreuid(uentr->pw_uid, uentr->pw_uid)) {
	    fprintf(stderr,"Error: setreuid(%d, %d): %s\n", uentr->pw_uid, uentr->pw_uid, strerror(errno));
	    return -1;
	}
    }
    /* paranoid checking */
    if (!getuid() || !geteuid()) {
	fprintf(stderr,"Error: cannot set effective user ID.\n");
	return -1;
    }
    return 0;
}

/* case insensitive strstr. */
char* smfStrCaseStr(char *str1, char *str2) {
    char *ptr1;
    char *ptr2;

    if (str1 == NULL)
	return NULL;
    if (str2 == NULL)
	return str1;
    while (*(ptr1 = str1) && *ptr1 != '\r' && *ptr1 != '\n') {
	for (ptr2 = str2; *ptr1 && *ptr1 != '\r' && *ptr1 != '\n' && *ptr2 && tolower(*ptr1) == tolower(*ptr2); ptr1++, ptr2++);
	if (*ptr2 == '\0')
	    return str1;
	str1++;
    }
    return NULL;
}

/* case insensitive strstr. (ignore CR/NL)*/
char* smfStrCaseStrNL(char *str1, char *str2) {
    char *ptr1;
    char *ptr2;

    if (str1 == NULL)
	return NULL;
    if (str2 == NULL)
	return str1;
    while (*(ptr1 = str1)) {
	for (ptr2 = str2; *ptr1 && *ptr2 && tolower(*ptr1) == tolower(*ptr2); ptr1++, ptr2++);
	if (*ptr2 == '\0')
	    return str1;
	str1++;
    }
    return NULL;
}

/* case insensitive strstr. (ignore CR/NL, fixed length)*/
char* smfStrNCaseStrNL(char *str1, char *str2, size_t len) {
    char *ptr1, *ptr2, *eol;

    eol = str1 + len;
    if (str1 == NULL)
	return NULL;
    if (str2 == NULL)
	return str1;
    while ((ptr1 = str1) < eol) {
	for (ptr2 = str2; ptr1 < eol && *ptr2 && tolower(*ptr1) == tolower(*ptr2); ptr1++, ptr2++);
	if (*ptr2 == '\0')
	    return str1;
	str1++;
    }
    return NULL;
}

void smfReplaceString(char *dstStr, char *srcStr, char *pattern, char *subStr) {
    char *p;

    if (!(p = strstr(srcStr, pattern)) || !(subStr))
	strncpy(dstStr, srcStr, MAX_STRING_SIZE);
    else {
	strncpy(dstStr, srcStr, p - srcStr);
	strncpy(dstStr + (p - srcStr), subStr, MAX_STRING_SIZE - (p - srcStr));
	strncat(dstStr, p + strlen(pattern), MAX_STRING_SIZE - strlen(dstStr));
    }
}

int smfPatternMatch(SMFMESSAGE *message, char *line, SMFEVENT* event) {
    regmatch_t pmatch;
    char *perr = NULL;
    int rc;

    /* don't need to call regcomp() as we compiled it already */
    rc = regexec(&(event->preg), line, 0, &pmatch, 0);
    if (rc != 0 && rc != REG_NOMATCH) {
	if ((perr = malloc(MAX_STRING_SIZE))) {
	    regerror(rc, &(event->preg), perr, MAX_STRING_SIZE);
	    smfLog(message, "error: regexec: %s", perr);
	}
    }
    if (perr)
	free(perr);
    if (event->regexopt & REGEX_OPT_NEGATIVE)
	return rc ^ REG_NOMATCH;
    else
	return rc;
}

char* smfRegexReplace(SMFMESSAGE *message, char *dstStr, char *srcStr, SMFEVENT *event) {
    regex_t preg;
    regmatch_t pmatch[MAX_REGMATCH_NUMBER];
    size_t nmatch = MAX_REGMATCH_NUMBER;
    char pseq[8], *psub, *pin = NULL, *pout = NULL, *perr = NULL, *p = NULL;
    int rc, i;

    rc = regcomp(&preg, event->pattern, (event->regexmod | REG_EXTENDED));
    if (rc != 0) {
	if ((perr = malloc(MAX_STRING_SIZE))) {
	    regerror(rc, &preg, perr, MAX_STRING_SIZE);
	    smfLog(message, "error: regcomp: %s", perr);
	}
    }
    else {
	rc = regexec(&preg, srcStr, MAX_REGMATCH_NUMBER, pmatch, 0);
	if (rc != 0 && rc != REG_NOMATCH) {
	    if ((perr = malloc(MAX_STRING_SIZE))) {
		regerror(rc, &preg, perr, MAX_STRING_SIZE);
		smfLog(message, "error: regexec: %s", perr);
	    }
	}
	else
	    if ((pout = malloc(MAX_STRING_SIZE))) {
		if (rc == 0 && preg.re_nsub > 0 &&
		    (psub = malloc(MAX_STRING_SIZE)) &&
		    (pin = malloc(MAX_STRING_SIZE))) {
		    strncpy(pin, dstStr, MAX_STRING_SIZE);
		    for (i = 1; i <= preg.re_nsub; i++) {
			strncpy(psub, srcStr + pmatch[i].rm_so, pmatch[i].rm_eo - pmatch[i].rm_so);
			*(psub + (pmatch[i].rm_eo - pmatch[i].rm_so)) = '\0';
			sprintf(pseq, "${%d}", i);
			while(strstr(pin, pseq)) {
			    smfReplaceString(pout, pin, pseq, psub);
			    p = pout;
			    pout = pin;
			    pin = p;
			}
		    }
		    smfReplaceString(pout, pin, "${0}", srcStr);
		    free(psub);
		    free(pin);
		} else
		    smfReplaceString(pout, dstStr, "${0}", srcStr);
		p = strdup(pout);
	    }
    }
    regfree(&preg);
    if (perr)
	free(perr);
    if (pout)
	free(pout);
    return p;
}

size_t smfStripHTMLTag(char *dstStr, char *srcStr, size_t in_len) {
    char *p = dstStr, *p1, *p2 = srcStr, *eol;
    size_t out_len;

    eol = srcStr + in_len;
    while ((p1 = (char*) memchr(p2, '<', eol - p2))) {
	memcpy(p, p2, p1 - p2);
	p += p1 - p2;
	if ((p1 = (char*) memchr(p1, '>', eol - p1)))
	    p2 = p1 + 1;
	else
	    p2 = (char*) memchr(p2, '<', eol - p2) + 1;
    }
    if (p2 < eol)
	memcpy(p, p2, eol - p2);
    out_len = (size_t) (p - dstStr) + (size_t) (eol - p2);
    *(dstStr + out_len) = '\0';
    return out_len;
}

size_t smfDecodeHTML(char *dstStr, char *srcStr, size_t in_len) {
    char *p, *eol, c;
    size_t out_len = 0;
    int i;

    eol = srcStr + in_len;
    for (p = srcStr; p < eol; p++) {
	c = *p;
	if (c == '&') {
	    if ((eol - p) > 5 && *(p + 1) == '#' && (*(p + 5) == ';' || *(p + 4) == ';')) {
		if (*(p + 4) == ';') {
		    c = DEC[(int) *(p + 2)] * 10 + DEC[(int) *(p + 3)];
		    i = 4;
		}
		else {
		    c = DEC[(int) *(p + 2)] * 100 + DEC[(int) *(p + 3)] * 10 + DEC[(int) *(p + 4)];
		    i = 5;
		}
		if (c)
		    p += i;
		else
		    c = '&';
	    }
	}
	*(dstStr + out_len++) = c;
    }
    *(dstStr + out_len) = '\0';
    return out_len;
}

char* smfGetDecodedURL(char*  pin, size_t in_len, size_t* out_len) {
    char *pout = NULL, *eol, *p1, *p2, c;
    size_t buf_len = 0;
    int i;

    eol = pin + in_len;
    *out_len = 0;
    p1 = pin;
    while (p2 = smfStrNCaseStrNL(p1, "http://", (eol - p1))) {
	for (p2 += 7; p2 < eol; p2++) {
	    c = *p2;
	    switch (c) {
		case '"':
		case ' ':
		case '\t':
		case '\r':
		case '\n':
		case '>':
		case '<':
		    c = '\0';
		    break;
		case '\0':
		case '+':
		    c = ' ';
		    break;
		case '%':
		    if ((eol - p2) > 2) {
			c = HEX[(int) *(p2 + 1)] * 16 + HEX[(int) *(p2 + 2)];
			if (c)
			    p2 += 2;
			else
			    c = '%';
		    }
		    break;
		case '&':
		    if ((eol - p2) > 5 && *(p2 + 1) == '#' && (*(p2 + 5) == ';' || *(p2 + 4) == ';')) {
			if (*(p2 + 4) == ';') {
			    c = DEC[(int) *(p2 + 2)] * 10 + DEC[(int) *(p2 + 3)];
			    i = 4;
			}
			else {
			    c = DEC[(int) *(p2 + 2)] * 100 + DEC[(int) *(p2 + 3)] * 10 + DEC[(int) *(p2 + 4)];
			    i = 5;
			}
			if (c)
			    p2 += i;
			else
			    c = '&';
		    }
	    }
	    if (c) {
		if ((*out_len) == buf_len) {
		    buf_len = *out_len + MAX_STRING_SIZE;
		    if (!(pout = realloc(pout, buf_len)))
			return NULL;
		}
		*(pout + (*out_len)++) = c;
	    }
	    else
		break;
	}
	p1 = p2;
    }
    return pout;
}

char* smfGetHeaderValue(char *line, char *header, char *buf) {
    char *ptr1, *ptr2;

    if (line == NULL)
	return NULL;
    if (!header)
	ptr1 = line;
    else
	if (!strncasecmp(line, header, strlen(header)))
	    ptr1 = line + strlen(header) + 1;
	else
	    return NULL;
    while (isspace(*ptr1))
	ptr1++;
    ptr2 = ptr1;
    if (*ptr1 == '"') { /* quoted-string */
	ptr2 = ++ptr1;
	while (*ptr1 && *ptr1 != '\r' && *ptr1 != '\n' && *ptr1 != '"')
	    if (*ptr1++ == '\\')
		*ptr1++;
    } else
	while (*ptr1 && !isspace(*ptr1) && *ptr1 != ';' && *ptr1 != '\r' && *ptr1 != '\n')
		ptr1++;
    if (ptr1 > ptr2 && (ptr1 - ptr2) < MAX_STRING_SIZE) {
	strncpy(buf, ptr2, ptr1 - ptr2);
	*(buf + (ptr1 - ptr2)) = '\0';
	return buf;
    } else
	return NULL;
}

char* smfGetHeaderAttribute(char *line, char *attribute, char *buf) {
    char *ptr1, *ptr2;

    if (line == NULL || attribute == NULL)
	return NULL;
    ptr2 = line;
    while ((ptr1 = smfStrCaseStr(ptr2, attribute))) {
	ptr1 += strlen(attribute);
	while (isspace(*ptr1))
	    ptr1++;
	if (*(ptr1++) == '=')
	    break;
	ptr2 += strlen(attribute);
    }
    if (ptr1 == NULL)
	return NULL;
    while (isspace(*ptr1))
	ptr1++;
    ptr2 = ptr1;
    if (*ptr1 == '"') { /* quoted-string */
	ptr2 = ++ptr1;
	while (*ptr1 && *ptr1 != '\r' && *ptr1 != '\n' && *ptr1 != '"')
	    if (*ptr1++ == '\\')
		*ptr1++;
    } else {
	/* special symb. from RFC1521 */
	while (*ptr1 &&
	    *ptr1 != '('  && *ptr1 != ')' &&
	    *ptr1 != '<'  && *ptr1 != '>' &&
	    *ptr1 != '@'  && *ptr1 != ',' &&
	    *ptr1 != ';'  && *ptr1 != ':' &&
	    *ptr1 != '\\' && *ptr1 != '"' &&
	    *ptr1 != '/'  && *ptr1 != '[' &&
	    *ptr1 != ']'  && *ptr1 != '?' &&
	    *ptr1 != '=' && *ptr1 != '\r' &&
	    *ptr1 != '\n')
		ptr1++;
	/* just remove any trailing whitespaces in malformed attribute */
	while (isspace(*(ptr1-1)))
	    ptr1--;
    }
    if (ptr1 > ptr2 && (ptr1 - ptr2) < MAX_STRING_SIZE) {
	strncpy(buf, ptr2, ptr1 - ptr2);
	*(buf + (ptr1 - ptr2)) = '\0';
	return buf;
    } else
	return NULL;
}

size_t smfMimeB64DecodeBuffer(char *srcBuf, char* dstBuf, size_t inpLen) {
    int i, j, c;
    size_t inpRead = 0, outWrite = 0;
    char inp[4], out[3];

    while (inpRead < inpLen) {
	inp[0] = inp[1] = inp[2] = inp[3] = 0;
	for (i = 0; i < 4; i++) {
	    c = *(srcBuf + inpRead++);
	    if ((c == '\n' && (*(srcBuf + inpRead) == '\n' || inpRead == inpLen)) || c == '=')
		break;
	    if (B64[c] == 0x80) {
		/* just ignore all garbage */
		i--;
		continue;
	    }
	    inp[i] = B64[c];
	}
	out[0] = (inp[0] << 2) | (inp[1] >> 4);
	out[1] = (inp[1] << 4) | (inp[2] >> 2);
	out[2] = (inp[2] << 6) | inp[3];
	for (j = 0, i--; j < i; j++)
	    *(dstBuf + outWrite++) = out[j];
    }
    *(dstBuf + outWrite) = '\0';
    return outWrite;
}

size_t smfMimeQPDecodeBuffer(char *srcBuf, char* dstBuf, size_t inpLen) {
    size_t inpRead = 0, outWrite = 0;
    char c;

    for (inpRead = 0; inpRead < inpLen; inpRead++) {
	c = srcBuf[inpRead];
	if (c == '=') {
	    if (inpRead <= (inpLen - 2)) {
		c = HEX[(int) srcBuf[++inpRead]] * 16;
		if (c)
		    c += HEX[(int) srcBuf[++inpRead]];
	    }
	    else
		c = '\0';
	}
	if (c)
	    dstBuf[outWrite++] = c;
    }
    *(dstBuf + outWrite) = '\0';
    return outWrite;
}

char* smfMimeDecodeString(char *srcStr, char *buf) {
    char *p, *enc1, *enc2, *enc3;

    if (!(p = srcStr))
	return NULL;
    while ((enc1 = strstr(p, "=?"))) {
	if (((enc2 = strstr(enc1, "?B?")) ||
	     (enc2 = strstr(enc1, "?b?")) ||
	     (enc2 = strstr(enc1, "?Q?")) ||
	     (enc2 = strstr(enc1, "?q?"))) && (enc3 = strstr(enc2 + 3, "?="))) {
	    strncpy(buf, enc2 + 3, (enc3 - enc2) - 3);
	    *(buf + (enc3 - enc2) - 3) = '\0';
	    if (tolower(*(enc2 + 1)) == 'b')
		smfMimeB64DecodeBuffer(buf, enc1, strlen(buf));
	    else
		smfMimeQPDecodeBuffer(buf, enc1, strlen(buf));
	    p = enc1 + strlen(enc1);
	    strcat(enc1, enc3 + 2);
	}
        else
	    p = enc1 + 2;
    }
    return srcStr;
}

size_t smfParseMimeMultipart(SMFMESSAGE *message, char *boundary, unsigned char *body, size_t bodylen) {
    size_t len = 0, part_len = 0, part_offset = 0;
    int str_len, boundary_len, type = MTYPE_OTHER, encoding = ENC_BINARY;
    unsigned char *ptr1, *ptr2, *headers;
    char *mpart_ctype = NULL, *mpart_enc, *mpart_filename = NULL, *mpart_mimename = NULL, *mpart_boundary = NULL;
    MSGPART *mpart = NULL;

    boundary_len = strlen(boundary);
    for (ptr1 = body; len < bodylen; ptr1 = ptr2) {
	ptr2 = smfGetLine(ptr1, &str_len);
	if (ptr2 > ptr1)
	    len += ptr2 - ptr1;
	else {
	    /* stupid messages with zero symbols in the body, I hate MS. */
	    ptr2++;
	    len++;
	    continue;
	}
	/* check for message part boundary */
	if (*ptr1 == '-' && *(ptr1 + 1) == '-' &&
	    (str_len == (boundary_len + 2) || (str_len == (boundary_len + 4) &&
	    *(ptr1 + 2 + boundary_len) == '-' && *(ptr1 + 3 + boundary_len) == '-')) &&
	    !strncmp(ptr1 + 2, boundary, boundary_len)) {
	    /* message boundary */
	    if (part_offset > 0) {
		/* the begin of the new part and the end of previous one */
		if (!(mpart = (MSGPART*) malloc(sizeof(MSGPART)))) {
		    len = 0;
		    break;
		}
		mpart->type = type;
		mpart->encoding = encoding;
		mpart->headers = headers;
		mpart->body = body + part_offset;
		mpart->body_decoded = mpart->body;
		mpart->body_stripped = mpart->body;
		mpart->bodylen = part_len - 1;
		mpart->bodylen_decoded = mpart->bodylen;
		mpart->bodylen_stripped = mpart->bodylen;
		mpart->mtype = mpart_ctype;
		if (mpart_ctype)
		    mpart_ctype = NULL;
		if (mpart_mimename) {
		    mpart->mimename = smfMimeDecodeString(mpart_mimename, message->buf);
		    mpart_mimename = NULL;
		}
		else {
		    if (mpart->type == MTYPE_TEXT)
			snprintf(message->buf, MAX_STRING_SIZE, "%s.txt", message->queueID);
		    else
			if (mpart->type == MTYPE_HTML)
			    snprintf(message->buf, MAX_STRING_SIZE, "%s.html", message->queueID);
			else
			    snprintf(message->buf, MAX_STRING_SIZE, "%s.bin", message->queueID);
		    mpart->mimename = strdup(message->buf);
		}
		if (mpart_filename) {
		    mpart->filename = smfMimeDecodeString(mpart_filename, message->buf);
		    mpart_filename = NULL;
		}
		else
		    mpart->filename = mpart->mimename;
		mpart->next = message->mpart;
		message->mpart = mpart;
		mpart = NULL;
	    }
	    part_len = 0;
	    part_offset = ptr2 - body;
	    type = MTYPE_OTHER;
	    encoding = ENC_BINARY;
	    if (mpart_boundary)
	        free(mpart_boundary);
	    mpart_boundary = NULL;
	    if (mpart_ctype)
	        free(mpart_ctype);
	    mpart_ctype = NULL;
	    if (mpart_filename)
	        free(mpart_filename);
	    mpart_filename = NULL;
	    if (mpart_mimename)
	        free(mpart_mimename);
	    mpart_mimename = NULL;
	    headers = ptr2;
	    if (str_len == (boundary_len + 2))
		continue;
	    else
		break;
	}
	if (part_len == 0 && part_offset > 0) {
	    /* should be headers here */
	    if (str_len == 0) {
		/* end of headers */
		if (type == MTYPE_MULTIPART && mpart_boundary) {
		    part_len = smfParseMimeMultipart(message, mpart_boundary, ptr2, bodylen - len);
		    if (part_len == 0) {
			len = 0;
			break;
		    }
		    len += part_len;
		    ptr2 += part_len;
		    part_len = 0;
		    part_offset = 0;
		    type = MTYPE_OTHER;
		    encoding = ENC_BINARY;
		    if (mpart_filename)
	    		free(mpart_filename);
		    mpart_filename = NULL;
		    if (mpart_mimename)
	    		free(mpart_mimename);
		    mpart_mimename = NULL;
		    if (mpart_ctype)
	    		free(mpart_ctype);
		    mpart_ctype = NULL;
		    free(mpart_boundary);
		    mpart_boundary = NULL;
		}
		else {
		    part_len = 1;
		    part_offset = ptr2 - body;
		}
		continue;
	    }
	    if (!strncasecmp(ptr1, "Content-Type", 12) &&
		(smfGetHeaderValue(ptr1, "Content-Type", message->buf))) {
		if (mpart_ctype)
		    free(mpart_ctype);
		if (!(mpart_ctype = strdup(message->buf))) {
		    len = 0;
		    break;
		}
		if (!strcasecmp(mpart_ctype, "text/plain") || !strcasecmp(mpart_ctype, "text"))
		    type = MTYPE_TEXT;
		else {
		    if (!strcasecmp(mpart_ctype, "text/html"))
			type = MTYPE_HTML;
		    else {
		        if (!strncasecmp(mpart_ctype, "multipart", 9) &&
			    (smfGetHeaderAttribute(ptr1, "boundary", message->buf) ||
			    (isspace(*ptr2) && smfGetHeaderAttribute(ptr2, "boundary", message->buf)))) {
			    type = MTYPE_MULTIPART;
			    if (mpart_boundary)
				free(mpart_boundary);
			    if (!(mpart_boundary = strdup(message->buf))) {
				len = 0;
				break;
			    }
			} else {
		    	    if (!strncasecmp(mpart_ctype, "message/rfc822", 9)) {
				for (ptr1 = ptr2; len < bodylen; ptr1 = ptr2) {
				    ptr2 = smfGetLine(ptr1, &str_len);
				    if (ptr2 > ptr1) {
					len += ptr2 - ptr1;
					if (str_len == 0)
					    break;
				    }
				    else {
					ptr2++;
					len++;
					continue;
				    }
				}
			    }
			}
		    }
		}
		if (smfGetHeaderAttribute(ptr1, "name", message->buf) ||
		    (isspace(*ptr2) && smfGetHeaderAttribute(ptr2, "name", message->buf))) {
		    if (!(mpart_mimename = strdup(message->buf))) {
			len = 0;
			break;
		    }
		}
	    } else
		if (!strncasecmp(ptr1, "Content-Disposition", 19) &&
		    (smfGetHeaderAttribute(ptr1, "filename", message->buf) ||
		    (isspace(*ptr2) && smfGetHeaderAttribute(ptr2, "filename", message->buf)))) {
		    if (!(mpart_filename = strdup(message->buf))) {
			len = 0;
			break;
		    }
		}
		else
		    if (!strncasecmp(ptr1, "Content-Transfer-Encoding", 25)) {
			if ((mpart_enc = smfGetHeaderValue(ptr1, "Content-Transfer-Encoding", message->buf)))
			    if (!strcasecmp(mpart_enc, "base64"))
				encoding = ENC_BASE64;
			    else
				if (!strcasecmp(mpart_enc, "quoted-printable"))
				    encoding = ENC_QPRINTABLE;
				else
				    encoding = ENC_BINARY;
		    }
	}
	else
	    if (part_offset > 0)
		/* supposed to be the message part body */
		part_len += ptr2 - ptr1;
    }
    /* the treatment for malformed multipart bodies that aren't ended by boundary */
    if (part_len > 0 && part_offset > 0 && len > 0) {
	if ((mpart = (MSGPART*) malloc(sizeof(MSGPART)))) {
	    mpart->type = type;
	    mpart->encoding = encoding;
	    mpart->headers = headers;
	    mpart->body = body + part_offset;
	    mpart->body_decoded = mpart->body;
	    mpart->body_stripped = mpart->body;
	    mpart->bodylen = part_len - 1;
	    mpart->bodylen_decoded = mpart->bodylen;
	    mpart->bodylen_stripped = mpart->bodylen;
	    mpart->mtype = mpart_ctype;
	    if (mpart_ctype)
		mpart_ctype = NULL;
	    if (mpart_mimename) {
		mpart->mimename = smfMimeDecodeString(mpart_mimename, message->buf);
		mpart_mimename = NULL;
	    }
	    else {
		if (mpart->type == MTYPE_TEXT)
		    snprintf(message->buf, MAX_STRING_SIZE, "%s.txt", message->queueID);
		else
		    if (mpart->type == MTYPE_HTML)
			snprintf(message->buf, MAX_STRING_SIZE, "%s.html", message->queueID);
		    else
			snprintf(message->buf, MAX_STRING_SIZE, "%s.bin", message->queueID);
		mpart->mimename = strdup(message->buf);
	    }
	    if (mpart_filename) {
		mpart->filename = smfMimeDecodeString(mpart_filename, message->buf);
		mpart_filename = NULL;
	    }
	    else
		mpart->filename = mpart->mimename;
	    mpart->next = message->mpart;
	    message->mpart = mpart;
	    mpart = NULL;
	}
	else
	    len = 0;
    }
    if (mpart)
	free(mpart);
    if (mpart_mimename)
	free(mpart_mimename);
    if (mpart_filename)
	free(mpart_filename);
    if (mpart_ctype)
	free(mpart_ctype);
    if (mpart_boundary)
	free(mpart_boundary);
    if (!len) {
	smfLog(message, "error: broken multipart body or memory exhausted");
	syslog(LOG_ERR, "broken multipart body or memory exhausted");
    }
    return len;
}

void smfQuarantineAction(SMFMESSAGE *message, char *event, char *dir) {
    MSGHEADER *header, *ph1 = NULL, *ph2 = NULL;
    char *pdir = message->buf + (MAX_STRING_SIZE/2);
    time_t now;
    FILE *fp;

    now = time(0);
    strftime(pdir, MAX_STRING_SIZE/2, dir, localtime(&now));
    (void) mkdir(pdir, 0755);
    if (*(pdir + strlen(pdir) - 1) == '/')
	snprintf(message->buf, MAX_STRING_SIZE/2, "%s%s.%s", pdir, event, message->queueID);
    else
	snprintf(message->buf, MAX_STRING_SIZE/2, "%s/%s.%s", pdir, event, message->queueID);
    if ((fp = fopen(message->buf, "w"))) {
	/* just reconstruct links to the original order of headers */
	for (header = message->header; header; header = ph1) {
	    ph1 = header->next;
	    header->next = ph2;
	    ph2 = header;
	}
	message->header = ph2;
	for (header = message->header; header; header = header->next)
	    fprintf(fp, "%s: %s\r\n", header->name, header->value);
	fprintf(fp, "\r\n");
	if (message->body && message->bodylen > 0)
	    if (!fwrite(message->body, message->bodylen, 1, fp))
		smfLog(message, "error: can't write quarantine file for event %s", event);
	fclose(fp);
    } else
	smfLog(message, "error: can't create quarantine file in %s for event %s", pdir, event);
}

/* this custom popen function prevents running shell */
FILE *smfPopen(SMFMESSAGE *message, char *program, char *type) {
    char *cp, *argv[MAX_PROGRAM_ARGUMENTS], *lasts;
    int argc, pdes[2];
    pid_t pid = 0;
    FILE *fp = NULL;

    if (((*type != 'r') && (*type != 'w')) || type[1])
	return (NULL);
    if (pipe(pdes) < 0)
	return (NULL);
    for (argc = 0, cp = program; argc < MAX_PROGRAM_ARGUMENTS; cp = NULL)
	if (!(argv[argc++] = strtok_r(cp, " \t", &lasts)))
	    break;
    argv[argc - 1] = NULL;
    pid = vfork();
    switch(pid) {
	case -1: /* error */
	    close(pdes[0]);
	    close(pdes[1]);
	    break;
	case 0: /* child */
	    if (*type == 'r') {
		if (pdes[1] != STDOUT_FILENO) {
		    dup2(pdes[1], STDOUT_FILENO);
		    close(pdes[1]);
		}
		dup2(STDOUT_FILENO, STDERR_FILENO);
		close(pdes[0]);
	    }
	    else {
		if (pdes[0] != STDIN_FILENO) {
		    dup2(pdes[0], STDIN_FILENO);
		    close(pdes[0]);
		}
		close(pdes[1]);
	    }
	    execv(argv[0], argv);
	    exit(255);
	default: /* parent */
	    if (*type == 'r') {
		fp = fdopen(pdes[0], type);
		close(pdes[1]);
	    } else {
		fp = fdopen(pdes[1], type);
		close(pdes[0]);
	    }
    }
    message->program = pid;
    return fp;
}

int smfPclose(SMFMESSAGE *message, FILE *fp) {
    int status = 0;

    if (message->program <= 0)
	return -1;
    fclose(fp);
    message->program = waitpid(message->program, &status, 0);
    if (message->program < 0)
	return -1;
    if (WIFEXITED(status))
	return (WEXITSTATUS(status));
    return 0;
}

sfsistat smfProgramAction(SMFMESSAGE *message, MSGPART *mpart, char *program) {
    FILE *fp;
    char *outMsg = NULL, *filename = NULL;
    unsigned char *body = NULL;
    int msgSize, logSize = 0;
    size_t bodylen = 0;
    sfsistat status = -1;

    if (mpart->encoding == ENC_BASE64) {
	if ((body = malloc(mpart->bodylen)))
	    bodylen =  smfMimeB64DecodeBuffer(mpart->body, body, mpart->bodylen);
    }
    else
    if (mpart->encoding == ENC_QPRINTABLE) {
	if ((body = malloc(mpart->bodylen)))
	    bodylen =  smfMimeQPDecodeBuffer(mpart->body, body, mpart->bodylen);
    }
    else {
	if (mpart->bodylen > 0) {
	    body = mpart->body;
	    bodylen = mpart->bodylen;
	}
    }
    if (!body)
        smfLog(message, "error: memory exhausted");
    else
	if (!bodylen)
    	    smfLog(message, "error: file decoding");
	else {
	    if (*(tmpDir + strlen(tmpDir) - 1) == '/')
		sprintf(message->buf, "%s%s.%s", tmpDir, message->queueID, mpart->filename);
	    else
		sprintf(message->buf, "%s/%s.%s", tmpDir, message->queueID, mpart->filename);
	    if ((fp = fopen(message->buf, "w"))) {
		status = fwrite(body, bodylen, 1, fp);
		fclose(fp);
		if (status == 1 && (filename = strdup(message->buf))) {
		    /* let's run the program */
		    smfReplaceString(message->buf, program, "$file", filename);
		    status = -1;
		    if ((fp = smfPopen(message, message->buf, "r")) != NULL) {
			strcpy(message->buf, "program output: ");
			while (fgets(message->buf + 16, MAX_STRING_SIZE - 16, fp) != NULL) {
			    if ((msgSize = strlen(message->buf)) > 16)
				if ((outMsg = realloc(outMsg, logSize + msgSize + 1))) {
				    strncpy(outMsg + logSize, message->buf, msgSize + 1);
				    logSize += msgSize;
				}
			    *(message->buf + 16) = '\0';
			}
			status = ((status = smfPclose(message, fp)) == 255) ? -1 : status;
			if (outMsg) {
			    if (status > 0)
				smfLog(message, outMsg);
			    free(outMsg);
			}
		    }
		    if (status == -1)
			smfLog(message, "error: can't run the program %s", program);
		}
		else
		    smfLog(message, "error: can't write temporary file for the program %s", program);
	    }
	    else
		smfLog(message, "error: can't create temporary file for the program %s", program);
	}
    if (filename) {
	unlink(filename);
	free(filename);
    }
    if (body && body != mpart->body)
	free(body);
    return status;    
}

sfsistat smfDoAction(SMFICTX *ctx, MSGEVENT *event, SMFACTION *action) {
    SMFMESSAGE *message = ((SMFCONNECTION*) smfi_getpriv(ctx))->message;
    MSGEVENT *a_event;
    SMFLIST *smfl;
    char *header, *value = NULL, *p = NULL, *p2, **p3, *lasts;
    size_t nplen, value_len;
    int h_errno;
    unsigned long addr;
    struct hostent h, *ph;
    struct in_addr in;
    sfsistat rc = SMFIS_CONTINUE, status;

    switch (action->opcode) {
	case A_ACCEPT:
	    event->status = E_PROCESSED;
	    rc = SMFIS_ACCEPT;
	    break;
	case A_NULL:
	    event->status = E_PROCESSED;
	    break;
	case A_REJECT:
	    /* just ignore all other rejects if we had one */
	    if (!(message->amask & (A_ACCEPT | A_REJECT))) {
		if (event->part) {
		    smfReplaceString(message->buf, action->value, "$file", event->part->filename);
		    if (!cfgTest)
			smfi_setreply(ctx, "554", "5.7.1", message->buf);
		}
		else
		    if (event->header &&
			(p = smfRegexReplace(message, action->value, event->header->value, event->event))) {
			if (!cfgTest)
			    smfi_setreply(ctx, "554", "5.7.1", p);
			free(p);
		    }
		rc = SMFIS_REJECT;
		message->trigger = event;
	    }
	    event->status = E_PROCESSED;
	    break;
	case A_DISCARD:
	    /* just ignore all other discards */
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD))) {
		rc = SMFIS_DISCARD;
		message->trigger = event;
	    }
	    event->status = E_PROCESSED;
	    break;
	case A_ADDRCPT:
	    if (action->value) {
		if (message->phase == EOM_PHASE) {
		    if (!cfgTest)
			smfi_addrcpt(ctx, action->value);
		    event->status = E_PROCESSED;
		}
		else
		    event->status = E_WAITING;
	    }
	    break;
	case A_CHGRCPT:
	    if (event->header && action->value) {
		if (message->phase == EOM_PHASE) {
		    if ((p = smfRegexReplace(message, action->value, event->header->value, event->event))) {
			if (!cfgTest) {
			    smfi_addrcpt(ctx, p);
			    smfi_delrcpt(ctx, event->header->value);
			}
			free(p);
			event->status = E_PROCESSED;
		    }
		    else
			rc = SMFIS_TEMPFAIL;
		}
		else
		    event->status = E_WAITING;
	    }
	    break;
	case A_DELRCPT:
	    if (event->header) {
		if (message->phase == EOM_PHASE) {
		    if (!cfgTest)
			smfi_delrcpt(ctx, event->header->value);
		    event->status = E_PROCESSED;
		} else
		    event->status = E_WAITING;
	    }
	    break;
	case A_ADDHDR:
	    if (action->value) {
		if (message->phase == EOM_PHASE) {
		    if (event->header &&
			(p = smfRegexReplace(message, action->value, event->header->value, event->event))) {
			strcpy(message->buf, p);
			free(p);
		    }
		    else
			strcpy(message->buf, action->value);
		    if ((header = strtok_r(message->buf, ": ", &lasts)) && (value = strtok_r(NULL, "", &lasts)) && !cfgTest)
			smfi_addheader(ctx, header, value);
		    event->status = E_PROCESSED;
		}
		else
		    event->status = E_WAITING;
	    }
	    break;
	case A_DELHDR:
	    if (event->header) {
		if (message->phase == EOM_PHASE) {
		    if (!cfgTest)
			smfi_chgheader(ctx, event->header->name, event->header->snumber, "");
		    event->status = E_PROCESSED;
		}
		else
		    event->status = E_WAITING;
	    }
	    break;
	case A_CHGHDR:
	    if (event->header && action->value) {
		if (message->phase == EOM_PHASE) {
		    if ((p = smfRegexReplace(message, action->value, event->header->value, event->event))) {
		    if (!cfgTest)
			smfi_chgheader(ctx, event->header->name, event->header->snumber, p);
			free(p);
		    }
		    event->status = E_PROCESSED;
		}
		else
		    event->status = E_WAITING;
	    }
	    break;
	case A_QUARANTINE:
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD | A_QUARANTINE)))
		if (action->value) {
		    if (message->phase == EOM_PHASE) {
			if (event->status == E_TRIGGERED || event->status == E_WAITING) {
			    if (!cfgTest)
				smfQuarantineAction(message, event->event->name, action->value);
			    if (action->final)
				rc = smfDoAction(ctx, event, action->final);
			    else {
				rc = SMFIS_DISCARD;
				message->trigger = event;
				event->status = E_PROCESSED;
			    }
			}
		    }
		    else {
			event->status = E_WAITING;
			return SMFIS_CONTINUE;
		    }
		}
	    break;
	case A_PROGRAM:
	    /* does not make sence to run a program if we had accept or reject */
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD)))
		if (event->part && action->value) {
		    if (event->status == E_TRIGGERED) {
			status = smfProgramAction(message, event->part, action->value);
			if (status > 0) {
			    if (action->final)
				rc = smfDoAction(ctx, event, action->final);
			    /* by default the action is reject if was not specified a final action */
			    else {
				sprintf(message->buf, "Rejected by external scanner %s", action->name);
				if (!cfgTest)
				    smfi_setreply(ctx, "554", "5.7.1", message->buf);
				message->amask |= A_REJECT;
				rc = SMFIS_REJECT;
				message->trigger = event;
			    }
			    if ((a_event = (MSGEVENT*) malloc(sizeof(MSGEVENT)))) {
				a_event->event = action->event;
				a_event->hostaddr = NULL;
				a_event->header = NULL;
				a_event->part = event->part;
				a_event->status = E_PROCESSED;
				a_event->next = message->event;
				message->event = a_event;
				if (debug_level > 0) {
				    if (message->queueID)
					smfPrintDebug(3, message->buf, "smfDoAction(%s): [%s]: Event %s triggered", action->name, message->queueID, action->event->name);
				    else
					smfPrintDebug(3, message->buf, "smfDoAction(%s): Event %s triggered", action->name, action->event->name);
				}
			    }
			}
			else
			    if (status == -1) {
				syslog(LOG_ERR, "Unsuccessful call (%s)", action->value);
				rc = SMFIS_TEMPFAIL;
			    }
			if (event->status != E_WAITING)
			    event->status = E_PROCESSED;
		    }
		    else
			if (event->status == E_WAITING && action->final)
			    rc = smfDoAction(ctx, event, action->final);
		}
	    if (rc == SMFIS_CONTINUE) {
		if (debug_level > 0) {
		    if (message->queueID)
			smfPrintDebug(2, message->buf, "smfDoAction(%s): [%s]: return %s", action->name, message->queueID, smfis_codes[rc]);
		    else
			smfPrintDebug(2, message->buf, "smfDoAction(%s): return %s", action->name, smfis_codes[rc]);
		}
		return rc;
	    }
	    break;
	case A_ACL:
	    if (action->list && (event->header || (event->part && event->part->body))) {
		if (event->status == E_TRIGGERED) {
		    if (event->header) {
			if (!(*(action->value)) ||
			    !(value = smfRegexReplace(message, action->value, event->header->value, event->event)))
			    value = event->header->value;
			value_len = strlen(value);
		    }
		    else {
			if (*(action->value)) {
			    if (!strcasecmp(action->value, "url")) {
				p = smfGetDecodedURL(event->part->body_decoded, event->part->bodylen_decoded, &value_len);
				if (p)
				    value = p;
				else {
				    event->status = E_PROCESSED;
				    break;
				}
			    }
			}
			else {
			    if (event->part->type == MTYPE_HTML &&
				(event->event->regexopt & (REGEX_OPT_STRIPTAG | REGEX_OPT_DECODEHTML))) {
				value = event->part->body_stripped;
				value_len = event->part->bodylen_stripped;
			    }
			    else {
				value = event->part->body_decoded;
				value_len = event->part->bodylen_decoded;
			    }
			}
		    }
		    for (smfl = action->list; smfl; smfl = smfl->next) {
			if (smfStrNCaseStrNL(value, smfl->item, value_len)) {
			    if (action->final)
				rc = smfDoAction(ctx, event, action->final);
			    else /* by default do nothing */
				event->status = E_PROCESSED;
			    if ((a_event = (MSGEVENT*) malloc(sizeof(MSGEVENT)))) {
				a_event->event = action->event;
				a_event->hostaddr = NULL;
				a_event->header = event->header;
				a_event->part = event->part;
				a_event->status = E_PROCESSED;
				a_event->next = message->event;
				message->event = a_event;
				if (debug_level > 0) {
				    if (message->queueID)
					smfPrintDebug(3, message->buf, "smfDoAction(%s): [%s]: Event %s triggered", action->name, message->queueID, action->event->name);
				    else
					smfPrintDebug(3, message->buf, "smfDoAction(%s): Event %s triggered", action->name, action->event->name);
				}
			    }
			    break;
			}
		    }
		    if (p)
			free(p);
		    if (!smfl) /* not found */
			event->status = E_PROCESSED;
		}
		else
		    if (event->status == E_WAITING && action->final)
			rc = smfDoAction(ctx, event, action->final);
	    }
	    break;
	case A_RBL:
	    /* does not make sence to check RBL one more time if we have accept or reject */
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD)))
		if ((event->header || event->hostaddr) && action->value) {
		    if (event->status == E_TRIGGERED) {
			if (event->header)
			    p = smfRegexReplace(message, action->value, event->header->value, event->event);
			else
			    p = smfRegexReplace(message, action->value, event->hostaddr, event->event);
			if (p) {
			    h_errno = 0;
			    /* Below is AFAIK the only platform sensitive stuff */
#if defined(SOLARIS)
			    gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &h_errno);
#else
#ifdef __linux__
			    ph = &h;
			    gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &ph, &h_errno);
#else
#if defined(__FreeBSD__)
			    /* I didn't find reentrant gethostbyname() implementation for FreeBSD.
			       So lets leave FreeBSD without rbl support for a while */
			    h_errno = HOST_NOT_FOUND;
#else
			    /* I just hope other platforms have the same gethostbyname_r() implementation as in Solaris */
			    gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &h_errno);
#endif /* __FreeBSD__ */
#endif /* __linux__ */
#endif /* SOLARIS */
			    if (!h_errno) {
				if (action->final)
				    rc = smfDoAction(ctx, event, action->final);
				/* by default the action is reject if was not specified a final action */
				else {
				    sprintf(message->buf, "Rejected according to the external blackhole list for the name %s", p);
				    if (!cfgTest)
					smfi_setreply(ctx, "554", "5.7.1", message->buf);
				    message->amask |= A_REJECT;
				    rc = SMFIS_REJECT;
				    message->trigger = event;
				}
				if ((a_event = (MSGEVENT*) malloc(sizeof(MSGEVENT)))) {
				    a_event->event = action->event;
				    a_event->hostaddr = NULL;
				    a_event->header = event->header;
				    a_event->part = NULL;
				    a_event->status = E_PROCESSED;
				    a_event->next = message->event;
				    message->event = a_event;
				    if (debug_level > 0) {
					if (message->queueID)
					    smfPrintDebug(3, message->buf, "smfDoAction(%s): [%s]: Event %s triggered", action->name, message->queueID, action->event->name);
					else
					    smfPrintDebug(3, message->buf, "smfDoAction(%s): Event %s triggered", action->name, action->event->name);
				    }
				}
			    } else
				if (h_errno != HOST_NOT_FOUND) {
				    syslog(LOG_ERR, "gethostbyname_r() for %s returned code %d", p, h_errno);
				    rc == SMFIS_TEMPFAIL;
				}
			    free(p);
			}
			else
			    rc == SMFIS_TEMPFAIL;
			if (event->status != E_WAITING)
			    event->status = E_PROCESSED;
		    }
		    else
			if (event->status == E_WAITING && action->final)
			    rc = smfDoAction(ctx, event, action->final);
		}
	    if (rc == SMFIS_CONTINUE) {
		if (debug_level > 0) {
		    if (message->queueID)
			smfPrintDebug(2, message->buf, "smfDoAction(%s): [%s]: return %s", action->name, message->queueID, smfis_codes[rc]);
		    else
			smfPrintDebug(2, message->buf, "smfDoAction(%s): return %s", action->name, smfis_codes[rc]);
		}
		return rc;
	    }
	    break;
	case A_RESOLV:
	    /* does not make sence to check RESOLV or RRESOLV one more time if we have accept or reject */
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD)))
		if (event->header && action->value) {
		    if (event->status == E_TRIGGERED) {
			p = smfRegexReplace(message, action->value, event->header->value, event->event);
			if (p) {
			    p = strtok_r(p, ", ", &lasts);
			    p2 = strtok_r(NULL, ", ", &lasts);
			    h_errno = 0;
			    /* Below is AFAIK the only platform sensitive stuff */
#if defined(SOLARIS)
			    ph = gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &h_errno);
#else
#ifdef __linux__
			    ph = &h;
			    gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &ph, &h_errno);
#else
#if defined(__FreeBSD__)
			    /* I didn't find reentrant gethostbyname() implementation for FreeBSD.
				So lets leave FreeBSD without resolv support for a while */
			    h_errno = HOST_NOT_FOUND;
			    ph = NULL;
#else
			    /* I just hope other platforms have the same gethostbyname_r() implementation as in Solaris */
			    ph = gethostbyname_r(p, &h, message->buf, MAX_STRING_SIZE, &h_errno);
#endif /* __FreeBSD__ */
#endif /* __linux__ */
#endif /* SOLARIS */
			    if (!h_errno && ph) {
			        /* host name is resolvable */
				if (p2) {
				    /* lets compare against second argument */
				    h_errno = HOST_NOT_FOUND;
				    for (p3 = ph->h_addr_list; *p3 != 0; p3++) {
					memcpy(&in.s_addr, *p3, sizeof(in.s_addr));
					if (!strcmp(p2, inet_ntoa(in))) {
					    h_errno = 0;
					    break;
					}
				    }
				}
			    }
			    if (h_errno == HOST_NOT_FOUND) {
				if (action->final) {
				    rc = smfDoAction(ctx, event, action->final);
				}
				/* by default the action is reject if was not specified a final action */
				else {
				    if (p2)
					sprintf(message->buf, "Rejected because of wrong domain name %s for IP-address %s", p, p2);
				    else
					sprintf(message->buf, "Rejected because cannot resolve domain name %s", p);
				    if (!cfgTest)
					smfi_setreply(ctx, "554", "5.7.1", message->buf);
				    message->amask |= A_REJECT;
				    rc = SMFIS_REJECT;
				    message->trigger = event;
				}
				if ((a_event = (MSGEVENT*) malloc(sizeof(MSGEVENT)))) {
				    a_event->event = action->event;
				    a_event->hostaddr = NULL;
				    a_event->header = event->header;
				    a_event->part = NULL;
				    a_event->status = E_PROCESSED;
				    a_event->next = message->event;
				    message->event = a_event;
				    if (debug_level > 0) {
					if (message->queueID)
					    smfPrintDebug(3, message->buf, "smfDoAction(%s): [%s]: Event %s triggered", action->name, message->queueID, action->event->name);
					else
					    smfPrintDebug(3, message->buf, "smfDoAction(%s): Event %s triggered", action->name, action->event->name);
				    }
				}
			    } else
				if (h_errno != 0) {
				    if (action->opcode == A_RESOLV)
					syslog(LOG_ERR, "gethostbyname_r() for %s returned code %d", p, h_errno);
				    else
					syslog(LOG_ERR, "gethostbyaddr_r() for %s returned code %d", p, h_errno);
				    rc == SMFIS_TEMPFAIL;
				}
			    free(p);
			}
			else
			    rc == SMFIS_TEMPFAIL;
			if (event->status != E_WAITING)
			    event->status = E_PROCESSED;
		    }
		    else
			if (event->status == E_WAITING && action->final)
			    rc = smfDoAction(ctx, event, action->final);
		}
	    if (rc == SMFIS_CONTINUE) {
		if (debug_level > 0) {
		    if (message->queueID)
			smfPrintDebug(2, message->buf, "smfDoAction(%s): [%s]: return %s", action->name, message->queueID, smfis_codes[rc]);
		    else
			smfPrintDebug(2, message->buf, "smfDoAction(%s): return %s", action->name, smfis_codes[rc]);
		}
		return rc;
	    }
	    break;
	case A_STRIP:
	    /* does not make any sence to call it second time or after accept/reject */
	    if (!(message->amask & (A_ACCEPT | A_REJECT | A_DISCARD | A_STRIP)))
		if (event->part && action->value) {
		    if (message->phase == EOM_PHASE) {
			event->status = E_PROCESSED;
			smfReplaceString(message->buf, action->value, "$file", event->part->filename);
			strcat(message->buf, "\r\n");
			if (message->type == MTYPE_MULTIPART)
			    nplen = strlen(message->buf) + lenStripCType;
			else
			    nplen = strlen(message->buf);
			/* if a diagnostic message does not fit, just strip an attachment */
			if (((event->part->body - event->part->headers) + event->part->bodylen) < nplen)
			    nplen = 0;
			else
			    if (message->type == MTYPE_MULTIPART) {
				strcpy(event->part->headers, StripCType);
				strcpy(event->part->headers + lenStripCType, message->buf);
			    }
			    else {
				strcpy(event->part->headers, message->buf);
				smfi_chgheader(ctx, "Content-Type", 1, "text/plain");
				smfi_chgheader(ctx, "Content-Transfer-Encoding", 1, "");
			    }
			memmove(event->part->headers + nplen, event->part->body + event->part->bodylen,
				message->bodylen - ((event->part->body - message->body) + event->part->bodylen));
			message->bodylen = message->bodylen - (event->part->body - event->part->headers) - event->part->bodylen + nplen;
			if (message->type == MTYPE_MULTIPART && nplen) {
			    event->part->body = event->part->headers + lenStripCType;
			    event->part->bodylen = nplen - lenStripCType;
			}
			else {
			    event->part->body = event->part->headers;
			    event->part->bodylen = nplen;
			}
			if (!cfgTest)
			    smfi_replacebody(ctx, message->body, message->bodylen);
			smfLog(message, "stripped: Event %s, File %s", event->event->name, event->part->filename);
		    }
		    else {
			event->status = E_WAITING;
			return SMFIS_CONTINUE;
		    }
		} else {
		    if (debug_level > 0) {
			if (message->queueID)
			    smfPrintDebug(2, message->buf, "smfDoAction(%s): [%s]: return %s", action->name, message->queueID, smfis_codes[SMFIS_CONTINUE]);
			else
			    smfPrintDebug(2, message->buf, "smfDoAction(%s): return %s", action->name, smfis_codes[SMFIS_CONTINUE]);
		    }
		    return SMFIS_CONTINUE;
		}
	    break;
    }
    message->amask |= action->opcode;
    if (debug_level > 0) {
	if (message->queueID)
	    smfPrintDebug(2, message->buf, "smfDoAction(%s): [%s]: return %s", action->name, message->queueID, smfis_codes[rc]);
	else
	    smfPrintDebug(2, message->buf, "smfDoAction(%s): return %s", action->name, smfis_codes[rc]);
    }
    return rc;
}

sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *sap) {
    SMFCONNECTION *connection;
    SMFEVENTLIST *smfel;
    MSGEVENT *mevent, *mevent2;
    SMFACTIONLIST *smfal;
    int event_number, event_matched;
    sfsistat status;
    
    static char *func = "mlfi_connect";

    if (!(connection = (SMFCONNECTION*) malloc(sizeof(SMFCONNECTION))))
	return SMFIS_TEMPFAIL;
    if (!(connection->message = (SMFMESSAGE*) malloc(sizeof(SMFMESSAGE)))) {
	free(connection);
	return SMFIS_TEMPFAIL;
    }
    connection->event = NULL;
    connection->message->buf[MAX_STRING_SIZE] = '\0';
    connection->message->queueID = NULL;
    connection->message->log = NULL;
    if (sap)
	connection->hostaddr = strdup(inet_ntoa(((struct sockaddr_in *) sap)->sin_addr));
    else
	connection->hostaddr = strdup(hostname);
    if (debug_level > 0)
	smfPrintDebug(1, connection->message->buf, "%s[%s]: entered", func, connection->hostaddr);
    /* just to make sure we start and finish with the same configuration */
    connection->message->cfgNdx = smfConfNdx;
    connection->message->amask = A_NULL;
    connection->message->rc = SMFIS_CONTINUE;
    smfi_setpriv(ctx, connection);
    /* check if any rules defined in [hosts] section */
    if (smfHost_list[smfConfNdx]) {
	/* should be more comprehensive in the next version as we have sockaddr_in6 */
	/* in case reverse lookup does not work for this host */
	if (hostname && sap && *hostname == '[' && *(hostname + strlen(hostname) - 1) == ']')
	    hostname = connection->hostaddr;
	for (smfel = smfHost_list[smfConfNdx]; smfel; smfel = smfel->next) {
	    if (hostname && !smfPatternMatch(connection->message, hostname, smfel->event) ||
		(!smfPatternMatch(connection->message, connection->hostaddr, smfel->event))) {
		if (!(mevent = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
		    return SMFIS_TEMPFAIL;
		mevent->hostaddr = connection->hostaddr;
		mevent->header = NULL;
		mevent->part = NULL;
		mevent->status = E_MATCHED;
		mevent->event = smfel->event;
		mevent->next = connection->event;
		connection->event = mevent;
		if (debug_level > 0)
		    smfPrintDebug(3, connection->message->buf, "%s[%s]: Event %s triggered", func, connection->hostaddr, smfel->event->name);
	    }
	}
    }
    /* check if we have any events so far */
    for (mevent = connection->event; mevent; mevent = mevent->next) {
	event_number = 0;
	event_matched = 0;
	for (smfel = mevent->event->pos_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    for (mevent2 = connection->event; mevent2; mevent2 = mevent2->next) {
		if (mevent2->event == smfel->event) {
		    event_matched++;
		    break;
		}
	    }
	}
	for (smfel = mevent->event->neg_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    if (smfel->event->chapter == C_HOSTS)
		event_matched++;
	    /* just continue if negative event defined in further chapter */
	    else
		break;
	    for (mevent2 = connection->event; mevent2; mevent2 = mevent2->next) {
		if (mevent2->event == smfel->event) {
		    event_matched--;
		    break;
		}
	    }
	}
	if (event_number && event_number == event_matched) {
	    mevent->status = E_TRIGGERED;
	    for (smfal = mevent->event->action; smfal; smfal = smfal->next) {
		status = smfDoAction(ctx, mevent, smfal->action);
		/* TEMPFAIL has higher priority, then ACCEPT, etc. */
		connection->message->rc = status > connection->message->rc ? status : connection->message->rc;
		/* does not make sence to continue if it's TEMPFAIL or ACCEPT*/
		if (connection->message->rc == SMFIS_TEMPFAIL || connection->message->rc == SMFIS_ACCEPT)
		    return mlfi_cleanup(ctx, connection->message->rc);
	    }
	}
    }
    if (connection->message->rc == SMFIS_CONTINUE)
	return SMFIS_CONTINUE;
    else {
	smfLog(connection->message, "rejected: Event %s, Connection from %s", connection->message->trigger->event->name, connection->hostaddr);
	smfFlushLog(connection->message);
	return mlfi_cleanup(ctx, connection->message->rc);
    }
}

sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom) {
    SMFCONNECTION *connection = (SMFCONNECTION *) smfi_getpriv(ctx);
    SMFMESSAGE *message;
    SMFHEADERLIST *smfhl;
    SMFEVENTLIST *smfel;
    SMFACTIONLIST *smfal;
    MSGHEADER *header;
    MSGEVENT *event;
    sfsistat status;
    char *queueID;
    static char *func = "mlfi_envfrom";

    /* crazy checking, but just in case */
    if (!connection)
	return SMFIS_TEMPFAIL;
    message = connection->message;
    if (!cfgTest) {
	if (!(queueID = smfi_getsymval(ctx,"i"))) {
	    syslog(LOG_ERR,"cannot get queue ID");
	    return SMFIS_TEMPFAIL;
	}
	message->queueID = strdup(queueID);
    }
    else
	message->queueID = strdup("gConfigTest777");
    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    message->event = connection->event;
    message->log = NULL;
    message->body = NULL;
    message->mpart = NULL;
    message->boundary = NULL;
    message->filename = NULL;
    message->mimename = NULL;
    message->header = NULL;
    message->type = MTYPE_TEXT;
    message->mtype = NULL;
    message->encoding = ENC_BINARY;
    message->bodylen = 0;
    message->phase = NEW_PHASE;
    message->amask = A_NULL;
    message->rc = SMFIS_CONTINUE;
    if (!(header = (MSGHEADER*) malloc(sizeof(MSGHEADER))) ||
	!(header->name = strdup(EnvelopeFrom)) || !(header->value = strdup(envfrom[0])))
	return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    header->snumber = 1;
    message->header = header;
    header->next = NULL;
    /* check if EnvelopeFrom rule defined in [headers] section */
    for (smfhl = smfHeader_list[message->cfgNdx]; smfhl; smfhl = smfhl->next) {
	if (!strcasecmp(smfhl->name, EnvelopeFrom)) {
	    for (smfel = smfhl->entr; smfel; smfel = smfel->next) {
		if (!smfPatternMatch(message, envfrom[0], smfel->event)) {
		    if (!(event = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
			return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		    event->event = smfel->event;
		    event->hostaddr = NULL;
		    event->header = header;
		    event->part = NULL;
		    event->status = E_MATCHED;
		    event->next = message->event;
		    message->event = event;
		    if (debug_level > 0)
			smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, envfrom \"%s\"", func, message->queueID, smfel->event->name, envfrom[0]);
		    /* for faster processing if we have the same and the only event as matched in event list */
		    if (smfel->event == smfel->event->pos_event->event &&
			smfel->event->pos_event->next == NULL &&
			smfel->event->neg_event == NULL) {
			event->status = E_TRIGGERED;
			for (smfal = smfel->event->action; smfal; smfal = smfal->next) {
			    status = smfDoAction(ctx, event, smfal->action);
			    /* TEMPFAIL has higher priority, then ACCEPT, etc */
			    message->rc = status > message->rc ? status : message->rc;
			    if (message->rc == SMFIS_TEMPFAIL || message->rc == SMFIS_ACCEPT)
				return mlfi_cleanup(ctx, message->rc);
			}
		    }
		}
	    }
	    break;
	}
    }
    return SMFIS_CONTINUE;
}

sfsistat mlfi_envrcpt(SMFICTX *ctx, char **envrcpt) {
    SMFMESSAGE *message = ((SMFCONNECTION *) smfi_getpriv(ctx))->message;
    SMFHEADERLIST *smfhl;
    SMFEVENTLIST *smfel;
    SMFACTIONLIST *smfal;
    MSGHEADER *header;
    MSGEVENT *event;
    sfsistat status;
    static char *func = "mlfi_envrcpt";

    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    /* save recipient */
    if (!(header = (MSGHEADER*) malloc(sizeof(MSGHEADER))) ||
	!(header->name = strdup(EnvelopeRcpt)) ||
	!(header->value = strdup(envrcpt[0])))
	return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    header->snumber = 1;
    header->next = message->header;
    message->header = header;
    /* check if EnvelopeRcpt rule defined in [headers] section */
    for (smfhl = smfHeader_list[message->cfgNdx]; smfhl; smfhl = smfhl->next) {
	if (!strcasecmp(smfhl->name, EnvelopeRcpt)) {
	    for (smfel = smfhl->entr; smfel; smfel = smfel->next) {
		if (!smfPatternMatch(message, envrcpt[0], smfel->event)) {
		    if (!(event = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
			return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		    event->event = smfel->event;
		    event->hostaddr = NULL;
		    event->header = header;
		    event->part = NULL;
		    event->status = E_MATCHED;
		    event->next = message->event;
		    message->event = event;
		    if (debug_level > 0)
			smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, envrcpt \"%s\"", func, message->queueID, smfel->event->name, envrcpt[0]);
		    /* for faster processing if we have the same and the only event as matched in event list */
		    if (smfel->event == smfel->event->pos_event->event &&
			smfel->event->pos_event->next == NULL &&
			smfel->event->neg_event == NULL) {
			event->status = E_TRIGGERED;
			for (smfal = smfel->event->action; smfal; smfal = smfal->next) {
			    status = smfDoAction(ctx, event, smfal->action);
			    /* TEMPFAIL has higher priority, then ACCEPT, etc */
			    message->rc = status > message->rc ? status : message->rc;
			    if (message->rc == SMFIS_TEMPFAIL || message->rc == SMFIS_ACCEPT)
				return mlfi_cleanup(ctx, message->rc);
			}
		    }
		}
	    }
	    break;
	}
    }
    return SMFIS_CONTINUE;
}

sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) {
    SMFMESSAGE *message = ((SMFCONNECTION *) smfi_getpriv(ctx))->message;
    SMFHEADERLIST *smfhl;
    SMFEVENTLIST *smfel;
    MSGHEADER *header;
    MSGEVENT *event;
    int len, headerlen, str_len;
    char *ctype, *encoding, *mimename = NULL, *filename = NULL, *ptr1, *ptr2;
    static char *func = "mlfi_header";

    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    /* save a header in a private space */
    if (!(header = (MSGHEADER*) malloc(sizeof(MSGHEADER))) ||
	!(header->name = strdup(headerf)) ||
	!(header->value = strdup(headerv)))
	return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    if (message->header && !strcasecmp(message->header->name, header->name))
	header->snumber = message->header->snumber + 1;
    else
	header->snumber = 1;
    header->next = message->header;
    message->header = header;
    headerlen = strlen(headerv);
    /* check for messages type */
    if (!strcasecmp(headerf, "Content-Type")) {
	if ((ctype = smfGetHeaderValue(headerv, NULL, message->buf))) {
	    message->mtype = strdup(ctype);
	    if (!strcasecmp(ctype, "text") || !strcasecmp(ctype, "text/plain"))
		message->type = MTYPE_TEXT;
	    else {
		if (!strcasecmp(ctype, "text/html"))
		    message->type = MTYPE_HTML;
		else {
		    if (!strncasecmp(ctype, "multipart", 9)) {
			for (len = 0, ptr1 = headerv; len < headerlen; ptr1 = ptr2) {
			    ptr2 = smfGetLine(ptr1, &str_len);
			    if (smfGetHeaderAttribute(ptr1, "boundary", message->buf)) {
				if (!(message->boundary = strdup(message->buf)))
				    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
				message->type = MTYPE_MULTIPART;
				break;
			    }
			    len += ptr2 - ptr1;
			}
		    }
		    else {
			message->type = MTYPE_OTHER;
			for (len = 0, ptr1 = headerv; len < headerlen; ptr1 = ptr2) {
			    ptr2 = smfGetLine(ptr1, &str_len);
			    if (mimename = smfGetHeaderAttribute(ptr1, "name", message->buf))
				break;
			    len += ptr2 - ptr1;
			}
			/* lets check mime types */
			for (smfel = smfMimeType_list[message->cfgNdx]; smfel; smfel = smfel->next) {
			    if (!smfPatternMatch(message, message->mtype, smfel->event)) {
				if (!(event = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
				    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
				event->event = smfel->event;
				event->hostaddr = NULL;
				event->header = header;
				event->part = NULL;
				event->status = E_MATCHED;
				event->next = message->event;
				message->event = event;
				if (debug_level > 0)
				    smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, mime type \"%s\"", func, message->queueID, smfel->event->name, message->mtype);
			    }
			}
		    }
		}
	    }
	}
    }
    else {
	if (!strcasecmp(headerf, "Content-Disposition")) {
	    for (len = 0, ptr1 = headerv; len < headerlen; ptr1 = ptr2) {
		ptr2 = smfGetLine(ptr1, &str_len);
		if (filename = smfGetHeaderAttribute(ptr1, "filename", message->buf))
		    break;
		len += ptr2 - ptr1;
	    }
	}
	else {
	    if (!strcasecmp(headerf, "Content-Transfer-Encoding")) {
		if ((encoding = smfGetHeaderValue(headerv, NULL, message->buf))) {
		    if (!strcasecmp(encoding, "base64"))
			message->encoding = ENC_BASE64;
		    else {
			if (!strcasecmp(encoding, "quoted-printable"))
			    message->encoding = ENC_QPRINTABLE;
			else
			    message->encoding = ENC_BINARY;
		    }
		}
	    }
	}
    }
    if (mimename) {
	if (!(message->mimename = strdup(mimename)) ||
	    !(smfMimeDecodeString(message->mimename, message->buf)))
	    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    }
    if (filename) {
	if (!(message->filename = strdup(filename)) ||
	    !(smfMimeDecodeString(message->filename, message->buf)))
	    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    }
    /* check if a rule defined for this header in [headers] section */
    for (smfhl = smfHeader_list[message->cfgNdx]; smfhl; smfhl = smfhl->next) {
	if (!strcasecmp(smfhl->name, headerf)) {
#ifndef DO_NOT_DECODE_SUBJECT
	    if (!strcasecmp(headerf, "Subject"))
		headerv = smfMimeDecodeString(headerv, message->buf);
#endif
	    if (headerv) {
		for (smfel = smfhl->entr; smfel; smfel = smfel->next) {
		    if (!smfPatternMatch(message, headerv, smfel->event)) {
			if (!(event = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
			    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
			event->event = smfel->event;
			event->hostaddr = NULL;
			event->header = header;
			event->part = NULL;
			event->status = E_MATCHED;
			event->next = message->event;
			message->event = event;
			if (debug_level > 0)
			    smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, header \"%s: %s\"", func, message->queueID, smfel->event->name, headerf, headerv);
		    }
		}
	    }
	    break;
	}
    }
    return SMFIS_CONTINUE;
}

sfsistat mlfi_eoh(SMFICTX *ctx) {
    SMFMESSAGE *message = ((SMFCONNECTION *) smfi_getpriv(ctx))->message;
    MSGEVENT *mevent, *mevent2;
    MSGHEADER *mheader;
    SMFEVENT *smfe;
    SMFEVENTLIST *smfel;
    SMFACTIONLIST *smfal;
    int event_number, event_matched;
    sfsistat status;
    static char *func = "mlfi_eoh";

    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    message->phase = EOH_PHASE;
    /* check if we have any events so far */
    for (mevent = message->event; mevent; mevent = mevent->next) {
	event_number = 0;
	event_matched = 0;
	for (smfel = mevent->event->pos_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    for (mevent2 = message->event; mevent2; mevent2 = mevent2->next) {
		if (mevent2->event == smfel->event) {
		    event_matched++;
		    break;
		}
	    }
	}
	for (smfel = mevent->event->neg_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    if (smfel->event->chapter == C_HOSTS || smfel->event->chapter == C_HEADERS)
		event_matched++;
	    /* just continue if negative event defined in further chapter */
	    else
		break;
	    for (mevent2 = message->event; mevent2; mevent2 = mevent2->next) {
		if (mevent2->event == smfel->event) {
		    event_matched--;
		    break;
		}
	    }
	}
	if (event_number && event_number == event_matched) {
	    mevent->status = E_TRIGGERED;
	    for (smfal = mevent->event->action; smfal; smfal = smfal->next) {
		status = smfDoAction(ctx, mevent, smfal->action);
		/* TEMPFAIL has higher priority, then ACCEPT, etc. */
		message->rc = status > message->rc ? status : message->rc;
		/* does not make sence to continue if it's TEMPFAIL or ACCEPT*/
		if (message->rc == SMFIS_TEMPFAIL || message->rc == SMFIS_ACCEPT)
		    return mlfi_cleanup(ctx, message->rc);
	    }
	}
    }
    if (message->rc == SMFIS_REJECT || message->rc == SMFIS_DISCARD)
	mlfi_log(message);
    else
    if (message->rc == SMFIS_CONTINUE) {
	if (!smfBody_list[message->cfgNdx] &&
	    !smfAttachment_list[message->cfgNdx] &&
	    !smfMimeType_list[message->cfgNdx]) {
	    for (mevent = message->event; mevent; mevent = mevent->next)
		if (mevent->status == E_TRIGGERED || mevent->status == E_WAITING)
		    return SMFIS_CONTINUE;
	    message->rc = SMFIS_ACCEPT;
	}
    }
    if (message->rc == SMFIS_CONTINUE)
	return message->rc;
    else
	return mlfi_cleanup(ctx, message->rc);
}

sfsistat mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t bodylen) {
    SMFMESSAGE *message = ((SMFCONNECTION *) smfi_getpriv(ctx))->message;
    static char *func = "mlfi_body";

    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    if (!(message->body = (char*) realloc(message->body, message->bodylen + bodylen + 2)))
	return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
    memcpy((unsigned char*)(message->body + message->bodylen), bodyp, bodylen);
    message->bodylen += bodylen;
    message->body[message->bodylen] = '\0';
    return SMFIS_CONTINUE;
}

sfsistat mlfi_eom(SMFICTX *ctx) {
    SMFMESSAGE *message = ((SMFCONNECTION *) smfi_getpriv(ctx))->message;
    MSGEVENT *mevent, *mevent2;
    MSGHEADER *mheader;
    SMFEVENT *smfe;
    SMFEVENTLIST *smfel;
    SMFACTIONLIST *smfal;
    MSGPART *mpart;
    unsigned char *line;
    char ch;
    size_t mbodylen, processed;
    int event_number, event_matched;
    sfsistat status;
    static char *func = "mlfi_eom";

    if (debug_level > 0)
	smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
    /* Only for debuging purpose. I didn't document "quarantine" parameter for "global" chapter */
    if (globalQuarantineDir)
	smfQuarantineAction(message, "debug", globalQuarantineDir);
    message->phase = EOM_PHASE;
    if (!(message->body))
	return mlfi_cleanup(ctx, SMFIS_ACCEPT);
    if (message->type == MTYPE_MULTIPART && message->boundary) {
	if (!smfParseMimeMultipart(message, message->boundary, message->body, message->bodylen))
	    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
	/* treatment for malformed multipart messages */
	if (!(message->mpart))
	    message->type = MTYPE_TEXT;
    }
    /* this is either single part or malformed multipart body */
    if (!(message->mpart)) {
	/* one part message with type other than multipart */
	if (!(mpart = (MSGPART*) malloc(sizeof(MSGPART))))
	    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
	mpart->type = message->type;
	mpart->mtype = message->mtype;
	mpart->encoding = message->encoding;
	if (!(message->mimename)) {
	    if (message->type == MTYPE_TEXT)
		snprintf(message->buf, MAX_STRING_SIZE, "%s.txt", message->queueID);
	    else
		if (message->type == MTYPE_HTML)
		    snprintf(message->buf, MAX_STRING_SIZE, "%s.html", message->queueID);
		else
		    snprintf(message->buf, MAX_STRING_SIZE, "%s.bin", message->queueID);
	    mpart->mimename = strdup(message->buf);
	}
	else
	    mpart->mimename = strdup(message->mimename);
	if (message->filename)
	    mpart->filename = strdup(message->filename);
	else
	    mpart->filename = mpart->mimename;
	if (!(mpart->mimename))
	    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
	mpart->headers = message->body;
	mpart->body = message->body;
	mpart->body_decoded = mpart->body;
	mpart->body_stripped = mpart->body;
	mpart->bodylen = message->bodylen;
	mpart->bodylen_decoded = mpart->bodylen;
	mpart->bodylen_stripped = mpart->bodylen;
	mpart->next = message->mpart;
	message->mpart = mpart;
    }
    /* check all parts of message body */
    for (mpart = message->mpart; mpart; mpart = mpart->next) {
	/* check all rules that defined for [attachments] section */
	for (smfel = smfAttachment_list[message->cfgNdx]; smfel; smfel = smfel->next) {
	    if (!smfPatternMatch(message, mpart->mimename, smfel->event) ||
		!smfPatternMatch(message, mpart->filename, smfel->event)) {
		if (!(mevent = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
		    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		mevent->event = smfel->event;
		mevent->hostaddr = NULL;
		mevent->header = NULL;
		mevent->part = mpart;
		mevent->status = E_MATCHED;
		mevent->next = message->event;
		message->event = mevent;
		if (debug_level > 0)
		    smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, attached file \"%s\"", func, message->queueID, smfel->event->name, mpart->filename);
	    }
        }
	/* check body rules only for txt or html types */
	if (smfBody_list[message->cfgNdx] && (mpart->type == MTYPE_TEXT || mpart->type == MTYPE_HTML) && mpart->bodylen <= max_parse_txt) {
	    if (mpart->encoding == ENC_BINARY) {
		ch = *(mpart->body + mpart->bodylen);
		*(mpart->body + mpart->bodylen) = '\0';
	    }
	    else {
		if (!(mpart->body_decoded = malloc(mpart->bodylen + 1)))
		    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		if (mpart->encoding == ENC_BASE64)
		    mpart->bodylen_decoded = smfMimeB64DecodeBuffer(mpart->body, mpart->body_decoded, mpart->bodylen);
		else
		    if (mpart->encoding == ENC_QPRINTABLE)
			mpart->bodylen_decoded = smfMimeQPDecodeBuffer(mpart->body, mpart->body_decoded, mpart->bodylen);
	    }
	    for (smfel = smfBody_list[message->cfgNdx]; smfel; smfel = smfel->next) {
		if (mpart->type == MTYPE_HTML &&
		    (smfel->event->regexopt & (REGEX_OPT_STRIPTAG | REGEX_OPT_DECODEHTML))) {
		    if (mpart->body_stripped == mpart->body) {
			if (!(mpart->body_stripped = malloc(mpart->bodylen_decoded + 1)))
			    return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
			mpart->bodylen_stripped = mpart->bodylen_decoded;
			if (smfel->event->regexopt & REGEX_OPT_STRIPTAG) {
			    mpart->bodylen_stripped = smfStripHTMLTag(mpart->body_stripped, mpart->body_decoded, mpart->bodylen_stripped);
			    if (smfel->event->regexopt & REGEX_OPT_DECODEHTML)
				mpart->bodylen_stripped = smfDecodeHTML(mpart->body_stripped, mpart->body_stripped, mpart->bodylen_stripped);
			}
			else
			    if (smfel->event->regexopt & REGEX_OPT_DECODEHTML)
				mpart->bodylen_stripped = smfDecodeHTML(mpart->body_stripped, mpart->body_decoded, mpart->bodylen_stripped);
		    }
		    line = mpart->body_stripped;
		}
		else
		    line = mpart->body_decoded;
		if (!smfPatternMatch(message, line, smfel->event)) {
		    if (!(mevent = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
			return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		    mevent->event = smfel->event;
		    mevent->hostaddr = NULL;
		    mevent->header = NULL;
		    mevent->part = mpart;
		    mevent->status = E_MATCHED;
		    mevent->next = message->event;
		    message->event = mevent;
		    if (debug_level > 0)
			smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered", func, message->queueID, smfel->event->name);
		}
	    }
	    if (mpart->encoding == ENC_BINARY)
		*(mpart->body + mpart->bodylen) = ch;
	}
	/* check all rules that defined for [mime types] section */
	if (mpart->mtype) {
	    for (smfel = smfMimeType_list[message->cfgNdx]; smfel; smfel = smfel->next) {
		if (!smfPatternMatch(message, mpart->mtype, smfel->event)) {
		    if (!(mevent = (MSGEVENT*) malloc(sizeof(MSGEVENT))))
			return mlfi_cleanup(ctx, SMFIS_TEMPFAIL);
		    mevent->event = smfel->event;
		    mevent->hostaddr = NULL;
		    mevent->header = NULL;
		    mevent->part = mpart;
		    mevent->status = E_MATCHED;
		    mevent->next = message->event;
		    message->event = mevent;
		    if (debug_level > 0)
			smfPrintDebug(3, message->buf, "%s[%s]: Event %s triggered, mime type \"%s\"", func, message->queueID, smfel->event->name, mpart->mtype);
		}
	    }
	}
    }
    /* check if we have any events for a whole message so far */
    for (mevent = message->event; mevent; mevent = mevent->next) {
	event_number = 0;
	event_matched = 0;
	for (smfel = mevent->event->pos_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    for (mevent2 = message->event; mevent2; mevent2 = mevent2->next)
		if (mevent2->event == smfel->event) {
		    event_matched++;
		    break;
		}
	}
	for (smfel = mevent->event->neg_event; smfel && event_number == event_matched; smfel = smfel->next) {
	    event_number++;
	    event_matched++;
	    for (mevent2 = message->event; mevent2; mevent2 = mevent2->next)
		if (mevent2->event == smfel->event) {
		    event_matched--;
		    break;
		}
	}
	if (mevent->status != E_PROCESSED && event_number && event_number == event_matched) {
	    if (mevent->status == E_MATCHED)
		mevent->status = E_TRIGGERED;
	    for (smfal = mevent->event->action; smfal; smfal = smfal->next) {
		status = smfDoAction(ctx, mevent, smfal->action);
		/* TEMPFAIL has higher priority, then ACCEPT, i.e. */
		message->rc = status > message->rc ? status : message->rc;
	    }
	}
    }
    if (message->rc == SMFIS_REJECT || message->rc == SMFIS_DISCARD)
	mlfi_log(message);
    return mlfi_cleanup(ctx, message->rc);
}

sfsistat mlfi_close(SMFICTX *ctx) {
    SMFCONNECTION *connection = (SMFCONNECTION *) smfi_getpriv(ctx);
    MSGEVENT *eprev, *enext;
    static char *func = "mlfi_close";

    if (connection) {
	if (debug_level > 0)
	    smfPrintDebug(1, connection->message->buf, "%s[%s]: entered", func, connection->hostaddr);
	if (connection->message->queueID)
	    mlfi_cleanup(ctx, SMFIS_CONTINUE);
	for (eprev = connection->event, enext = connection->event; enext; eprev = enext) {
	    enext = enext->next;
	    free(eprev);
	}
	free(connection->message);
	if (connection->hostaddr)
	    free(connection->hostaddr);
	free(connection);
	smfi_setpriv(ctx, NULL);
    }
    return SMFIS_CONTINUE;
}

void mlfi_log(SMFMESSAGE *message) {
    MSGHEADER *mheader;
    char *message_id = NULL, *env_from = EmptyEnvFrom, *alog;

    for (mheader = message->header; mheader; mheader = mheader->next) {
	if (!strcasecmp(mheader->name, "Message-ID"))
	    message_id = mheader->value;
	if (!strcasecmp(mheader->name, EnvelopeFrom))
	    env_from = mheader->value;
    }
    if (message->rc == SMFIS_REJECT)
	alog = ALOG_REJECTED;
    else
	alog = ALOG_DISCARDED;
    if (message_id)
	smfLog(message, "%s: Event %s, From %s, Message-ID %s", alog, message->trigger->event->name, env_from, message_id);
    else
	smfLog(message, "%s: Event %s, From %s", alog, message->trigger->event->name, env_from);
}

sfsistat mlfi_abort(SMFICTX *ctx) {
    SMFCONNECTION *connection = (SMFCONNECTION *) smfi_getpriv(ctx);
    SMFMESSAGE *message;
    static char *func = "mlfi_abort";

    message = connection->message;
    if (debug_level > 0) {
	if (message->queueID)
	    smfPrintDebug(1, message->buf, "%s[%s]: entered", func, message->queueID);
	else
	    smfPrintDebug(1, message->buf, "%s[%s]: entered", func, connection->hostaddr);
    }
    return mlfi_cleanup(ctx, SMFIS_CONTINUE);
}

sfsistat mlfi_cleanup(SMFICTX *ctx, sfsistat rc) {
    SMFCONNECTION *connection = (SMFCONNECTION *) smfi_getpriv(ctx);
    SMFMESSAGE *message;
    MSGEVENT *eprev, *enext;
    MSGHEADER *hprev, *hnext;
    MSGPART *pprev, *pnext;
    static char *func = "mlfi_cleanup";

    message = connection->message;
    if (debug_level > 0) {
	if (message->queueID)
	    smfPrintDebug(1, message->buf, "%s[%s]: entered, return code %s", func, message->queueID, smfis_codes[rc]);
	else
	    smfPrintDebug(1, message->buf, "%s[%s]: entered, return code %s", func, connection->hostaddr, smfis_codes[rc]);
    }
    if (message->queueID) {
	smfFlushLog(message);
	free(message->queueID);
	message->queueID = NULL;
	if (message->log)
	    free(message->log);
	if (message->boundary)
	    free(message->boundary);
	if (message->mimename)
	    free(message->mimename);
	if (message->filename)
	    free(message->filename);
    	for (hprev = message->header, hnext = message->header; hnext; hprev = hnext) {
	    hnext = hnext->next;
	    free(hprev->name);
	    free(hprev->value);
	    free(hprev);
	}
    	for (eprev = message->event, enext = message->event; enext != connection->event; eprev = enext) {
	    enext = enext->next;
	    free(eprev);
	}
    	for (pprev = message->mpart, pnext = message->mpart; pnext; pprev = pnext) {
	    pnext = pnext->next;
	    if (pprev->filename && pprev->filename != pprev->mimename)
		free(pprev->filename);
	    if (pprev->mimename)
		free(pprev->mimename);
	    if (pprev->mtype && pprev->mtype != message->mtype)
		free(pprev->mtype);
	    if (pprev->body_decoded && pprev->body_decoded != pprev->body)
		free(pprev->body_decoded);
	    if (pprev->body_stripped && pprev->body_stripped != pprev->body)
		free(pprev->body_stripped);
	    free(pprev);
	}
	if (message->body)
	    free(message->body);
	if (message->mtype)
	    free(message->mtype);
    }
    return rc;
}

void smfTestMessage(char* mfile) {
    char *p, *p2, *buf, *headerf = NULL, *headerv = NULL, *hdrs[2], *lasts;
    char *conn = "127.0.0.1"; /* by default, if "Connection" header was not specified */
    FILE *fp;
    SMFICTX *ctx;
    int fd, i, len;

    if (!(buf = (char*) malloc(MAX_BUFFER_SIZE)) || !(ctx = (SMFICTX*) malloc(MAX_BUFFER_SIZE)))
	smfError(1, "smfTestMessage: memory exhausted");
    if ((fd = open(mfile, O_RDONLY)) == -1 || !(fp = fdopen(fd, "r")))
	smfError(1, "smfTestMessage: message file %s is inaccessible.", mfile);
    while (fgets(buf, MAX_STRING_SIZE, fp)) {
	for (i = 0; *(buf + i); i++) {
	    if (*(buf + i) == '\n' || (*(buf + i) == '\r' && *(buf + i + 1) == '\n')) {
		*(buf + i) = 0;
		break;
	    }
	}
	if (!strncasecmp(buf, "Connection:", 11)) {
	    if (strtok_r(buf, ": \t", &lasts) && (p = strtok_r(NULL, "", &lasts))) {
		while (*p && (*p == ' ' || *p == '\t'))
		    p++;
		conn = strdup(p);
	    }
	    else
		smfError(1, "smfTestMessage: malformed message string \"%s\"", buf);
	    continue;
	}
	else
	if (!strncasecmp(buf, EnvelopeFrom, strlen(EnvelopeFrom)) &&
	    *(buf + strlen(EnvelopeFrom)) == ':') {
	    if ((p = strtok_r(buf, ": \t", &lasts)) && (p2 = strtok_r(NULL, "", &lasts))) {
		while (*p2 && (*p2 == ' ' || *p2 == '\t'))
		    p2++;
		hdrs[0] = p2;
	    }
	    else
		smfError(1, "smfTestMessage: malformed message string \"%s\"", buf);
	    break;
	}
	else
	if (!strncmp(buf, "From ", 5)) {
	    hdrs[0] = strdup(buf + 5);
	    break;
	}
	else {
	    hdrs[0]  = "<nobody@localhost>";
	    fseek(fp, 0, SEEK_SET);
	    break;
	}
    }
    hdrs[1] = NULL;
    if (mlfi_connect(ctx, conn, NULL) != SMFIS_CONTINUE ||
	mlfi_envfrom(ctx, hdrs) != SMFIS_CONTINUE) {
	mlfi_close(ctx);
	return;
    }
    while (fgets(buf, MAX_STRING_SIZE, fp)) {
	for (i = 0; *(buf + i); i++)
	    if (*(buf + i) == '\n' || *(buf + i) == '\r') {
		*(buf + i) = 0;
		break;
	    }
	if (*buf == '\0') { /* end of headers */
	    if (headerf && headerv) {
		if (mlfi_header(ctx, headerf, headerv) != SMFIS_CONTINUE) {
		    mlfi_close(ctx);
		    return;
		}
		free(headerf);
		free(headerv);
		headerf = NULL;
		headerv = NULL;
	    }
	    if (mlfi_eoh(ctx) != SMFIS_CONTINUE) {
		mlfi_close(ctx);
		return;
	    }
	    break;
	}
	else {
	    if (*buf == ' ' || *buf == '\t') {
		if (headerf && headerv) {
		    len = strlen(headerv);
		    headerv = realloc(headerv, len + strlen(buf) + 2);
		    *(headerv + len) = '\n';
		    strcpy(headerv + len + 1, buf);
		}
		else
		    smfError(1, "smfTestMessage: malformed message header \"%s\"", buf);
	    }
	    else {
		if ((p = strtok_r(buf, ": \t", &lasts))) {
		    if (headerf && headerv) {
			if (!strncmp(headerf, EnvelopeRcpt, strlen(EnvelopeRcpt))) {
			    hdrs[0] = headerv;
			    if (mlfi_envrcpt(ctx, hdrs) != SMFIS_CONTINUE) {
				mlfi_close(ctx);
				return;
			    }
			}
			else {
			    if (mlfi_header(ctx, headerf, headerv) != SMFIS_CONTINUE) {
				mlfi_close(ctx);
				return;
			    }
			}
			free(headerf);
			free(headerv);
			headerf = NULL;
			headerv = NULL;
		    }
		    headerf = strdup(p);
		    if ((p2 = strtok_r(NULL, "", &lasts))) {
			while (*p2 && (*p2 == ' ' || *p2 == '\t'))
			    p2++;
		    }
		    else
			p2 = p + strlen(p);
		    headerv = strdup(p2);
		}
		else
		    smfError(1, "smfTestMessage: malformed message header \"%s\"", buf);
	    }
	}
    }
    fflush(fp);
    while ((len = read(fd, buf, MAX_BUFFER_SIZE)) > 0) {
	if (mlfi_body(ctx, buf, len) != SMFIS_CONTINUE) {
	    mlfi_close(ctx);
	    return;
	}
    }
    mlfi_eom(ctx);
    mlfi_close(ctx);
    close(fd);
}

void smfUSR1handler(int signo) {
    int ndx;

    if (signo == SIGUSR1) {
	syslog(LOG_INFO, "Got signal SIGUSR1, reload the configuration...");
	ndx = smfConfNdx + 1;
	if (ndx == MAX_CONF_NDX)
	    ndx = 0;
	smfClearConfig(ndx);
	if (smfParseConfig(confFile, ndx) == -1)
	    syslog(LOG_ERR, "Configuration error, continue to use the old one...");
	else {
	    smfConfNdx = ndx;
	    syslog(LOG_INFO, "The configuration was reloaded successfuly.");
	}
    }
    signal(SIGUSR1, smfUSR1handler);
}

int main(int argc, char** argv) {
    int opt, isdaemon = 0, cfgCode;
    extern char *optarg;
    char *conn = NULL, *msgfile = NULL;
    struct rlimit rlp;

    *(ErrBuf + MAX_STRING_SIZE) = '\0';
    openlog(ExecName, LOG_CONS | LOG_PID, LOG_MAIL);
    confFile = DefaultConfig;
    while ((opt = getopt(argc, argv, "c:p:v:f:dt")) != -1) {
	switch(opt) {
	    case 'p':
        	conn  = strdup(optarg);
		break;
	    case 'c':
		confFile = strdup(optarg);
		break;
	    case 'f':
		msgfile = strdup(optarg);
		break;
	    case 'v':
		debug_level = atoi(optarg);
		if (debug_level < 0) {
		    smfUsage(ExecName);
		    smfError(1, "error option -%c", (char) opt);
		}
		break;
	    case 't':
		isdaemon = 0;
		cfgTest = 1;
		break;
	    case 'd':
		isdaemon = 1;
		break;
	    default:
		smfUsage(ExecName);
		smfError(1, "unknown option -%c", (char) opt);
	}
    }
    smfInit();
    if (cfgTest)
	fprintf(stdout, "Testing the configuration...\n");
    else
	if (msgfile) {
	    fprintf(stderr, "The option \"-f <message file>\" can be specified only with option \"-t\"\n");
	    return 1;
	}
    cfgCode = smfParseConfig(confFile, 0);
    if (cfgTest) {
	if (!cfgCode) {
	    fprintf(stdout, "Looks good!\n");
	    if (msgfile) {
		fprintf(stdout, "--\n");
		printf("Starting %s version %s, debug level is %d\n", ExecName, SMF_VERSION, debug_level);
		smfTestMessage(msgfile);
		fprintf(stdout, "--\n");
	    }
	    return 0;
	}
	else {
	    fprintf(stderr, "The configuration is incorrect.\n");
	    return 1;
	}
    }
    if (!tmpDir || !conn || cfgCode == -1) {
	smfUsage(ExecName);
	return 1;
    }
    if (setUser || setGroup)
	if (smfSetUGID(setUser, setGroup))
	    smfError(1, "cannot drop root privelege");
    if (smfi_register(smfilter) == MI_FAILURE)
	smfError(1, "smfi_register failed for %s", ExecName);
    if (signal(SIGUSR1, smfUSR1handler) == SIG_ERR)
	smfError(1, "SIGUSR1 handler redefine error");
    if (max_nofiles > 0 && !getrlimit(RLIMIT_NOFILE, &rlp)) {
	rlp.rlim_cur = max_nofiles;
	setrlimit(RLIMIT_NOFILE, &rlp);
    }
    if (isdaemon) {
	if (!fork()) {
	    setsid();
	    syslog(LOG_INFO, "started as daemon.");
	    smfi_setconn(conn);
	    runDaemon = 1;
	    if (max_soconn > MI_SOMAXCONN)
		smfi_setbacklog(max_soconn);
	    return smfi_main();
	}
	return 0;
    } else {
	printf("Starting %s version %s, debug level is %d\n", ExecName, SMF_VERSION, debug_level);
	smfi_setconn(conn);
	return smfi_main();
    }
}


syntax highlighted by Code2HTML, v. 0.9.1