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