'''
Filesystem scanners for sagator.

(c) 2003-2016,2019 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 *

__all__=['filesys']

class filesys(ascanner):
  '''
  Scanner which uses filesystem scanners.
  
  Usage: filesys(prefix='/tmp/fs', logfile='', regexp='')

  Where: '/prefix' is a string, which defines directory
            and a part of filename to store scanned files,
            by default '/tmp/fs'
         'logfile' is a string, which defines full path and filename
            of logfile, by default not logfile used
         'regexp' is a string, which defines how to parse logfile

  New in version 0.7.0.
  '''
  name = 'filesys()'
  req_lines = 1
  ignored_virnames = []
  virname_to_return_value = {}
  def __init__(self, prefix='/tmp/fs', logfile='', regexp=''):
      self.prefix = prefix
      self.logfile = logfile
      self.reg = re.compile(regexp,re.IGNORECASE)
      self.hdr = fromhdr()
  def scanbuffer(self, buffer, args={}):
      level = 0.0
      detected = b''
      virlist = []
      # save email
      t = mktemp(self.prefix, '.eml', 'w', 0o600).autorm()
      # open logfile
      log = None
      if self.logfile:
        try:
          log = open(self.logfile, 'r')
          log.seek(0, 2) # end of file
        except IOError:
          pass
      try:
        os.write(t.fd, self.hdr+buffer)
        t.f.close()
        ts = os.open(t.root_name, os.O_RDONLY) # OSError 13 raised when virus found
        if os.fstat(ts)[6]!=len(self.hdr+buffer):
          level,detected,virlist = self.get_vir_info(
            log, (t.root_name, t.name), 13,
            'File truncated %d:%d'
             % (os.fstat(ts)[6], len(self.hdr+buffer)))
        os.close(ts)
      except OSError as err:
        (ec,es) = err.args
        if ec==13: # permission denied
          level,detected,virlist = self.get_vir_info(
            log,(t.root_name,t.name),ec,es)
        else:
          debug.traceback(9, 'filesys(): ')
          raise
      # close log
      if log:
        log.close()
      return level,detected,virlist
  def scanfile(self, files, dirname='', args={}):
      # open logfile
      if self.logfile:
        log = open(self.logfile, 'r')
        log.seek(0, 2) # end of file
      else:
        log = None
      for fname in files:
        try:
          ts = safe.osopen(fname, os.O_RDONLY)
          os.close(ts)
        except OSError as err:
          (ec, es) = err.args
          if ec==13: # permission denied
            return self.get_vir_info(log, (safe.fn(fname), fname), ec, es)
          else:
            raise
      return 0.0, b'', []
  def shortname(self, filename):
      return filename
  def get_vir_info(self, log, filenames, ec, es):
      level = 0.0
      virlist = []
      debug.echo(7, "%s: %s [%d]" % (self.name, es, ec))
      if ec==13:
        detected, info = b'', ''
        level += 1.0
        if log:
          req_lines = self.req_lines
          l = True
          timeout = time.time()+5 # 5 seconds timeout
          while (req_lines>0) and l:
            l = log.readline()
            # ignore EOF before timeout
            if not l and time.time()<timeout:
              l = True
              time.sleep(0.1)
              continue
            reg1 = self.reg.search(l)
            if reg1:
              if self.shortname(reg1.group('filename')) in filenames:
                if reg1.group('virus') in self.ignored_virnames:
                  # this virname should be ignored
                  continue
                req_lines -= 1
                if not detected:
                  detected = reg1.group('virus')
                if not info:
                  info = reg1.group('info')
                virlist.append(l)
                debug.echo(6, "%s: A: %s" % (self.name, l.strip()))
                continue
            else:
              debug.echo(4, "%s: Not decoded: %s" % (self.name, repr(l)))
            debug.echo(9, "%s: I: %s" % (self.name, l.strip()))
        if detected in list(self.virname_to_return_value.keys()):
          level,detected = self.virname_to_return_value[detected]
        if (not detected) and info:
          return level/2.0, info, virlist
        if not virlist:
          raise ScannerError("Can't decode virname.")
        return level, detected or b'UNKNOWN', virlist
      else:
        raise
