'''
clamav module

(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 *
import socket,re

__all__=['clamscan','clamd','libclam']

def f_clamscandir(fda,scanner):
    level,rc,ret=0.0,0,[]
    for fdir in fda:
      pf=popen(scanner+[safe.fn(fdir)])
      pf.tocmd.close()
      rc=pf.wait()
      for line in pf.readlines():
        if re.search(" FOUND",line):
          ret.append(line)
          level+=1.0
      pf.fromcmd.close()
      debug.echo(4,"CLAMSCANF: RET: ",rc,ret)
    if rc==0:
      level,vir=0.0,''
    elif rc==1:
      l=re.search(": (.*) FOUND",ret[0].rstrip())
      if l:
        vir=l.group(1)
      else:
        debug.echo(0,'CLAMSCANF: ERROR: ',ret)
        raise ScannerError,'clamscandir: '+str(ret)
    else:
      raise ScannerError,'clamscan: returned error code: '+str(rc)+str(ret)
    return level,vir,ret

def f_clamd(buffer,scanner):
    if type(scanner)==type('string'): # local socket
      s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
      socket_settimeout(s,120)
      s.connect(scanner)
    else:
      s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      socket_settimeout(s,120)
      s.connect((scanner[0],scanner[1]))
    f=s.makefile('rw',BUFSIZE)
    s.sendall("STREAM\n")
    rl=f.readline()
    l=re.search('^PORT ([0-9]*)',rl)
    if l:
      # Send buffer
      ss=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      socket_settimeout(ss,120)
      if type(scanner)==type('string'): # local socket
        ss.connect(('127.0.0.1',int(l.group(1))))
      else:
        ss.connect((scanner[0],int(l.group(1))))
      ss.sendall(fromhdr())
      ss.sendall(buffer)
      ss.shutdown(2)
      ss.close()
      # Read reply
      level=0.0
      ret=[]
      for line in f.readlines():
        if not re.search(": OK$",line.rstrip()):
          ret.append(line)
      f.close()
      s.shutdown(2)
      s.close()
      if ret==[]:
        level,vir=0.0,''
      else:
        l=re.search(": (.*) FOUND$",ret[0].rstrip())
        if l:
          vir=l.group(1)
          level=1.0
        else:
          debug.echo(0,'CLAMD: ERROR: ',ret)
          raise ScannerError,'clamd: '+str(ret)
      return level,vir,ret
    else:
      raise ScannerError,'Protocol error, status: '+rl

class clamscan(ascanner):
  '''
  ClamAV command line realscanner.

  This scanner is a realscanner, which can be used to scan
  for viruses. ClamAV is a free program under GPL and may be freely
  used. Its database is very well.
  
  It can be easily used in chroot. mkchroot.sh from sagator package
  automatically integrates it into chroot environment.

  Requires: clamscan binary from clamav

  WARNING: If you are using clamscan from chroot, you need to configure
           freshclam to update chrooted copy of database!!!
  
  Usage: clamscan(['/usr/bin/clamscan','--stdout','--infected',
                   '--disable-summary','-r'])

  Where: in [] are command line parameters for clamscan binary
  '''
  name='clamscan()'
  def __init__(self,arg):
      self.arg=arg
  def scanfile(self,files,dirname='',args={}):
      level,detected,virlist=f_clamscandir([dirname],self.arg)
      return level,detected,virlist

class clamd(ascanner):
  '''
  ClamAV daemon realscanner.

  This scanners is a realscanner, which can be used to scan
  for viruses. ClamAV is a free program under GPL and may be freely
  used. Its database is very well.

  If you need a chrooted clamd, copy it into chroot and start it
  from here. If you are not familiar with it, use clamscan scanner
  from this module. It easily does chroot support, but it
  is not powerfull.
  
  Requires: clamd (clamav daemon)
  
  Usage: clamd(['localhost',3310])
     or: clamd('/var/spool/vscan/clamd')

  Where: 1st definition defines a clam daemon on host localhost,
             port 3310 (it is the default)
         2nd definition defines access to clamd via UNIX socket
  '''
  name='clamd()'
  def __init__(self,arg):
      self.arg=arg
  def scanbuffer(self,buffer,args={}):
      level,detected,virlist=f_clamd(buffer,self.arg)
      return level,detected,virlist

libclam_doc=\
  '''
  ClamAV realscanner - uses libclamavmodule python library.
  
  This scanners is a realscanner, which can be used to scan
  for viruses. ClamAV is a free program under GPL and may be freely
  used. Its database is very well.
  
  Requires: libclamav module for python (sagator-libclamav package)
  
  Usage: libclam(options=libclam.CL_SCAN_STDOPT, limits={}, db_options=None)
  
  Where: options is an number, which defines libclam options.
           See clamav documentation for more info.
         limits is an tuple, which defines limits for clamav
           decompressor, see clamav doc. for more info.
         db_options is an number, which defines libclam loaddb options.
           See clamav documentation for more info. This option is available
           only with sagator-libclamav-1.2.2 or higher.

  WARNING: If you are using clamav from chroot, you need to configure
           freshclam to update chrooted copy of database!!!
  '''

try:
  import libclamav

  class libclam(ascanner):
    name='libclam()'
    def __init__(self, options=None, limits={}, db_options=None):
        self.av = None # do not load database before chroot!
        if options==None:
          self.options = self.CL_SCAN_STDOPT # new syntax
        else:
          self.options = options
        self.limits = limits
        self.db_options = db_options
    def reinit(self):
        if not self.av:
          if self.db_options is not None:
            # allow db_options for new sagator-libclamav
            self.av = libclamav.clamav(self.db_options)
          else:
            self.av = libclamav.clamav()
          self.av.setlimits(self.limits)
        else:
          if self.av.reload()==0:
            return
        debug.echo(2, self.name, ": Loaded virpatterns: ", self.av.virnum)
    def scanfile(self, files, dir='', args={}):
        for fname in files:
          fd=safe.osopen(fname, os.O_RDONLY)
          try:
            v=self.av.scandesc(fd,self.options)
            os.close(fd)
            if v:
              mail.addheader('X-Sagator-LibClamAV', v)
              return 1.0, v, [fname+": "+v]
          except:
            os.close(fd)
            raise
        return 0.0, '', []

  # copy all constants from libclamav object to sagator's libclam object
  for clam_const in dir(libclamav):
    clam_value = getattr(libclamav, clam_const)
    if type(clam_value)==type(1):
      if clam_const[:3]=='CL_':
        setattr(libclam, clam_const, clam_value)
      else:
        setattr(libclam, "CL_"+clam_const, clam_value)

except ImportError,import_es:
  class libclam(ascanner):
    '''
    This method is undefined, because libclamavmodule is not found.
    Please recompile sagator, if you really need it.
    '''
    name='libclam()'
    errorstring='''Importing of libclamav module failed!
    You have configured libclamav() scanner and this scanner can't
    import ctypes module for python. Install python-ctypes package
    or reconfigure sagator without libclamav() scanner.'''
    def __init__(self,*arg1,**arg2):
        pass
    def scanbuffer(self,buffer,args={}):
        debug.echo(1,self.name+": scanbuffer(): "+self.errorstring," [",import_es,"]")
        raise ScannerError,self.errorstring
    def scanfile(self,files,dirname='',args={}):
        debug.echo(1,self.name+": scanbuffer(): "+self.errorstring," [",import_es,"]")
        raise ScannerError,self.errorstring

# update documentation
libclam.__doc__=libclam_doc
del libclam_doc
