#!/usr/bin/python2.3

'''sgscan - sagator's mailbox/email scanner

(c) 2003-2004 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.
'''

SAVECLEAN=''
AV_ONLY=0
VERBOSE=0

import sys,os,fcntl,re,time,struct,string
from cStringIO import StringIO
from aglib import *

# load config
from default import *
try:
  from config import *
except ImportError,es:
  if str(es)[-6:]=="config":
    print "ERROR! Config file not found! Exiting now."
    sys.exit(1)
  else:
    raise

def mailhdr(str):
    return str[0:5]=="From "

def scan(data,fn,mailcount=-1):
    mail.data=data
    mail.xheader=''
    debug.echo(2, "Object: ",fn)
    scanarr,scannames=[],'None'
    globals.reset()
    if mailhdr(fn):
      fn2=fn.split()
      fn2=string.join([fn2[1]]+fn2[3:])
    else:
      fn2=fn
    level=0.0
    for scnr in SCANNERS:
      if (AV_ONLY>0) and (scnr.get('is_spamscan')>0): # if it contain a spamscan
        debug.echo(4,"Spam scanner skipped: ",scnr.name)
      else:
        l,detected,virlist,scan_reply,err=do_scan(scnr,os.path.basename(fn))
        if err:
          if mailcount>0:
            print "%2d: %s: %s" % (mailcount,fn2,err)
          else:
            print "%s: %s" % (fn,err)
          return False
        level+=l
        if scan_reply:
          debug.echo(2, scan_reply)
        else:
          scanarr=scanarr+[scnr.name]
          scannames=string.join(scanarr,' ')
        if is_infected(l,detected):
          if mailcount>0:
            print "%2d: %s: %s [%s,%0.2f]" % (mailcount,fn2,detected,
              globals.found_by.name,level)
          else:
            print "%s: %s [%s,%0.2f]" % (fn,detected,
              globals.found_by.name,level)
          return True
    if VERBOSE:
      if mailcount>0:
        print "%2d: %s: CLEAN" % (mailcount,fn2)
      else:
        print fn+": CLEAN"
    return False

debug.set_logfile('-')
debug.set_level(0)
globals.scan_only=True
# parse command line arguments
files=[]
n=1
while n<len(sys.argv):
  if sys.argv[n]=="--help":
    print "sgscan - sagator's file scanner"
    print ""
    print "Usage: sgscan [--config F] [--debug X] \\"
    print "              [--clean] [--verbose] [--av-only] files..."
    print ""
    print "Params: --help        this help"
    print "        --config f    load \"f\" as alternate config (without .py extension)"
    print "        --logfile l   filename for logging ('-' for stdout)"
    print "        --debug l     set debug level to l"
    print "        --av-only     don't run antispam test, only antivir tests"
    print "        --clean       clean infected emails from mailboxes"
    print "        --verbose     show also CLEAN emails"
    sys.exit(0)
  elif sys.argv[n]=='--config':
    n+=1
    try:
      exec("from %s import *" % sys.argv[n])
    except ImportError,es:
      print "ImportError:",es
  elif sys.argv[n]=="--clean":
    SAVECLEAN=".clean"
  elif sys.argv[n]=="--av-only":
    AV_ONLY=1
  elif sys.argv[n]=="--verbose":
    VERBOSE=1
  elif sys.argv[n]=="--debug":
    n+=1
    debug.set_level(int(sys.argv[n]))
  else:
    files.append(sys.argv[n])
  n+=1

# reinit scanners
for scnr in SCANNERS:
  scnr.reinit()
safe.ROOT_PATH=CHROOT

cwd=os.getcwd()
total,infected=0,0
time0=time.mktime(time.localtime())
for fn in files:
  while 1:
    try:
      try:
        os.chdir(cwd)
        f=open(fn,"r+")
      except IOError,(ec,es):
        print "%s: IOError: %s [%d], skipping file" % (fn,es,ec)
        break
      line1=f.readline()
      if mailhdr(line1):
        print fn+": mailbox detected"
        if SAVECLEAN:
          rv=fcntl.fcntl(f.fileno(), fcntl.F_SETFL, os.O_NDELAY)
          rv=fcntl.fcntl(f.fileno(), fcntl.F_SETLKW,
                         struct.pack('hhqqhh', fcntl.F_WRLCK, 0,0,0,0,0))
          fc=open(fn+SAVECLEAN,"w")
          os.chmod(fn+SAVECLEAN,0700)
        mailcount=0
        box_infected=0
        while 1:
          mailcount+=1
          frm=line1
          lines=StringIO()
          while 1:
            line1=f.readline()
            if line1=="": break
            if mailhdr(line1): break
            lines.write(line1)
          if not scan(lines.getvalue(),frm.strip(),mailcount):
            if SAVECLEAN:
              fc.write(frm)
              fc.write(lines.getvalue())
          else:
            infected+=1
            box_infected+=1
          total+=1
          lines.close()
          if line1=="": break
        # remove .clean file if no viruses found
        if SAVECLEAN:
          os.chdir(cwd)
          if box_infected>0:
            # mailbox cleaned, viruses found
            fc.close()
            os.lseek(f.fileno(),0,0)
            os.ftruncate(f.fileno(),0)
            f.write(open(fn+SAVECLEAN,'r').read())
          try:
            os.unlink(fn+SAVECLEAN)
          except OSError:
            pass
      elif re.search('^NOOP CONNECT FROM: ',line1):
        print fn+": sagator's quarantine file detected"
        # skip to "data"
        while 1:
          line1=f.readline()
          if re.compile("^DATA",re.IGNORECASE).search(line1):
            break
        lines=StringIO()
        while 1:
          line1=f.readline()
          if (line1=='.\n') | (line1=='.\r\n'):
            break
          lines.write(line1)
        if scan(lines.getvalue(),os.path.basename(fn)):
          infected+=1
        total+=1
        lines.close()
        break # skip to next file
      else:
        # not mailbox
        debug.echo(2, "Scanning file: "+fn)
        if scan(open(fn,'r').read(),fn):
          infected+=1
        total+=1
      f.close()
      break
    except IOError,(ec,es):
      if ec!=35: # Resource deadlock avoided
        raise
      debug.echo(0, "Deadlock avoided, repeating last scan...")
print "Total infected/spamed files/emails:",\
  str(infected)+"/"+str(total),"in:", \
  time.mktime(time.localtime())-time0,"seconds"
