#!/usr/bin/python
import os
import time
import socket # for gethostbyaddr

files = {}
logs = []

def logbase(s):
    files["base"] = s

# realm support is a little odd, since it isn't mainline:
# just use class@realm
def log(zclass, zinst="*", zfile=None):
    if not zfile:
        zfile = zclass
    recip = "*"
    atsign = zclass.rfind("@")
    if atsign > -1:
        recip += zclass[atsign:]
        zclass = zclass[:atsign]
    zpath = os.path.join(files["base"], zfile)
    logset = [recip, zclass.lower(), zinst.lower(), zpath]
    print "Z:", zpath, logset
    if zpath in files:
        # consider, later, just aliasing the right filehandle, so this isn't an error
        raise Exception("duplicate path %s vs. %s" % (files[zpath], logset))
    logs.append(logset)
    files[zpath] = logset

def format_notice(n):
    msgs = n.message.split("\0")
    if len(msgs) == 1:
        msgsig = ""
        msgbody = msgs[0]
    elif len(msgs) == 2:
        msgsig = msgs[0]
        msgbody = msgs[1]
    else:
        msgsig = "mangled/multifield message"
        msgbody = "|\n".join(msgs)

    # if n.opcode != "": include something
    realm_suffix = "@" + zzz.zgetrealm()
    sender = n.sender
    if sender.endswith(realm_suffix):
        sender = sender.replace(realm_suffix, "")
    try:
        sender_host = socket.gethostbyaddr(n.sender_addr)[0]
        # consider paranoia: roll it forward and if they differ, log it
    except socket.herror:
        sender_host = n.sender_addr

    # also - assume auth, but log bad-auth on sender (false-from?)
    return "\n".join(["Instance: %(inst)s Time: %(time)s Host: %(host)s" %
                      {"inst":n.zinst, "time":time.ctime(n.time), "host":sender_host},
                      "From: %(sig)s <%(sender)s>" % {"sig": msgsig, "sender": sender},
                      "",
                      msgbody,
                      ])

import zzz, zzz2, select


def logsub(zport, classfile, instfile):
    for r,c,i,f in logs:
        if i == "*":
            classfile[c] = open(f, "a")
        else:
            instfile[c,i] = open(f, "a")
        zzz2.Subscription(r, c, i).sub(zport)

def run(helpertime=None, helper=None):
    n = zzz.Notice("", "", "", "", "", "")
    zport = zzz.openport()
    classfile = {}
    instfile = {}

    logsub(zport, classfile, instfile)

    zfd = zzz.zgetfd()

    scantime = None
    if helpertime:
        scantime = helpertime/4
    last_help_time = time.time()

    while 1:
        rr,ww,xx = select.select([zfd], [], [], scantime) # timeout *not* mutable
        if rr:
            while n.pending():
                n.receive()
                try:
                    dstf = instfile[n.zclass.lower(),n.zinst.lower()]
                except KeyError:
                    dstf = classfile[n.zclass.lower()]
                
                print "sending to", dstf
                print >>dstf, format_notice(n)
                dstf.flush()
        else: # must be a timeout...
            if helpertime:
                if time.time() - last_help_time >= helpertime:
                    helper()
                    logsub(zport, classfile, instfile)
                    last_help_time = time.time()



# innovations to try:
#
# sub to logins for every user you see; stash them (shelf?) and note them on the run
#   (per class, that is.)  also zlocate the users after they show up and see if they change,
#   and log that too.
#
# stat the config file and reread it? (clearly a reason not to use the python syntax.)
#   maybe take some authenticated (or local) control-messages, but that means
#   writing out the config too.
#
# support explicit reauthentication

def mk_kinit_helper(princ):
    def kinit_helper():
        os.spawnlp(os.P_WAIT, "kinit", 
                   "kinit", "-k", "-t", "ztab", princ)
        os.spawnlp(os.P_WAIT, "krb524init", 
                   "krb524init")
    return kinit_helper



if __name__ == "__main__":
    import sys
    try:
        prog, base, subsfile, princ = sys.argv
    except ValueError:
        sys.exit("Usage: " + sys.argv[0] + " basedir subsfile principal")
    logbase(base)
    for subline in file(subsfile):
        if subline.startswith("#"):
            continue
        zclass, zinst, zrecip = subline.rstrip().split(",")
        if zrecip.startswith("*@"):
            atsign = zrecip.rfind("@")
            zrealm = zrecip[atsign:]
            # not actually necessary, and forces a zinit before we have tickets
            # if zrealm[1:] != zzz.zgetrealm():
            zclass += zrealm
            zrecip = zrecip[:atsign]
        if zrecip == "%me%": zrecip = zzz.sender()
        if zrecip != "*": raise Exception("don't log personals yet (%s,%s)" % (zclass,zinst))
        print (zrecip, zclass, zinst)
        log(zclass, zinst)
    helper = mk_kinit_helper(princ)
    os.putenv("KRB5CCNAME", "FILE:ztab.k5")
    os.putenv("KRBTKFILE", "ztab.k4")
    helper()
    run(60*60, helper)

# later, fix the case where a class is the same in two realms - how do we even
# noticed which we got from (and is this reportable/exploitable?)