'''
Antivir Gateway Library for Sagator

(c) 2003-2004 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

This program is a email antivir gate for an SMTP server.

 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.

'''

import os,sys,string,re,time
import pwd,grp,socket,rfc822

from avlib import *
import stats

TEST_MSG='''\
From: Test User <test@tester.salstar.sk>\r
To: Test User2 <test2@tester2.salstar.sk>\r
Date: Fri, 16 Apr 2004 15:38:51 +0200 (CEST)\r
Subject: testing mail\r
\r
simple mail\r
'''

#########################################################################
### service class template

class service:
  '''Sagator service template'''
  def daemonize(self):
      # redirect stout and strerr
      debug.dup()
      # fork
      pid=os.fork()
      if pid<=0:
        globals.pidf.close()
      return pid

#########################################################################
### Some helpful functions

# chroot, setgid and setuid
def dochroot():
    '''
    Do chroot() and setuid(),setgid()
    '''
    def func_err(fn,f):
        try:
          eval(f)
          return 0
        except OSError,(ec,es):
          debug.echo(0,"WARNING! Can't do %10s: %s" % (fn,es))
        except AttributeError:
          debug.echo(0,"WARNING! Can't do "+f+": Your python don't have this function!")
        return 1
    uid,gid=0,0
    if globals.USER:
      try:
        uid=pwd.getpwnam(globals.USER)[2]
      except KeyError,es:
        debug.echo(0,"ERROR, getpwnam: ",[[globals.USER]],es)
        sys.exit(100)
    if globals.GROUP:
      try:
        gid=grp.getgrnam(globals.GROUP)[2]
      except KeyError,es:
        debug.echo(0,"ERROR, getgrnam: ",[[globals.GROUP]],es)
        sys.exit(100)
    if globals.CHROOT:
      if func_err('chroot',"os.chroot('"+globals.CHROOT+"')"):
        safe.ROOT_PATH=globals.CHROOT
    if gid:
      func_err('setgroups',"os.setgroups(["+str(gid)+"])")
      func_err('setgid',"os.setgid("+str(gid)+")")
    if uid:
      func_err('setuid',"os.setuid("+str(uid)+")")
    if (os.getuid()==0) or (os.getgid()==0
        or (os.geteuid()==0) or (os.getegid()==0)):
      debug.echo(0,"ERROR! I can't setuid and i am running as root!")
      sys.exit(10)

def test_scanners(scanners):
    for scnr in scanners:
      scnr.reinit()
    if os.fork()==0:
      dochroot()
      globals.scan_only=True
      scanarr,scannames=[],'None'
      mail.data=TEST_MSG
      for scnr in scanners:
        if scnr.name:
          debug.echo(1, "Testing "+scnr.name+"...")
        level,detected,virlist,scan_report,err=do_scan(scnr)
        if scan_report:
          debug.echo(0, "Scanner ",scnr.name," test failed! Disable it manually!")
          debug.echo(0, "  ",scan_report)
        scanarr=scanarr+[scnr.name]
      mail.data=''
      sys.exit(0)
    os.wait()

def do_scan(scnr,fn=''):
    '''
    Scan email (stored in mail class) for viruses.
    Use scnr scanner.
    '''
    level,detected,virlist=0.0,'',[]
    try:
      if fn:
        scnr.filename=fn
      level,detected,virlist=scnr.scanstream(mail.data)
      scnr.destroy()
      return level,detected,virlist,'',None
    except socket.error,(ec,es):
      debug.traceback(4,"do_scan: ")
      return level,detected,virlist,\
        scnr.name+": SocketError: "+es,'Socket error: '+es
    except (IOError,OSError):
      debug.traceback(4,"do_scan: ")
      return level,detected,virlist,\
        scnr.name+": Error: "+debug.traceback_value_str(),'Error: '+debug.traceback_value_str()
    except (ScannerError,ValueError),es:
      debug.traceback(4,"do_scan: ")
      return level,detected,virlist,\
        scnr.name+": "+str(es),'Error: '+str(es)
    except:
      debug.traceback(4,"do_scan: ")
      return level,detected,virlist,scnr.name+": unknown error",'Unknown error'

def checkvir(scanners):
    '''
    Check email (stored in mail class) for viruses.
    '''
    try:
      level,detected,virlist=0.0,'',[]
      scanarr,scannames=[],'None'
      for scnr in scanners:
        l,detected,virlist,scan_reply,err=do_scan(scnr)
        level+=l
        if scan_reply:
          debug.echo(0, "CheckVir: ERROR: ",scan_reply)
          return S_TEMPFAIL,scan_reply
        else:
          scanarr=scanarr+[scnr.name]
          scannames=string.join(scanarr,' ')
        if is_infected(level,detected):
          debug.echo(3,"Status: ",[detected,virlist,level])
          break
      mail.addvirusinfo(detected,scannames)
      if not is_infected(level,detected):
        return S_OK,'CLEAN' # mail delivered OK
      else: # VIRUS FOUND
        rstat=globals.ACTION
        debug.echo(5,"action: ",rstat)
        return rstat,detected
    except socket.error,(ec,es):
      debug.echo(0,"checkvir: ERROR: socket.error: ",[[ec]],es)
      debug.traceback(4,"checkvir: ")
      return S_TEMPFAIL,es
    except IOError,(ec,es):
      debug.echo(0,"checkvir: ERROR: IO: ",es)
      debug.traceback(4,"checkvir: ")
      return S_TEMPFAIL,'IOError'
    except:
      debug.traceback(3,"checkvir: ")
      return S_TEMPFAIL,'AnyError, see sagator logs'

#########################################################################
### Signal handlers

def sigchld(sn,stack):
    ''' signal: Child process terminated '''
    # try to remove older zombies (sigchld lost?)
    try:
      while 1:
        pid,s=os.waitpid(0,os.WNOHANG)
        if pid==0:
          break
        debug.echo(1,"Child exited ... pid,status: ",(pid,s))
    except OSError: # no child processes?
      pass

def sighup(sn,stack):
    ''' signal: HUP signal '''
    debug.echo(3,"SIGHUP: Reopening log ...")
    debug.reopen()

def sigusr2(sn,stack):
    ''' signal: USR2 signal '''
    debug.echo(1,"SIGUSR2: Tracelog request received ...")
    debug.debug_level=debug.trace_level
    debug.stack(1,"SIGUSR2: ")

def sigterm(sn,stack=None):
    ''' signal: Terminate process '''
    if type(sn)==type(''):
      debug.echo(1,"SERVER: Exiting - %s ..." % sn)
    else:
      debug.echo(1,"SERVER: Exiting - SIGTERM ...")
    try:
      os.unlink(globals.pid_file)
    except OSError:
      pass
    try:
      if globals.stat_coll.pid>0:
        os.kill(globals.stat_coll.pid,signal.SIGTERM)
    except:
      pass
    sigchld(None,None) # unzombie all childs
    sys.exit(0)
