/*-
* Copyright (c) 2002 Granch Ltd. All rigts reserved.
*
* All or some portions of this file are derived from material licensed
* to the Grahcn Ltd. and are reproduced herein with the permission of
* Granch Ltd.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE GRANCH LTD. AND THEIR CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Author : Rashid N. Achilov E-Mail shelton@granch.ru
*
* @(#)mlficatch.c 0.91.3 (Granch Ltd.) 23/01/03
*/
/*------------------------------------------------------------------------
Kaspersky Anti-Virus mail filter, based on libmilter API
UNIX FreeBSD 4.6 version
Mail Filter Catch-up callbacks
--------------------------------------------------------------------------*/
#include "kavmilter.h"
#include "externals.h"
#include "functions.h"
// CONNECT From: catch-up
sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
{
struct mlfiPriv *priv;
char *localid = "(CONNECT From:)";
// Allocate memory to mlfiPriv data structure
if ((priv = malloc(sizeof (struct mlfiPriv))) == NULL)
{
syslog(LOG_ERR,"%s mlfiPriv %s, failed",cannot,localid);
return SMFIS_TEMPFAIL;
}
else
memset(priv, '\0', sizeof(struct mlfiPriv));
// Set private data area address
smfi_setpriv(ctx, (void *) priv);
// Save CONNECT From: in container
if ((priv->mlfi_connectfrom = strdup(hostname)) == NULL)
{
syslog(LOG_ERR,"%s hostname %s, failed",cannot,localid);
return SMFIS_TEMPFAIL;
}
// Walk through...
return SMFIS_CONTINUE;
}
// HELO catch-up
sfsistat mlfi_helo( SMFICTX *ctx, char *helohost)
{
char *tls;
char *buf;
char *localid = "(HELO From:)";
struct mlfiPriv *priv = MLFIPRIV;
// Try to take TLS version
tls = smfi_getsymval(ctx, "{tls_version}");
if (!tls)
tls = "No TLS";
// Try to take HELO (if any)
if (!helohost)
helohost = "(no HELO issued)";
// Allocate container to HELO
if (!(buf = (char *) malloc (strlen(tls) + strlen (helohost) + 3)))
{
syslog(LOG_ERR,"%s TLS/HELO %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Save HELO line in container, free previously allocated (if any)
sprintf(buf, "%s, %s", helohost, tls);
if (priv->mlfi_helofrom)
free(priv->mlfi_helofrom);
priv->mlfi_helofrom = buf;
// Walk through...
return SMFIS_CONTINUE;
}
// MAIL FROM: catch-up
sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv)
{
struct mlfiPriv *priv = MLFIPRIV;
int namelen;
pid_t curpid;
time_t curtime;
char *localid = "(MAIL FROM:)";
// Allocate MAIL FROM: container and store to it
if ((priv->mlfi_mailfrom = strdup(argv[0])) == NULL)
{
syslog(LOG_ERR,"%s source address %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Clear initial private data structure fields
priv->mlfi_fname = NULL;
priv->mlfi_fp = NULL;
priv->ptm = NULL;
priv->headerlines = priv->body_parts = 0;
priv->body_length = priv->recipients = 0;
*((char *) (&priv->_cflags)) = 0;
// Allocate memory to temp file name
namelen = strlen(_KAV_milter_config.temp_directory) + TEMPLENGTH;
if ((priv->mlfi_fname = (char *) malloc(namelen + 1)) == NULL)
{
syslog(LOG_ERR,"%s temp file name %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Allocate mamory to time repository
if ((priv->ptm = (struct tm *) malloc(sizeof(struct tm))) == NULL)
{
syslog(LOG_ERR,"%s time structure %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Get some values to make a temp file name
curpid = getpid();
curtime = time(NULL);
localtime_r(&curtime,priv->ptm);
// Preparing temp file name
snprintf(priv->mlfi_fname,namelen + 1,
"%s/Binf_%.2d%.2d%.2d%.2d%.2d%.2d.XXXXXX",_KAV_milter_config.temp_directory,
(int) priv->ptm->tm_mday, (int) priv->ptm->tm_mon + 1,
(int) (priv->ptm->tm_year - 100), (int) priv->ptm->tm_hour,
(int) priv->ptm->tm_min, (int) priv->ptm->tm_sec);
if (_KAV_milter_config.debug_level > 50)
syslog(LOG_WARNING,"constructed temp file is %s",priv->mlfi_fname);
// Open and chmod temp file
if ((namelen = mkstemp(priv->mlfi_fname)) == ERR )
{
syslog(LOG_ERR,"mkstemp error: %m, filename is %s",priv->mlfi_fname);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
if (chmod(priv->mlfi_fname, S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP) == ERR)
syslog(LOG_ERR,"chmod error: %m, filename is %s",priv->mlfi_fname);
// Associate file stream with opened descriptor
if ((priv->mlfi_fp = fdopen(namelen,"w+")) == NULL)
{
syslog(LOG_ERR,"fdopen error: %m, filename is %s",priv->mlfi_fname);
close(namelen);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
priv->_cflags._tempsave = OK;
// Store connection address and HELO host (if available)
if (fprintf(priv->mlfi_fp,
"Message tracing:\n----------------\nConnect from %s (%s)\n",
priv->mlfi_helofrom, priv->mlfi_connectfrom) == EOF)
{
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Store MAIL FROM: indentation.
if (fprintf(priv->mlfi_fp, "MAIL FROM: %s\n",priv->mlfi_mailfrom) == EOF)
{
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Walk through...
return SMFIS_CONTINUE;
}
// RCPT TO: catch-up
sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv)
{
struct mlfiPriv *priv = MLFIPRIV;
char *localid = "(RCPT TO:)";
// Allocate RCPT TO: local container and store to it
if (!priv->_cflags._firstrcpt)
if ((priv->mlfi_rcptto = strdup(argv[0])) == NULL)
{
syslog(LOG_ERR,"%s recipient address %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
else
priv->_cflags._firstrcpt = OK;
else ;
// Log all RCPT TO: tries to message tracing part of message dump
if (fprintf(priv->mlfi_fp, "RCPT TO: %s\n",argv[0]) == EOF)
{
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
else
priv->recipients++;
// Walk through...
return SMFIS_CONTINUE;
}
// Every header line catch-up
sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
struct mlfiPriv *priv = MLFIPRIV;
char *localid = "(Header)";
// If it is a first header, close Message tracing
if (!priv->_cflags._firstheader)
if (fprintf(priv->mlfi_fp,"----------------\n\n") == EOF)
{
syslog(LOG_ERR,"fprintf %s failed: %m",localid);
return SMFIS_TEMPFAIL;
}
else
priv->_cflags._firstheader = OK;
else ;
// Print header field and value in message dump
if (fprintf(priv->mlfi_fp,"%s: %s\n",headerf,headerv) == EOF)
{
syslog(LOG_ERR,"fprintf %s failed: %m",localid);
return SMFIS_TEMPFAIL;
}
priv->headerlines++;
// Walk through...
return SMFIS_CONTINUE;
}
// End-of-header condition in message catch-up
sfsistat mlfi_eoh(SMFICTX *ctx)
{
struct mlfiPriv *priv = MLFIPRIV;
char *localid = "(EOH)";
// Put one empty line to separate message headers and body
if (fprintf(priv->mlfi_fp,"\n") == EOF)
{
syslog(LOG_ERR,"fprintf %s failed: %m",localid);
return SMFIS_TEMPFAIL;
}
// Walk through...
return SMFIS_CONTINUE;
}
// Message body catch-up
sfsistat mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t bodylen)
{
struct mlfiPriv *priv = MLFIPRIV;
char *localid = "(Body)";
int nwritten;
// Store body part (or entire body, when small)
if ((nwritten = fwrite(bodyp, bodylen, 1, priv->mlfi_fp)) != OK)
{
syslog(LOG_ERR,"fwrite %s failed: %m",localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
// Accumulate quantity of parts and summary length of message body
priv->body_parts++;
priv->body_length += bodylen;
// Walk through...
return SMFIS_CONTINUE;
}
// End-of-message catch-up (main processing is here)
sfsistat mlfi_eom(SMFICTX *ctx)
{
struct mlfiPriv *priv = MLFIPRIV;
char *localid = "(EOM)";
short int retval, kavretlen = 8192, rejectlen = 256;
char *ispoint,*stppoint;
char *reasons[2] = { "infected", "suspicion" };
char *notes[2] = { "DANGER", "WARNING" };
char *thisreason, *thisnote;
char *reject;
// Allocate memory to KAV reply
if ((priv->_kavreply = (char *) malloc(kavretlen + rejectlen + 1)) == NULL)
{
syslog(LOG_ERR,"%s KAV reply buffer %s, failed",cannot,localid);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_TEMPFAIL;
}
else
reject = priv->_kavreply + kavretlen;
// First, close temp/save file to check it later
if (priv->_cflags._tempsave)
{
fclose(priv->mlfi_fp);
priv->_cflags._tempsave = NO;
}
// Check temp file against roaches
retval = _KAV_milter_find_roach(priv->mlfi_fname,priv->_kavreply,&kavretlen);
// Debug messages
if (retval > 0)
if (_KAV_milter_config.debug_level > 50)
syslog(LOG_ERR,"KAV return %d, length of message %d",retval,kavretlen);
// Check return code and take appropriate action
switch(retval & 0x0f)
{
case 6: // Infected object was deleted
case 5: // All objects were disinfected
case 4: // Known virus(es) were detected
case 2: // Modified or damaged virus detected
priv->_cflags._infected = OK;
thisreason = reasons[0];
thisnote = notes[0];
break;
case 3: // Suspicious object was found
priv->_cflags._infected = OK;
thisreason = reasons[1];
thisnote = notes[1];
break;
case 0: // Normal (non-infected) mail
smfi_addheader(ctx,"X-Kaspersky-Checking","Passed");
priv->_cflags._infected = NO;
mlfi_cleanup(ctx,PASS_MESSAGE);
return SMFIS_CONTINUE;
break;
case 8: // Corrupted files were found
case 7: // KAV files are corrupted
case ERR: // Miscelanous errors detected
smfi_setreply(ctx,SMTPNOKAV,SMTPADDNOKAV,
"KAV engine inactive or corrupted, please try again later");
mlfi_cleanup(ctx,PASS_MESSAGE);
return SMFIS_TEMPFAIL;
break;
case 1: // I/O error, when process mail by KAV
smfi_setreply(ctx,SMTPIOERR,SMTPADDIOERR,
"I/O error when processing mail occured, check your mail");
mlfi_cleanup(ctx,PASS_MESSAGE);
return SMFIS_TEMPFAIL;
break;
default: // Unknown response code, why?
syslog(LOG_ERR,"unrecognized KAV return code: %d", retval & 0x0f);
mlfi_cleanup(ctx,PASS_MESSAGE);
return SMFIS_TEMPFAIL;
break;
}
if (priv->_cflags._infected)
{
bzero(reject,rejectlen);
strcpy(reject,"Message ");
// Find in message a phrase, beginning from words "infected" or "suspicion"
// to type it in mail log file. Than find end of virus name, when can't do
// this, avoid segfault, use first 16 chars from name
// Bugfix: old daemon versions (reported about 3.135.3) hasn't space after
// virus name, instead they has '\n' immediately. We try to find '\n', when
// couldn't find space.
if ((ispoint = strstr(priv->_kavreply,thisreason)) == NULL)
ispoint = "(null)";
else
if ((stppoint =
strchr(ispoint + strlen(thisreason) + 2,' ')) == NULL)
if ((stppoint =
strchr(ispoint + strlen(thisreason) + 2,'\n')) == NULL)
{
if (_KAV_milter_config.debug_level > 50)
syslog(LOG_ERR,"End of virus name not found, avoid crash, use 16 chars");
stppoint = ispoint + strlen(thisreason) + 18;
}
else ;
else ;
// Note about virus infected mail with sender and recipient
syslog(LOG_ERR,"%s! mail from %s to %s %s",thisnote,
priv->mlfi_mailfrom,priv->mlfi_rcptto,ispoint);
// Depend on mode, reject this message with reject code or silently discard
if (_KAV_milter_config._idflags._mode_discard)
{
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_DISCARD;
}
else
if (_KAV_milter_config._idflags._mode_reject)
{
priv->_cflags._infected = OK;
memset(reject + 8, NULL, rejectlen - 8);
strncpy(reject + 8, ispoint, stppoint - ispoint);
// Some more debugging messages
if (_KAV_milter_config.debug_level > 20)
{
syslog(LOG_ERR,"Message is: \"%s %s %s\"",
SMTPREJECT, SMTPADDREJECT, reject);
syslog(LOG_ERR,"Message buffer: 0x%x, computed length %d",
(int) &reject, stppoint - ispoint);
}
smfi_setreply(ctx, SMTPREJECT, SMTPADDREJECT, reject);
mlfi_cleanup(ctx, PASS_MESSAGE);
return SMFIS_REJECT;
}
}
// Walk through...
return SMFIS_CONTINUE;
}
// Abort connection catch-up (simply call cleanup procedure)
sfsistat mlfi_abort(SMFICTX *ctx)
{
return mlfi_cleanup(ctx, PASS_MESSAGE);
}
// Clean-up procedure (called when normal or abort termination, but no
// connection termination, so mlfi_connect and mlfi_helo, and of course,
// priv itself CANNOT be freed!
sfsistat mlfi_cleanup(SMFICTX *ctx, char recite)
{
struct mlfiPriv *priv = MLFIPRIV;
// Free MAIL FROM: repository
if (priv->mlfi_mailfrom)
{
free(priv->mlfi_mailfrom);
priv->mlfi_mailfrom = NULL;
}
// Free RCPT TO: (first recipient only) repository
if (priv->mlfi_rcptto)
{
free(priv->mlfi_rcptto);
priv->mlfi_rcptto = NULL;
}
priv->recipients = 0;
// Free KAV reply buffer
if (priv->_kavreply)
{
free(priv->_kavreply);
priv->_kavreply = NULL;
}
// Free allocated time repository
if (priv->ptm)
{
free(priv->ptm);
priv->ptm = NULL;
}
// Close opened temporary file
if (priv->_cflags._tempsave)
{
fclose(priv->mlfi_fp);
priv->mlfi_fp = NULL;
priv->_cflags._tempsave = NO;
}
if (recite == RECITE_MESSAGE)
{
// TODO: here'll be code to extra save message, when need
syslog(LOG_ERR,"Cannot recite message: not implemented yet");
}
// Free allocated filename space
if (priv->mlfi_fname != NULL)
{
unlink(priv->mlfi_fname);
free(priv->mlfi_fname);
priv->mlfi_fname = NULL;
}
// Clearing per-message counters
priv->headerlines = 0;
priv->body_parts = 0;
priv->body_length = 0;
*((char *) (&priv->_cflags)) = 0;
// Walk through...
return SMFIS_CONTINUE;
}
// Close SMTP session catch-up
sfsistat mlfi_close(SMFICTX *ctx)
{
struct mlfiPriv *priv = MLFIPRIV;
// Free CONNECT From: repository
if (priv->mlfi_connectfrom)
{
free(priv->mlfi_connectfrom);
priv->mlfi_connectfrom = NULL;
}
// Free HELO FROM: repository
if (priv->mlfi_helofrom)
{
free(priv->mlfi_helofrom);
priv->mlfi_helofrom = NULL;
}
// At last, free private storage itself
free(priv);
smfi_setpriv(ctx, NULL);
// Walk through...
return SMFIS_CONTINUE;
}
syntax highlighted by Code2HTML, v. 0.9.1