#!/usr/bin/python
# -*- coding: utf-8 -*-


'''

Script zum Versand eines Fax

Aufruf:

    omgfaxdeliver [-d]  -f <faxfile> -i <ident> [-m <mailhost[:port]>] [-o <absender>] [-p <seiten>]
                     [-r <empfaenger>] [-s <subject>] -t <templatefile> [ name1=wert name2=wert name3 ...]

             -d Debug Ausgaben
             -f File name of Fax
             -i Identifier fuer Exchange
             -m SMTP-Host
             -o Mailadresse des Absenders, default: OfficeMaster@<fqdn>
             -p Anzahl Seiten, default: 1
             -q Requeue der Mail
             -r Mailadresse des Empfaengers
             -s Betreff-Text
             -t Name des Templates zum Aufbau der Mail

             name1[=wert] ...    Name und Wert fuer Platzhalter in den Templates
                                 Strings muessen in ' oder " eingeschlossen werden,
                                 vor und nach dem "=" darf kein Leerzeichen stehen.
                                 Wird dem Namen kein Wert zugewiesen, so wird er auf "1" gesetzt.



             Die Platzhalter fuer die Templates koennen auch per Environment definiert werden, Namen auf der Kommandozeile
             ueberschreiben diese Werte.
             Die Variablen FAX_PAGES, FAX_MAILHOST, FAX_ORIGINATOR und FAX_RECIPIENT werden, wenn sie nicht definiert sind,
             mit den Werten von -p, -m, -r bzw. -o besetzt.
             Das ermoeglicht es, innerhalb der Templates von -r und -o abweichende Werte benutzen zu koennen.
             Wird -m bzw. FAX_MAILHOST nicht angegeben, so wird aus der Domain des Empfaengers der MX-Host ermittelt.
             Der bei -i uebergebene String kann in den Templates als FAX_IDENT zititert werden.
             Ist -s nicht angegeben und auch FAX_SUBJECT nicht definiert, so wird "Fax for you" genommen.
             Innerhalb des Templates gibt es zwei spezielle Platzhalter: ##FAX_FILE## und ##FAX_PREVIEW##
             FAX_FILE enthaelt den Inhalt des Fax als Tiff base64 encoded
             FAX_PREVIEW enthaelt ein verkleinertes, 200 Pixel breites Fax als PNG base64 encoded


'''


import sys
sys.path.append("/usr/lib/python2.6/site-packages")

import os
import re
import datetime
from optparse import OptionParser
import smtplib
import base64
import socket
from subprocess import call
from tempfile import NamedTemporaryFile
import DNS
from shutil import move
#from pwd import getpwnam


GOODFAXDIR = "/data/fax/good/"
BADFAXDIR = "/data/fax/bad/"

THTTPDUSER = "www-data"

DEFAULTTEMPLATE = "/var/templates/fax.template"

OK = 1
NOK = 0

DEBUG = False

def decodeIdent(ident):
    print("######### RET")
    decoded=base64.decodestring(ident)
    decodedSplit=(decoded.split("&"))
    for item in decodedSplit:
        if("CallerId=" in item):
            val=item.split("=")[1]
            return val
    return ""

def dbg(dbgmsg):
    '''
    Print Debugging message
    '''
    if DEBUG:
        print >> sys.stderr, dbgmsg


def makebase64(filename, pdfname, makethumb):

    '''
    Return content of filename and preview image base64 encoded.
    additionally a PDF file will be generated
    '''

    if not os.path.isfile(filename):
        print "Warning: Fax file", filename, " not found"
        if makethumb:
            return ('', '')
        else:
            return ''

    handle = NamedTemporaryFile()
    try:
        dbg("Convert bff: faxfmt -bff %s -tif %s" % (filename, handle.name))
        retcode = call("faxfmt -bff " + filename + " -tif " + handle.name, shell=True)
        if retcode < 0:
            print >> sys.stderr, "Child was terminated by signal", -retcode
#        else:
#            print >>sys.stderr, "Child returned", retcode
    except OSError, e:
        print >> sys.stderr, "Execution failed:", e
        sys.exit()

    content = handle.read()

    # Same for thumbnail
    if makethumb:
        handlep = NamedTemporaryFile()
        try:
            dbg("Make thumbnail: omgfaxthumb -thumbnail 200 %s png:%s" % (handle.name, handlep.name))
            #retcode = call("omgfaxthumb -thumbnail 200 " + handle.name + " " + handlep.name, shell=True)
            #dbg("convert " + handle.name + " png:" + handlep.name)
            retcode = call("omgfaxthumb -thumbnail 200 " + handle.name + " png:" + handlep.name, shell=True)
            if retcode < 0:
                print >> sys.stderr, "Child was terminated by signal", -retcode
#            else:
#                print >>sys.stderr, "Child returned", retcode
        except OSError, e:
            print >> sys.stderr, "Execution failed:", e
            sys.exit()

        contentp = handlep.read()
        handlep.close()

    handle.close()

    # Last step: generate a pdf from fax
    try:
        dbg("Convert bff: faxfmt -bff %s -pdf /tmp/%s" % (filename, pdfname))
        retcode = call("faxfmt -bff " + filename + " -pdf " + "/tmp/" + pdfname, shell=True)
        if retcode < 0:
            print >> sys.stderr, "Convert to pdf: child was terminated by signal", -retcode
#        else:
#            print >>sys.stderr, "Child returned", retcode
    except OSError, e:
        print >> sys.stderr, "Convert to pdf: Execution failed:", e
        sys.exit()

    # That's all folks
    if makethumb:
        return (base64.b64encode(content), base64.b64encode(contentp))
    else:
        return base64.b64encode(content)


def processtemplate(tstr):

    '''
    Process template: replace vars insert files and replace <cr>
    '''

    dbg("Processing template")
    templatetext = re.sub('\^r', '\r', tstr)

    #  First scan for conditionals
    dbg("processing conditionals")
    pattern = re.compile("(@@IF\s+)(?P<varname>[A-Z_0-9]+)(\s+(?P<q>.)(?P<varval>.+?)(?P=q))?@@(?P<insert>.*?)(?P<endif>@@FI@@)", re.DOTALL)
    while pattern.search(templatetext):
        m = pattern.search(templatetext)
#        print " varname:",m.group('varname'),"opt:",m.group(3),"value opt:",m.group('varval'),"fi:",m.group('endif'),"varval:",m.group('varval'), "  quote:",m.group('q')
        varname = m.group('varname')
        varvalue = m.group('varval')
        if varname in templatevars:
            if varvalue is None:
                # It's defined so insert the code
                templatetext = templatetext[:m.start(1)] + m.group('insert') + templatetext[m.end('endif'):]
            else:
                # Test value
                if templatevars[varname] == varvalue:
                    # We have the correct value for this var. Insert the code
                    templatetext = templatetext[:m.start(1)] + m.group('insert') + templatetext[m.end('endif'):]
                else:
                    # !=  so skip the code
                    templatetext = templatetext[:m.start(1)] + templatetext[m.end('endif'):]
        else:
            templatetext = templatetext[:m.start(1)] + templatetext[m.end('endif'):]

    dbg("Replacing vars")

    # Now replace
    pattern = re.compile(r"@@([A-Z_0-9]+)@@")
    while pattern.search(templatetext):
        m = pattern.search(templatetext)
        varname = m.group(1)
        if varname in templatevars:
            templatetext = templatetext[:m.start()] + templatevars[varname] + templatetext[m.end():]
        else:
            print "Warning: Undefined", varname
            templatetext = templatetext[:m.start()] + templatetext[m.end():]

    # All vars are processed now insert files

    dbg("Inserting files")
    xpat = re.compile(r"##FAX_PREVIEW##")
    if xpat.search(templatetext):
        (templatevars['FAX_FILE'], templatevars['FAX_PREVIEW']) = makebase64(options.fax, datepart + '.pdf', True)
    else:
        dbg("No FAX_PREVIEW placeholder in template")
        templatevars['FAX_FILE'] = makebase64(options.fax, datepart + '.pdf', False)

    pattern = re.compile(r"##([A-Z_0-9]+)##")
    while pattern.search(templatetext):
        m = pattern.search(templatetext)
        varname = m.group(1)
        if varname in templatevars:
            templatetext = templatetext[:m.start()] + templatevars[varname] + templatetext[m.end():]
        else:
            print "Warning: no value for ", varname, " given"
            templatetext = templatetext[:m.start()] + templatetext[m.end():]
    return templatetext


def getmxhost(rec):
    ''' get MX host for domain
    '''
    if rec.find("@") >= 0:
        dummy, domain = rec.split("@")
    else:
        domain = rec

    DNS.ParseResolvConf()
    hostlist = DNS.mxlookup(domain)
    if len(hostlist) == 0:
        print "No MX record for @%s found" % (domain)
        sys.exit()
    dummy, mxhost = hostlist[0]
    dbg("getmxhost: found mxhost %s" % (mxhost))
    if mxhost is None:
        print "I'm goofed. Got List with MX hosts but no tuple found:", hostlist
        sys.exit(1)
    else:
        return mxhost
        #return "10.1.1.2:2525"


def getmailhost(rec):
    ''' return mailhost for smtp delivery.
    '''
    dbg("getmailhost")

    if options.mailhost is None:
        dbg("getmailhost: -m not given")
        # No option given perhaps in environment
        if 'FAX_MAILHOST' in templatevars:
            dbg("getmailhost: Found mailhost in dictionary")
            return templatevars['FAX_MAILHOST']
        else:
            # No. Sigh we have to ask dns
            templatevars['FAX_MAILHOST'] = getmxhost(rec)
            dbg("getmailhost: found mailhost by getmxhost %s" % (templatevars['FAX_MAILHOST']))
            return templatevars['FAX_MAILHOST']

    else:
        # Good. mailhost given as option.
        if options.mailhost == '':
            print "getmailhost: -m has an empty string as argument"
            dbg("getmailhost: -m not given")
            # No option given perhaps in environment
            if 'FAX_MAILHOST' in templatevars:
                dbg("getmailhost: Found mailhost in dictionary")
                return templatevars['FAX_MAILHOST']
            else:
                # No. Sigh we have to ask dns
                templatevars['FAX_MAILHOST'] = getmxhost(rec)
                dbg("getmailhost: found mailhost by getmxhost %s" % (templatevars['FAX_MAILHOST']))
                return templatevars['FAX_MAILHOST']
        # Put into dictionary
        templatevars['FAX_MAILHOST'] = options.mailhost
        return options.mailhost


def postproc(flag, fbase):

    dbg("postproc: start postproc flag=%s fbase=%s" % (flag, fbase))
    pdffile = fbase + ".pdf"
    rawmailfile = fbase + ".mail"
    dbg("postproc: start postproc pdffile=%s rawmailfile=%s" % (pdffile, rawmailfile))

    # We need to change ownership for thttpd
    #uid = getpwnam(THTTPDUSER)[2]
    #gid = getpwnam(THTTPDUSER)[3]

    if options.queue:
        dbg("postproc: queue")
        if flag:
            # sucessfully requeued
            if os.path.isfile(BADFAXDIR + rawmailfile):
                move(BADFAXDIR + rawmailfile, GOODFAXDIR + rawmailfile)
                #os.chown(GOODFAXDIR + pdffile, uid, gid)
            if os.path.isfile(BADFAXDIR + pdffile):
                move(BADFAXDIR + pdffile, GOODFAXDIR + pdffile)
                #os.chown(GOODFAXDIR + pdffile, uid, gid)
        #else:
            # requeue failed, do nothing
    else:
        dbg("postproc: save")
        if flag:
            # good fax
            dbg("postproc: good fax")
            fp = open(GOODFAXDIR + rawmailfile, "wb")
            fp.write(msg)
            fp.close()
            #os.chown(GOODFAXDIR + rawmailfile, uid, gid)
            if os.path.isfile("/tmp/" + pdffile):
                dbg("postproc: mv pdf file")
                move("/tmp/" + pdffile, GOODFAXDIR + pdffile)
                #os.chown(GOODFAXDIR + pdffile, uid, gid)
            else:
                print "Warning: pdf /tmp/%s is missing\n" % (pdffile)
        else:
            # bad fax
            dbg("postproc: bad fax")
            fp = open(BADFAXDIR + rawmailfile, "wb")
            fp.write(msg)
            fp.close()
            #os.chown(BADFAXDIR + rawmailfile, uid, gid)

            if os.path.isfile("/tmp/" + pdffile):
                move("/tmp/" + pdffile, BADFAXDIR + pdffile)
                #os.chown(BADFAXDIR + pdffile, uid, gid)
            else:
                print "Warning: pdf /tmp/%s is missing\n" % (pdffile)

### Here we go

#requeue = 0
errexit = False

#pdffile = datetime.datetime.now().strftime("%Y%m%d-%H%M%S.pdf")

usage = "usage: %prog [options]"
parser = OptionParser(usage=usage)
parser.add_option("-d", "--debug", metavar="BOOL", help="Enable debugging messages", action="store_true", default=False)
parser.add_option("-f", "--fax", metavar="FILE", help="fax file name")
parser.add_option("-i", "--ident", metavar="STRING", help="Identifier")
parser.add_option("-m", "--mailhost", metavar="HOST", help="smtp host name")
parser.add_option("-o", "--originator", metavar="MAILADDRESS", help="mail address of originator")
parser.add_option("-p", "--pages", metavar="INT", type="int", help="number of pages", default=1)
parser.add_option("-q", "--queue", metavar="FILE", help="Requeue fax")
parser.add_option("-r", "--recipient",  metavar="MAILADDRESS", help="mail address of recipient")
parser.add_option("-s", "--subject", metavar="STRING", help="Subject of mail")
parser.add_option("-t", "--template", metavar="FILE", help="template file name")
(options, args) = parser.parse_args()

if options.debug:
    DEBUG = True


if options.queue:
    # Requeue Message
    datepart, dummy = os.path.splitext(os.path.basename(options.queue))
    try:
        rfp = open(options.queue, 'rb')
        msg = rfp.read()
        rfp.close()
    except IOError:
        print "Error: %s not found\n" % (options.queue)
        sys.exit(1)
    # Now search From and to
    frompattern = re.compile("From: ([a-zA-Z0-9_\@\.-]*)")
    m = frompattern.search(msg)
    mailsender = m.group(1)
    topattern = re.compile("To: ([a-zA-Z0-9_\@\.-]*)")
    m = topattern.search(msg)
    mailrecipient = m.group(1)

    # Get original mailhost.
    mailhostpattern = re.compile("X-MAILHOST: ([a-zA-Z0-9_\.\-:]*)")
    m = mailhostpattern.search(msg)
    if m is not None:
        mailhost = m.group(1)

    # Note: At the moment mailhost from mail header will not be used
    #mailhost = getmxhost(mailrecipient)


else:

    datepart = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")

    # Get environment (template vars may be defined here)
    templatevars = dict((key, value) for key, value in os.environ.iteritems())

    # Get placeholders
    for a in args:
        if "=" in a:
            k, v = a.split('=')
            templatevars[k] = v
        else:
            templatevars[a] = "1"

    if options.debug:
        print >> sys.stderr, "Placeholders:"
        #print templatevars
        for k, v in templatevars.iteritems():
            print >> sys.stderr, k, "=", v

    if options.fax is None:
        print "fax file missing"
        sys.exit(1)

    if options.recipient is None:
        print "recipient missing"
        sys.exit(1)
    else:
        mailrecipient = options.recipient

    if options.originator is None:
        if 'FAX_ORIGINATOR' in templatevars:
            mailsender = templatevars['FAX_ORIGINATOR']
        else:
            mailsender = 'OfficeMaster@' + socket.getfqdn()
    else:
        mailsender = options.originator

    mailhost = getmailhost(mailrecipient)

    if options.subject is None:
        if 'FAX_SUBJECT' not in templatevars:
            templatevars['FAX_SUBJECT'] = "Fax for you"
    else:
        templatevars['FAX_SUBJECT'] = options.subject

    if options.ident is None:
        print "ident missing"
        sys.exit(1)

    # use ident like a var
    templatevars['FAX_IDENT'] = options.ident

    # This allows to have different originator/recipient for smtplib and Templates
    if not 'FAX_ORIGINATOR' in templatevars:
        templatevars['FAX_ORIGINATOR'] = mailsender
    if 'FAX_RECIPIENT' not in templatevars:
        templatevars['FAX_RECIPIENT'] = mailrecipient
    if 'FAX_PAGES' not in templatevars:
        templatevars['FAX_PAGES'] = "%d" % (options.pages)

    callerID=decodeIdent(options.ident)
    datepart = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + "{" + callerID + "}{" + options.recipient + "}"

    # Now open and process template file
    dbg("Opening template file")

    if options.template is not None:
        notfound = 0
        try:
            fp = open(options.template, 'rb')
            msg = processtemplate(fp.read())
            fp.close()
        except IOError:
            print "Warning: Template %s not found\n" % (options.template)
            notfound = 1
            msg = "This is a Fax (Template is missing)"
    else:
        # Think about: Use environment var? Make own Text with placeholders?
        print "Warning: No template given, trying to use default"
        notfound = 1
        #sys.exit(1)

    if notfound == 1:
        options.template = DEFAULTTEMPLATE
        try:
            fp = open(options.template, 'rb')
            msg = processtemplate(fp.read())
            fp.close()
        except IOError:
            print "Error: Default template %s not found\n" % (options.template)
            msg = "This is a Fax (Template is missing)"
            sys.exit(1)


# Now send mail
print "Connecting to mailhost %s" % (mailhost)
try:
    s = smtplib.SMTP(mailhost)
except socket.error, e:
    postproc(NOK, datepart)
    print "SMTP socket error:", e[1]
    errexit = True
except smtplib.SMTPConnectError, e:
    postproc(NOK, datepart)
    print "SMTP connection error", e[1]
    errexit = True
except:
    postproc(NOK, datepart)
    errexit = True
    raise
else:
    if DEBUG:
        s.set_debuglevel(True)
    try:
        print "Sending mail, From: %s   Recipient: %s" % (mailsender, mailrecipient)
        s.sendmail(mailsender, [mailrecipient], msg)  # Hier X-MAILHOST ??
    except AttributeError:  # SMTP instance has no attribute 'sock'
        postproc(NOK, datepart)
        print "Connect to Mailhost failed ( empty hostname?)"
        errexit = True
    except smtplib.SMTPServerDisconnected:
        postproc(NOK, datepart)
        print "Please run connect() first"
        errexit = True
    except smtplib.SMTPSenderRefused:
        postproc(NOK, datepart)
        print "Illegal originator name:", options.originator
        errexit = True
    except smtplib.SMTPRecipientsRefused:
        postproc(NOK, datepart)
        print "Illegal recipient name:", mailrecipient
        errexit = True
    except smtplib.SMTPDataError, (code, resp):
        postproc(NOK, datepart)
        print "Data Error: %s %s" % (code, resp)
        errexit = True
    except:
        postproc(NOK, datepart)
        errexit = True
        raise
    else:
        postproc(OK, datepart)
    finally:
        try:
            s.quit()
        except:
            pass
finally:
    if not options.queue:
        if options.fax is not None and os.path.isfile(options.fax):
            os.unlink(options.fax)


#print "Done"

# Just to be really shure
if options.fax is not None and os.path.isfile(options.fax):
    os.unlink(options.fax)
if errexit:
    sys.exit(1)
else:
    print "250 OK"  # To make WEb UI happy
    sys.exit(0)
