'''
clamav module, version 0.6.0

(c) 2003 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,string

__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",string.rstrip(ret[0]))
      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(stream,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 stream
      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',string.atoi(l.group(1))))
      else:
        ss.connect((scanner[0],string.atoi(l.group(1))))
      ss.sendall(fromhdr())
      ss.sendall(stream)
      ss.shutdown(2)
      ss.close()
      # Read reply
      level=0.0
      ret=[]
      for line in f.readlines():
        if not re.search(": OK$",string.rstrip(line)):
          ret.append(line)
      f.close()
      s.shutdown(2)
      s.close()
      if ret==[]:
        level,vir=0.0,''
      else:
        l=re.search(": (.*) FOUND$",string.rstrip(ret[0]))
        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):
  '''
  clamscan() realscanner - clamav scanner
  
  Usage: clamscan(['/usr/bin/clamscan','--stdout','--infected',
                   '--disable-summary','-r','--mbox'])

  Where: in [] are command line parameters for clamscan binary

  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.
  
  It can be easily used in chroot. mkchroot.sh from sagator package
  automatically integrates it into chroot environment.

  WARNING: If you are using clamscan from chroot, you need to configure
           freshclam to update chrooted copy of database!!!
  '''
  name='clamscan()'
  def __init__(self,arg):
      self.arg=arg
  def scanfile(self,files,dirname=''):
      level,detected,virlist=f_clamscandir([dirname],self.arg)
      return level,detected,virlist

class clamd(ascanner):
  '''
  clamd realscanner
  
  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

  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.
  '''
  name='clamd()'
  def __init__(self,arg):
      self.arg=arg
  def scanstream(self,stream):
      level,detected,virlist=f_clamd(stream,self.arg)
      return level,detected,virlist

try:
  import libclamav

  class libclam(ascanner):
    '''
    libclam realscanner - uses libclamavmodule python library
    
    Usage: libclam([options[,limits]])
    
    Where: options is an number, whic 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
    
    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.
    
    WARNING: Streamscanner ignores options and limits!

    WARNING: Streamscanner can't scan mailboxes and archives.
             Use parsemail() for emails anb if you need also
             archive support, use decompress() interscanner.

    WARNING: If you are using clam from chroot, you need to configure
             freshclam to update chrooted copy of database!!!
    '''
    name='libclam()'
    CL_RAW=libclamav.CL_RAW
    CL_ARCHIVE=libclamav.CL_ARCHIVE
    CL_MAIL=libclamav.CL_MAIL
    CL_DISABLERAR=libclamav.CL_DISABLERAR
    CL_OLE2=libclamav.CL_OLE2
    CL_ENCRYPTED=libclamav.CL_ENCRYPTED
    CL_DEFAULT=libclamav.CL_DEFAULT
    def __init__(self,options=libclamav.CL_DEFAULT,limits={}):
        self.av=None # do not load database before chroot!
        self.options=options
        self.limits=limits
    def reinit(self):
        if not self.av:
          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 scanstream(self,stream):
        try:
          v=self.av.scanbuff(stream)
          if v:
            return 1.0,v,[v]
        except RuntimeError,es:
          debug.echo(1,"libclam(): "+es)
          raise
        return 0.0,'',[]
    def scanfile(self,files,dir=''):
        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,'',[]
except ImportError,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 libclamavmodule failed!"
    def __init__(self):
        pass
    def scanstream(self,stream):
        debug.echo(1,self.name+": scanstream(): "+self.errorstring," [",es,"]")
        raise ScannerError,self.errorstring
    def scanfile(self,files,dirname=''):
        debug.echo(1,self.name+": scanstream(): "+self.errorstring," [",es,"]")
        raise ScannerError,self.errorstring
