#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