'''
Advanced logger for sagator
 
(c) 2003-2007 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_mysql','log_sqlite',
         'log_pgdb','log_pgsql','log_psycopg']

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)
    %(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 ''),
        '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...)
  
  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.
  
  New in version 0.7.0.
  '''
  FORMAT="INSERT INTO log " \
    +"(datetime,level,virname,status,qname,sender,recipient,size,ip)" \
    +" VALUES " \
    +"(now(),%(LEVEL)s,%(VIRNAME)s,%(STATUS)s,%(QNAME)s,%(SENDER)s,%(RECIPIENT)s,%(SIZE)s,%(SENTBY_IP)s);"
  def __init__(self,dbc,format,*scanners):
      if dbc.name=='sqlite':
        format=format.replace("now()","0")
      self.dbc=dbc
      self.quote=self.dbc.quote
      self.format=format
      match_any.__init__(self,scanners)
  def scanbuffer(self,buffer,args={}):
      level,detected,virlist=match_any.scanbuffer(self,buffer,args)
      if 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_mysql(log_sql):
  '''
  MySQL logger interscanner. [obsolete]
  
  Usage: log_mysql(host, port, dbname,dbuser,dbpasswd,format,scanners...)
  
  Where: host is a string, which defines MySQL database hostname
           (you can use "127.0.0.1", UNIX socket can't be used in chroot)
         port is an integer, which defines MySQL database TCP port number
           (it is by default 3306)
         dbuser is a string, which defines username which can log to DB
         dbname is a string, which defines database name
         dbpasswd is a string, which defines user password
         format is a string, which defines data format.
           For detailed format description see log() scanner documentation.
           You also can use log_mysql.FORMAT for default format.

  Obsolete since 0.7.0, use log_sql() instead.
  '''
  name='log_mysql()'
  def __init__(self,host,port,dbname,dbuser,dbpasswd,format,*scanners):
      params={'host':host, 'port':port, 'dbname':dbname,
              'dbuser':dbuser, 'dbpasswd': dbpasswd}
      log_sql.__init__(self,db.MySQLdb(params),format,scanners)

class log_sqlite(log_sql):
  '''
  SQLite logger interscanner. [obsolete]
  
  Usage: log_sqlite(dbname,format,scanners...)
  
  Where: dbname is a string, which defines, which database to use
         format is a string, which defines data format.
           For detailed format description see log() scanner documentation.
           You also can use log_sqlite.FORMAT for default format.

  Obsolete since 0.7.0, use log_sql() instead.
  '''
  name='log_sqlite()'
  def __init__(self,dbname,format,*scanners):
      params={'dbname':dbname}
      log_sql.__init__(self,db.sqlite(params),format,scanners)

class log_pgdb(log_sql):
  '''
  PostgreSQL logger, using pgdb python DB API 2.0 compilant module. [obsolete]
  
  Usage: log_pgdb(host, port, dbname,dbuser,dbpasswd,format,scanners...)
  
  Where: host is a string, which defines pgsql database hostname
           (you can use "localhost", UNIX socket can't be used in chroot)
         port is an integer, which defines pgsql database TCP port number
           (it is by default 5432)
         dbuser is a string, which defines username which can log to DB
         dbname is a string, which defines database name
         dbpasswd is a string, which defines user password
         format is a string, which defines data format.
           For detailed format description see log() scanner documentation.
           You also can use log_pgdb.FORMAT for default format.

  Obsolete since 0.7.0, use log_sql() instead.
  '''
  name='log_pgdb()'
  def __init__(self,host,port,dbname,dbuser,dbpasswd,format,*scanners):
      params={'host':host, 'port':port, 'dbname':dbname,
              'dbuser':dbuser, 'dbpasswd': dbpasswd}
      log_sql.__init__(self,db.pgdb(params),format,scanners)

class log_psycopg(log_sql):
  '''
  PostgreSQL logger, using psycopg python DB API compilant module. [obsolete]
  
  Usage: log_psycopg(host, port, dbname,dbuser,dbpasswd,format,scanners...)
  
  Where: host is a string, which defines pgsql database hostname
           (you can use "localhost", UNIX socket can't be used in chroot)
         port is an integer, which defines pgsql database TCP port number
           (it is by default 5432)
         dbuser is a string, which defines username which can log to DB
         dbname is a string, which defines database name
         dbpasswd is a string, which defines user password
         format is a string, which defines data format.
           For detailed format description see log() scanner documentation.
           You also can use log_psycopg.FORMAT for default format.

  Obsolete since 0.7.0, use log_sql() instead.
  '''
  name='log_psycopg()'
  def __init__(self,host,port,dbname,dbuser,dbpasswd,format,*scanners):
      params={'host':host, 'port':port, 'dbname':dbname,
              'dbuser':dbuser, 'dbpasswd': dbpasswd}
      log_sql.__init__(self,db.psycopg(params),format,scanners)

class log_pgsql(log_sql):
  '''
  PosgreSQL logger interscanner, using pg module. [obsolete]
  
  Usage: log_pgsql(host, port, dbname,dbuser,dbpasswd,format,scanners...)
  
  Where: host is a string, which defines pgsql database hostname
           (you can use "localhost", UNIX socket can't be used in chroot)
         port is an integer, which defines pgsql database TCP port number
           (it is by default 5432)
         dbuser is a string, which defines username which can log to DB
         dbname is a string, which defines database name
         dbpasswd is a string, which defines user password
         format is a string, which defines data format.
           For detailed format description see log() scanner documentation.
           You also can use log_pgsql.FORMAT for default format.

  Obsolete since 0.7.0, use log_sql() instead.
  '''
  name='log_pgsql()'
  def __init__(self,host,port,dbname,dbuser,dbpasswd,format,*scanners):
      params={'host':host, 'port':port, 'dbname':dbname,
              'dbuser':dbuser, 'dbpasswd': dbpasswd}
      log_sql.__init__(self,db.pg(params),format,scanners)
