'''
libclamav - clamav python module
 
(c) 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 ctypes import *
from ctypes.util import find_library
from const import *

class ClamAVError(Exception):
      '''ClamAVError'''
      pass

class Limits_CA3(Structure):
  def __init__(self, maxreclevel=8, maxfiles=1024, maxmailrec=64,
               maxratio=250, archivememlim=1, maxfilesize=10485760):
      super(Limits_CA3, self).__init__(
          maxreclevel=maxreclevel,
          maxfiles=maxfiles,
          maxmailrec=maxmailrec,
          maxratio=maxratio,
          archivememlim=archivememlim,
          maxfilesize=maxfilesize
      )
  _fields_ = [
    ('maxreclevel', c_uint),
    ('maxfiles', c_uint),
    ('maxmailrec', c_uint),
    ('maxratio', c_uint),
    ('archivememlim', c_ushort),
    ('maxfilesize', c_ulong)
  ]

class Limits_CA4(Structure):
  def __init__(self, maxscansize=0, maxfilesize=0,
               maxreclevel=0, maxfiles=0, archivememlim=0):
      super(Limits_CA4, self).__init__(
          maxscansize=maxscansize,
          maxfilesize=maxfilesize,
          maxreclevel=maxreclevel,
          maxfiles=maxfiles,
          archivememlim=archivememlim
      )
  _fields_ = [
    ('maxscansize', c_long),
    ('maxfilesize', c_long),
    ('maxreclevel', c_uint),
    ('maxfiles', c_uint),
    ('archivememlim', c_ushort)
  ]

class Limits_CA5(Structure):
  def __init__(self, maxscansize=0, maxfilesize=0,
               maxreclevel=0, maxfiles=0, archivememlim=0,
               min_cc_count=0, min_ssn_count=0):
      super(Limits_CA5, self).__init__(
          maxscansize=maxscansize,
          maxfilesize=maxfilesize,
          maxreclevel=maxreclevel,
          maxfiles=maxfiles,
          archivememlim=archivememlim,
          min_cc_count=min_cc_count,
          min_ssn_count=min_ssn_count
      )
  _fields_ = [
    ('maxscansize', c_long),
    ('maxfilesize', c_long),
    ('maxreclevel', c_uint),
    ('maxfiles', c_uint),
    ('archivememlim', c_ushort),
    ('min_cc_count', c_uint),
    ('min_ssn_count', c_uint)
  ]

Limits = {
  3: Limits_CA3,
  4: Limits_CA4,
  5: Limits_CA5
}

class DBStat(Structure):
  _fields_ = [
    ('dir', c_char_p),
    ('entries', c_uint),
    ('stattab', c_void_p),
    ('statdbname', c_void_p)
  ]

class clamav:
  def __init__(self, db_options=DB_STDOPT, dirname=None, libver=None):
      if libver:
        self.so = cdll.LoadLibrary('libclamav.so.%d' % libver)
      else:
        lib = find_library('clamav')
        libver = int(lib.split('.')[2])
        self.so = cdll.LoadLibrary(lib)
      # set types
      self.so.cl_retdbdir.restype = c_char_p
      self.so.cl_free.restype = c_void_p
      self.so.cl_retver.restype = c_char_p
      self.so.cl_strerror.restype = c_char_p
      self.signo = c_uint(0)
      self.engine = c_void_p(None)
      self.dbstat = DBStat(None, 0, None, None)
      self.statinidir(dirname)
      self.load(db_options, dirname)
      self.limits = Limits[libver]()
  def __del__(self):
      self.statfree()
      self.free()
  def retver(self):
      return self.so.cl_retver()
  def free(self):
      if self.engine:
        self.so.cl_free(self.engine)
      self.signo.value = 0
      self.engine.value = None
      self.virnum = 0
  def load(self, db_options=CL_DB_STDOPT, dirname=None):
      self.db_options = db_options
      if not dirname:
        dirname = self.so.cl_retdbdir()
      self.status = self.so.cl_load(dirname, byref(self.engine),
                                    byref(self.signo), db_options)
      self.virnum = self.signo.value
      self.checkstatus()
      if self.signo.value==0:
        raise ClamAVError,'No signatures loaded!'
      self.status = self.so.cl_build(self.engine)
      self.checkstatus()
  def checkstatus(self, ret=None):
      if self.status in [CLEAN,VIRUS]:
        return ret
      else:
        raise ClamAVError,self.strerror()
  def scandesc(self, desc, options=SCAN_STDOPT):
      scanned = c_ulong(0)
      virname = c_char_p()
      self.status = self.so.cl_scandesc(
        desc, byref(virname), byref(scanned),
        self.engine, byref(self.limits), options
      )
      self.virname = virname.value or ""
      return self.checkstatus(self.virname)
  def scanfile(self, filename, options=SCAN_STDOPT):
      scanned = c_ulong(0)
      virname = c_char_p()
      self.status = self.so.cl_scanfile(
        filename, byref(virname), byref(scanned),
        self.engine, byref(self.limits), options
      )
      self.virname = virname.value or ""
      return self.checkstatus(self.virname)
  def statinidir(self, dirname=None):
      self.dirname = dirname
      if not dirname:
        dirname = self.so.cl_retdbdir()
      self.status = self.so.cl_statinidir(dirname, byref(self.dbstat))
      self.checkstatus()
  def statchkdir(self):
      self.status = self.so.cl_statchkdir(byref(self.dbstat))
      return self.checkstatus(self.status)
  def statfree(self):
      try:
        self.status = self.so.cl_statfree(byref(self.dbstat))
        self.checkstatus()
      except TypeError:
        pass # ignore 'NoneType' object is not callable
  def strerror(self, clerror=None):
      if clerror==None:
        clerror = self.status
      return self.so.cl_strerror(clerror)
  def reload(self, dirname=None):
      if dirname:
        self.dirname=dirname
      if self.statchkdir():
        self.statfree()
        self.free()
        self.statinidir(self.dirname)
        self.load(self.db_options, self.dirname)
        return 1
      else:
        return 0
  def setlimits(self, args={}):
      for key,value in args.items():
        setattr(self.limits, key, value)
