#!/usr/bin/python

# this is an extauth script for use with ejabberd.
# it uses a binary protocol for communicating via stdin/stdout
# see ejabberd.im/extauth for more examples
# this script uses the userdb from our django mailadmin app
# for authentification
# see (bzr) http://lukas.einfachkaffee.de/bzr/mokeladmin-rewrite/ for details
#
# please be aware that currently, this script re-reads the textfile on 
# EVERY request ejabberd sends, so it's SLOW and INEFFECTIVE.
#
# Author: Lukas Kolbe <lukas@einfachkaffee.de>


import sys
import logging
import os
sys.stderr = open('extauth_err.log', 'a')
from struct import *

# we currently only support crypt()ed passwords
PW_TYPE = "crypt"

# ejabberd starts one instance for each domain it's serving, so I needed
# a way to distinguish the logfiles. pid in the filename might not be a 
# good idea, though ...
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s',
                    filename='extauth.log',
                    filemode='a')
logging.info('extauth script started, waiting for ejabberd requests')

userdbfile = sys.argv[1]

# this is dead stupid reading of the whole(!!!) file every(!!!) time a 
# request comes in
def parse_userdb(filename):
    dict = {}
    file = open(filename, 'r')
    for line in file:
        k, v = line.split(" ")
        dict[k.strip()] = v.strip()
    return dict

class EjabberdInputError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

def check_pw(passwd, passwd_crypt):
    if PW_TYPE == "crypt":
        from crypt import crypt
        return crypt(passwd, passwd_crypt) == passwd_crypt
    elif PW_TYPE == "md5":
        from md5 import md5
        return md5(passwd).hexdigest() == passwd_crypt
    else:
        return False

def make_pw(passwd):
    if PW_TYPE == "crypt":
        from random import getrandbits
        from crypt import crypt
        return crypt(passwd, "%s" % getrandbits(16))
    elif PW_TYPE == "md5":
        from md5 import md5
        return md5(passwd).hexdigest()
    else:
        return None

def genanswer(bool):
    answer = 0
    if bool:
        answer = 1
    token = pack('>hh', 2, answer)
    return token 

def ejabberd_out(bool):
    logging.debug("Ejabberd gets: %s" % bool)
    token = genanswer(bool)
    logging.debug("sent bytes: %#x %#x %#x %#x" % 
            (ord(token[0]), ord(token[1]), ord(token[2]), ord(token[3])))
    sys.stdout.write(token)
    sys.stdout.flush()

def ejabberd_in():
    logging.debug("trying to read 2 bytes from ejabberd:")
    try:
        input_length = sys.stdin.read(2)
    except IOError:
        logging.debug("ioerror")
    if len(input_length) is not 2:
        logging.debug("ejabberd sent us wrong things!")
        raise EjabberdInputError('Wrong input from ejabberd!')
    logging.debug('got 2 bytes via stdin')

    (size,) = unpack('>h', input_length)
    return sys.stdin.read(size).split(':')

def log_success(method, username, server, success):
    if success:
        logging.info("%s successful for %s@%s" % (method, username, server))
    else:
        logging.info("%s unsuccessful for %s@%s" % (method, username, server))

def auth(userdb, username, server, password):
    logging.debug("%s@%s wants authentication ..." % (username, server))
    if isuser(userdb, username, server):
        return check_pw(password, userdb["%s@%s" % (username, server)])
    else:
        return False

def isuser(userdb, username, server):
    logging.debug("do we know %s@%s?" % (username, server))
    return userdb.has_key("%s@%s" % (username, server))

def setpass(userdb, username, server, newpassword):
    # setpass not supported on textfile
    return False

while True:
    logging.debug("start of infinite loop")

    try: 
        # this loop blocks here until it can read from ejabberd
        data = ejabberd_in()
    except EjabberdInputError, inst:
        logging.info("Exception occured: %s", inst)
        break

    userdb = parse_userdb(userdbfile)

    logging.debug('Method: %s' % data[0])
    success = False

    if data[0] == "auth":
        success = auth(userdb, data[1], data[2], data[3])
        ejabberd_out(success)
        log_success("auth", data[1], data[2], success)

    elif data[0] == "isuser":
        success = isuser(userdb, data[1], data[2])
        ejabberd_out(success)
        log_success("isuser", data[1], data[2], success)

    elif data[0] == "setpass":
        success = setpass(userdb, data[1], data[2], data[3])
        ejabberd_out(success)
        log_success("setpass", data[1], data[2], success)

    logging.debug("end of infinite loop")

logging.info('extauth script terminating')



