/*
* contentpolicy.c -- module for ZMailer's smtpserver
* By Matti Aarnio <mea@nic.funet.fi> 1998, 2000, 2002-2003
*
* This is the ugly one, we run SENSOR program on each
* accepted message -- if the sensor decrees the program
* unacceptable, we return policy-analysis result per
* analyzed file to the caller of this program.
*
* The protocol in between the smtpserver, and the content
* policy analysis program is a simple one:
* 0) Caller starts the policy program
* 1) When the policy program is ready to answer to questions,
* it writes "#hungry\n" to its STDOUT.
* 2) This caller wrapper detects the state, and feeds it
* a job-spec: relfilepath \n
* 3) The policy-program will then respond with a line
* matching format:
* %i %i.%i.%i comment text \n
* 4) the interface collects that, and presents onwards.
* Loop restart from phase 1), UNLESS the policy program
* has yielded e.g. EOF, in which case the loop terminates.
*
* If no answer is received (merely two consequtive #hungry
* states, or non-conformant answers), a "ok" is returned,
* and the situation is logged.
*
*
*
*
*/
#include "smtpserver.h"
extern int debug;
char *contentfilter; /* set at cfgread.c */
/* Local data */
int contentpolicypid = -1;
int debug_content_filter;
static FILE *cpol_tofp;
static FILE *cpol_fromfp;
static int contentphase;
/* Phases:
0: started, expecting "#hungry"
1: seen "#hungry", ready for a job!
2: sent a task, expecting answer
*/
static int init_content_policy_prog()
{
int piperd[2], pipewr[2]; /* per parent */
pipe(piperd);
pipe(pipewr);
if (cpol_tofp) fclose(cpol_tofp);
if (cpol_fromfp) fclose(cpol_fromfp);
cpol_tofp = cpol_fromfp = NULL;
contentphase = 0;
contentpolicypid = fork();
if (contentpolicypid == 0) { /* CHILD */
close(piperd[0]);
close(pipewr[1]);
dup2(piperd[1], 1);
dup2(piperd[1], 2);
dup2(pipewr[0], 0);
close(piperd[1]);
close(pipewr[0]);
execl(contentfilter, contentfilter, NULL);
_exit(255); /* EXEC failure! */
}
if (contentpolicypid < 0) {
/* ERROR! :-( */
MIBMtaEntry->ss.ContentPolicyForkFailures ++;
return 0;
}
/* Parent */
cpol_tofp = fdopen(pipewr[1], "w");
cpol_fromfp = fdopen(piperd[0], "r");
close(pipewr[0]);
close(piperd[1]);
return 1; /* Successfull start! (?) */
}
static int init_content_policy_sock()
{
int msgsock;
struct sockaddr_un server;
memset((char*)&server,0,sizeof(server));
server.sun_family = AF_UNIX;
strncpy(server.sun_path,contentfilter,sizeof(server.sun_path)-1);
server.sun_path[sizeof(server.sun_path)-1]='\0';
if ((msgsock=socket(AF_UNIX,SOCK_STREAM,0)) < 0) {
type(NULL,0,NULL, "contentfilter socket(%s) error %d (%s)",
contentfilter,errno,strerror(errno));
return(0);
}
if (connect(msgsock,(struct sockaddr *)&server,
sizeof(server)-sizeof(server.sun_path)+strlen(server.sun_path)+1) < 0) {
type(NULL,0,NULL, "contentfilter connect(%s) error %d (%s)",
contentfilter,errno,strerror(errno));
return 0;
}
contentpolicypid = 0;
cpol_tofp = fdopen(msgsock, "w");
cpol_fromfp = fdopen(msgsock, "r");
return 1;
}
static int init_content_policy()
{
struct stat stbuf;
if (stat(contentfilter,&stbuf)) {
type(NULL,0,NULL, "contentfilter stat(%s) error %d",contentfilter,errno);
return 0;
}
if (S_ISREG(stbuf.st_mode))
return init_content_policy_prog();
else
return init_content_policy_sock();
}
static int
pickresponse(buf, bufsize, fp)
char *buf;
int bufsize;
FILE *fp;
{
int c, i;
c = i = 0;
--bufsize;
if (feof(fp) || ferror(fp)) return -1;
for (;;) {
if (ferror(fp) || feof(fp)) break;
c = fgetc(fp);
if (c == EOF) break;
if (c == '\n') break;
if (i < bufsize)
buf[i++] = c;
}
buf[i] = 0;
while (c != '\n') {
if (ferror(fp) || feof(fp)) break;
c = fgetc(fp);
}
return i;
}
int
contentpolicy(rel, state, fname)
struct policytest *rel;
struct policystate *state;
const char *fname;
{
char responsebuf[8192];
int i, rc, neg, val;
int seenhungry = 0;
if (state->always_reject) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; AlwaysReject");
return -1;
}
if (state->sender_reject) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; SenderReject");
return -2;
}
if (state->always_freeze) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; AlwaysFreeze");
return 1;
}
if (state->sender_freeze) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; SenderFreeze");
return 2;
}
/* If no 'filter *' defined, use old behaviour */
if (state->always_accept && (state->content_filter < 0)) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; AlwaysAccept w/o FILTER+");
return 0;
}
/* 'filter', but not 'filter +' ! */
if (state->content_filter == 0) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; AlwaysAccept w FILTER but not '+'");
return 0;
}
if (state->message != NULL)
free(state->message);
state->message = NULL;
if (! contentfilter) {
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy not run; not configured");
return 0; /* Until we have implementation */
}
if (contentpolicypid < 0)
if (!init_content_policy()) {
type(NULL,0,NULL, "ContentPolicy not run; failed to start ?!");
return 0; /* DUH! */
}
/* Ok, we seem to have content-filter program configured... */
if (debug_content_filter)
type(NULL,0,NULL, "ContentPolicy program running with pid %d; input='%s'",
contentpolicypid, fname);
pick_reply:;
i = pickresponse(responsebuf, sizeof(responsebuf), cpol_fromfp);
if (debug_content_filter)
type(NULL,0,NULL, "policyprogram said: rc=%d '%s'", i, responsebuf);
if (i <= 0) return 0; /* Urgh.. */
if (strcmp(responsebuf, "#hungry") == 0) {
++seenhungry;
if (seenhungry == 1 && cpol_tofp) {
fprintf(cpol_tofp, "%s\n", fname);
fflush(cpol_tofp);
goto pick_reply;
}
/* Seen SECOND #hungry !!!
Abort the connection by closing the command socket..
Collect all replies, and log them. */
if (cpol_tofp) fclose(cpol_tofp);
cpol_tofp = NULL;
sleep(1);
if (contentpolicypid > 1) kill(SIGKILL, contentpolicypid);
for (;;) {
i = pickresponse(responsebuf, sizeof(responsebuf), cpol_fromfp);
if (i <= 0) break;
if (debug_content_filter)
type(NULL,0,NULL, "policyprogram said: %s", responsebuf);
}
/* Finally yield zero.. */
return 0;
}
if (*responsebuf == '#' || *responsebuf == '\n') /* debug stuff ?? */
/* Debug-stuff... */
goto pick_reply;
i = 0;
val = neg = 0;
if (responsebuf[i] == '-') {
++i;
neg = 1;
}
while ('0' <= responsebuf[i] && responsebuf[i] <= '9') {
val *= 10;
val += (responsebuf[i] - '0');
++i;
}
if (neg) val = -val;
if (!(i >= neg) || responsebuf[i] != ' ') {
if (!seenhungry) goto pick_reply;
return 0; /* Bad result -> tool borken.. */
}
rc = val;
/* on non-void return, do set state->message on free()able storage ! */
/* Pick at first the heading numeric value. */
/* Scan until first space - or EOL */
for (; i < sizeof(responsebuf) && responsebuf[i] != 0; ++i) {
if (responsebuf[i] == ' ') break;
}
/* Scan over spaces */
while (i < sizeof(responsebuf) && responsebuf[i] == ' ') ++i;
if (!cpol_tofp) {
fclose(cpol_fromfp);
cpol_fromfp = NULL;
if (contentpolicypid > 1)
kill(SIGKILL, contentpolicypid);
contentpolicypid = -1;
}
state->message = strdup(responsebuf + i);
return rc;
}
void killcfilter(SS, cpid)
SmtpState *SS;
int cpid;
{
if (cpid > 0) {
if (cpol_tofp == NULL)
fclose(cpol_tofp);
cpol_tofp = NULL;
if (cpol_fromfp == NULL)
fclose(cpol_fromfp);
cpol_fromfp = NULL;
sleep(1); /* for normal filter shutdown */
kill(cpid, SIGKILL);
}
}
syntax highlighted by Code2HTML, v. 0.9.1