'''
Advanced logger for sagator
 
(c) 2003-2008 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
                                                                                
'''
                                                                                
from avlib import *
from match import match_any
from report import report
import db
                                                                                
__all__ = ['log', 'log_syslog', 'log_sql', 'log_cleanup']

class log(report):
  '''
  Advanced logger interscanner.

  This scanner can be used to log some special data.
  You can use these variables in format string:
    %(LEVEL)s		- detected virus level
    %(VIRNAME)s		- name of detected virus, empty if CLEAN
    %(STATUS)s		- if mail is dropped, rejected, ...
    %(QNAME)s		- quarantine file name
    %(SCANNER_NAME)s	- scanner which reported this virus
    %(SENDER)s		- scanned message sender
    %(RECIPIENTS)s	- scanned message recipients
    %(RECIPIENT)s	- scanned message recipients one by one (only for SQL)
    %(SUBJECT)s		- message's header Subject
    %(SIZE)s            - email size
    %(VERSION)s		- sagator's version
    %(SENTBY_IP)s	- sender's IP
    %(SENTBY_NAME)s	- sender's hostname
    %(SENTBY_HELO)s	- sender's HELO/EHLO string
    %(DATETIME)s	- date and time in standard format ("%c")
    %(SYSLOG_DATE)s	- date and time in syslog format ("%a %e %H:%M:%S")
    %(PID)s		- current process ID
  
  You also can use log.FORMAT for default format or log.SUMMARY_REPORT
  for summary reporter script.
  
  Usage: log(logto,format,scanners...)
  
  Where: logto is a string, which defines filename to store these data
           If logto is an integer, it defines log level in standard
           log file.
         format is a string, which defines data format

  New format style is introduced in version 0.9.0, for older versions please
  use old format (for example "$STATUS" instead of "%(STATUS)s").
  '''
  name='log()'
  # default format
  FORMAT="level='%(LEVEL)s', virname='%(VIRNAME)s', status='%(STATUS)s', "\
         "scanner='%(SCANNER_NAME)s', size='%(SIZE)s', sender='%(SENDER)s', "\
         "recipients='%(RECIPIENTS)s', sentby_ip='%(SENTBY_IP)s', "\
         "qname='%(QNAME)s'\n"
  SUMMARY_REPORT="REPORT: datetime='%(DATETIME)s', "+FORMAT.strip()+"\n"
  def __init__(self,logto,format,*scanners):
      self.logto=logto
      self.format=format
      match_any.__init__(self,scanners)
  def quote(self,s):
      return quote(s)
  def vars(self,level,detected,quote=str,**args):
      sender=mail.getsender()
      return {
        'LEVEL':	str(level),
        'VIRNAME':	quote(detected),
        'STATUS':	globals.action(level),
        'QNAME':	quote(globals.QFNAME),
        'SCANNER_NAME':	globals.found_by.name,
        'SENDER':	quote(mail.sender.strip()),
        'RECIPIENTS':	quote(','.join(mail.recip)),
        'RECIPIENT':	quote(args.get('recipient') or ''),
        'SUBJECT':	mail.headers.get('Subject'),
        'SIZE':		str(len(mail.data)),
        'VERSION':	SG_VER_REL,
        'SENTBY_IP':	sender['ADDR'] or '127.0.0.1',
        'SENTBY_NAME':	sender['NAME'] or 'localhost',
        'SENTBY_HELO':	sender['HELO'],
        'DATETIME':	time.strftime("%c"),
        'SYSLOG_DATE':	time.strftime("%a %e %H:%M:%S"),
        'PID':		str(os.getpid())
      }
  def repl_vars(self,level,detected):
      return replace_tmpl(self.format, self.vars(level, detected, self.quote))
  def scanbuffer(self,buffer,args={}):
      level,detected,virlist=match_any.scanbuffer(self,buffer,args)
      if is_infected(level):
        if type(self.logto)==type(1):
          debug.echo(self.logto,self.repl_vars(level,detected).strip())
        else:
          open(self.logto,'a').write(self.repl_vars(level,detected))
      return level,detected,virlist

class log_syslog(log):
  '''
  Syslog logger interscanner to log via syslog.
  
  For detailed description see log() scanner.
  
  Usage: log_syslog(format,scanners...)
  '''
  name='log_syslog()'
  def __init__(self,format,*scanners):
      import syslog
      self.format=format
      self.SYSLOG=syslog
      self.SYSLOG.openlog('sagator',syslog.LOG_PID,syslog.LOG_MAIL)
      match_any.__init__(self,scanners)
  def scanbuffer(self,buffer,args={}):
      level,detected,virlist=match_any.scanbuffer(self,buffer,args)
      if is_infected(level):
        self.SYSLOG.syslog(self.repl_vars(level,detected).strip())
      return level,detected,virlist

class log_sql(log):
  '''
  SQL interscanner for python DB-API 2.0 compatible DB modules.

  Usage: log_sql(db_connection,format,scanners...)
         log_sql(...).also_clean()
  
  Where: db_connection is a database connection. For these connections
           see doc/Databases.txt file.
         format is a string, which defines an SQL INSERT command with
           optional variables described in log() scanner.
         .also_clean() can be used to log also "CLEAN" emails
  
  New in version 0.7.0.
  '''
  FORMAT="INSERT INTO log " \
    +"(datetime,level,virname,status,qname,sender,recipient,size,ip)" \
    +" VALUES " \
    +"(CURRENT_TIMESTAMP,%(LEVEL)s,%(VIRNAME)s,%(STATUS)s,%(QNAME)s,%(SENDER)s,%(RECIPIENT)s,%(SIZE)s,%(SENTBY_IP)s);"
  def __init__(self,dbc,format,*scanners):
      self.dbc = dbc
      self.quote = self.dbc.quote
      self.format = format
      self.LOG_CLEAN = False
      match_any.__init__(self,scanners)
  def also_clean(self):
      self.LOG_CLEAN = True
      return self
  def scanbuffer(self,buffer,args={}):
      level,detected,virlist=match_any.scanbuffer(self,buffer,args)
      if self.LOG_CLEAN or is_infected(level):
        for TRY in range(3,0,-1):
          try:
            if '%(' in self.format:
              for recipient in mail.recip:
                insert=self.vars(level, detected, recipient=recipient)
                debug.echo(5,"log_sql(): %s %s" % (self.format, insert))
                self.dbc.execute(self.format, insert)
            else:
              for recipient in mail.recip:
                insert=self.repl_vars(level, detected)
                debug.echo(5,"log_sql(): ", str([insert]))
                self.dbc.execute(insert)
            self.dbc.commit()
            break
          except Exception, e:
            if TRY>1:
              debug.echo(3, "log_sql(): WARNING: Problem logging into database!")
              debug.traceback(5, "log_sql()")
            else:
              debug.echo(1, "log_sql(): ERROR: Error logging into database!")
              debug.echo(3, "log_sql(): ", e)
              debug.traceback(4, "log_sql()")
            # try to reconnect connection
            try:
              self.dbc.connect()
            except:
              debug.traceback(4,"log_sql('reconnect')")
      return level,detected,virlist

class log_cleanup(log):
  '''
  Clean old records from SQL log database table.

  Usage: log_cleanup(older_than=768)
  
  Where: older_than is an integer, which defines number of hours for
           up-to-date records. All older records will be deleted.
           By default aprox. one month (768h) of records are kept.
  
  New in version 1.1.0.
  '''
  name='log_cleanup()'
  def __init__(self, older_than=768):
      self.older_than = older_than
  def scanbuffer(self, buffer, args={}):
      t0=time.time()
      args['dbc'].execute_cycle(
        "DELETE FROM log WHERE datetime<%s",
        [
          time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(
            time.time()-self.older_than*60*60))
        ]
      )
      debug.echo(3, "%s: Records deleted: %d"
                    % (self.name, args['dbc'].rowcount))
      return 0.0,'',[]
