'''
spamassassin module, version 0.6.1

(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 re,socket

__all__=['spamassassin','spamassassind']

class spamassassin(ascanner):
  '''
  SpamAssassin scanner
  
  Usage: spamassassin(['/usr/bin/spamassassin','-e'],SPAMSTRING,SA_MAX_FILE)
  
  Where: in [] is a path and argumets of spamassassin binary
         SPAMSTRING is a string, which define a spamassassin report,
           which is checked. If it is found, mail is assigned as 'SPAM'
           (by default all reports with exit status!=0 are reported as spam).
         SA_MAX_FILE is a number, which defines maximum file size,
           which can be tested. If a mail (with header) is larger,
           it is not checked (by default 500000).

  This scanner scans for spams. If a spam is returned, a string
  SPAM is returned as virus name. This scanner uses a spamassassin
  binary. For better performance, configure spamd and use
  spamassassind.
  
  WARNING! You need to install spamassassin into chroot manually.
  mkchroot.sh script does it not. If you want to use spamassasin
  with chroot and don't want to make a copy in chroot, use
  a spamassassind scanner, but remember, that scanning for spams
  are not done in chroot environment.
  
  WARNING! This scanner is not fully compatible with header adding.
  Some data from headers are not added!
  '''
  is_spamscan=1
  name='SpamAssassin()'
  def __init__(self,arg,spamstring='',sa_max_file=500000):
      self.arg=arg
      self.SPAMSTRING=spamstring
      self.SA_MAX_FILE=sa_max_file
  def scanstream(self,stream):
      exitstatus=''
      if len(stream)>self.SA_MAX_FILE:
        return 0.0,'',['File too long to test it. Size: '+str(len(stream))]
      pf=popen(self.arg)
      pf.write(stream)
      pf.tocmd.close()
      pf.wait()
      level,ret,exitstatus,lastspamline=0.0,[],'',0
      for line in pf.readlines():
        if re.search("^$",line):
          break
        if re.search("^\t",line):
          if lastspamline:
            ret.append(ret.pop(-1)+line)
        else:
          if re.search("^X-Spam-",line):
            ret.append(line)
            lastspamline=1
            if self.SPAMSTRING:
              if re.search(self.SPAMSTRING,line):
                exitstatus='SPAM'
            reg2=re.search("X-Spam-Status: Yes, hits=([^, ]*)[, ]",line)
            if reg2:
              level=float(reg2.group(1))
          else:
            lastspamline=0
      for i in ret:
        mail.addheader(i)
      pf.fromcmd.close()
      if (pf.exitstatus>0) and (self.SPAMSTRING==''):
        exitstatus='SPAM'
      return level,exitstatus,ret

class spamassassind(ascanner):
  '''
  SpamAssassinD scanner
  
  Usage: spamassassind(['localhost',783],REQ_SPAM_LEVEL,SA_MAX_FILE)

  Where: ['localhost',783] are the host and port of spamd.
         if REQ_SPAM_LEVEL is less than spam hits, virus is returned,
           if it is -1 (default), spam level from spamassassin config is used
         for SA_MAX_FILE see spamassassin scanner description.
  
  This scanner scans for spams. If a spam is returned, a string
  SPAM is returned as virus name.
  This scanner uses a spamassassind daemon. If you are not familiar with it,
  you also can use spamassassin binary. Look at spamassassin scanner.
  '''
  is_spamscan=1
  name='SpamAssassinD()'
  reg_x_spam=re.compile('^(X-Spam-.*?)\r?\n[^ \t]',re.M|re.S).search
  reg_eoh=re.compile('^(.*?\r?\n\r?\n)',re.M|re.S).search
  reg_status=re.compile(
    '^X-Spam-Status: (Yes|No), (score|hits)=([-0-9.]*) (required)=([-0-9.]*) ').search
  reg_hdr_body=re.compile('^([^:]*):[ \t]*(.*)',re.M|re.S).search
  def __init__(self,arg,reqspamlevel=-1,sa_max_file=500000):
      self.arg=arg
      self.REQSPAMLEVEL=reqspamlevel
      self.SA_MAX_FILE=sa_max_file
  def scanstream(self,stream):
      if len(stream)>self.SA_MAX_FILE:
        return 0.0,'',['File too long to test it. Size: '+str(len(stream))]
      s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      socket_settimeout(s,120)
      s.connect((self.arg[0],self.arg[1]))
      f=s.makefile('rw',BUFSIZE)
      s.sendall("PROCESS SPAMC/1.2\r\n")
      s.sendall("Content-length: "+str(len(stream))+"\r\n")
      s.sendall("User: vscan\r\n\r\n")
      s.sendall(stream)
      s.shutdown(1)
      s.close()
      output1=f.readline().rstrip()
      debug.echo(4,"Spamd output: ",[[len(stream)]],str(output1))
      # spamd returned any error?
      if not re.search('^SPAMD/[0-9.]* 0 ',output1):
        raise ScannerError,'SpamAssassind: '+output1
      # workaround for 8bit mails
      if re.search('^SPAMD/[0-9.]* 76 ',output1):
        debug.echo(3,"ERROR: spamd(): Spamd returned: "+output1)
        return 0.0,'',[]
      # spamd returned EX_OK
      content_length=0
      while True:
        output2=f.readline()
        if not output2.strip(): # end of spamd header?
          break
        reg1=re.search('^Content-length: ([0-9]*)$',string.rstrip(output2))
        if reg1:
          content_length=int(reg1.group(1))
        else:
          debug.echo(0,"ERROR: spamd(): unknown header: %s" % output2)
      # read reported message and close connection
      output=f.read(content_length)
      f.close()
      # process returned email's header
      try:
        header=self.reg_eoh(output).group(1)
      except:
        header=output
      pos=0
      ret=[]
      spam_yn,spam_score,spam_req='No',0.0,1000.0
      while pos<len(header):
        reg2=self.reg_x_spam(header[pos:])
        if reg2:
          line=reg2.group(1).rstrip()
          pos+=reg2.end(1)
          ret.append(line)
          reg3=self.reg_status(line)
          if reg3:
            spam_yn=reg3.group(1)
            spam_score=float(reg3.group(3))
            spam_req=float(reg3.group(5))
          if line[:10]=='X-Spam-Rep':
            debug.echo(5,"spamd(): ",line)
          else:
            debug.echo(4,"spamd(): ",line)
          #reg4=self.reg_hdr_body(line)
          mail.xheader+=line+'\r\n' # not safe, use mail.addheader instead!
        else:
          break
      if self.REQSPAMLEVEL>=0:
        req_level=self.REQSPAMLEVEL
      else:
        req_level=spam_req
      if spam_score>=req_level:
        detected="SPAM"
      else:
        detected=''
      debug.echo(3,"Spamd status: [%s], score=%f/%f" \
        % (detected,spam_score,spam_req))
      return float(spam_score/req_level),detected,ret
