#!/usr/bin/python
#
# Store users in imapauth table.
# User defaults from database or defaults from config file
#
#

import sqlite3
import getopt
import sys
import os
import logging
import ConfigParser
import fileinput
import string
import base64
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA

versionstring='benno-imapuser version 2.6.0'


class User:
    def __init__(self,imapuser,imaphost):
        self.id=''
        self.imapuser=imapuser
        self.imaphost=imaphost
        self.name=''
        self.password=''
        self.archive=''
        self.role=''
        self.email=[]
        self.imapstatus=''
        self.status='3'


    def List(self,config):
        """List all users"""
        logging.debug("Load users from database")
        conn=DBaccess(config).conn
        c=conn.cursor()
        sql="SELECT imapuser,imaphost FROM imapuser"
        userlist=[]
        try:
            c.execute(sql)
            for row in c:
                userlist.append([row[0],row[1]])
            c.close()
            conn.close()
        except Exception, e:
            sys.exit("Cannot read data from user table: %s" % e)
        return userlist


    def load_defaults(self,config):
        """Load default settings from config file"""
        logging.debug("Set default attributes for user from configfile")
        if self.name=='':
            self.name=self.id
        if self.role=='':
            self.role=config.get('DEFAULT','role')
        if self.archive=='':
            self.archive=config.get('DEFAULT','archive')
        if self.imaphost=='':
            self.imaphost=config.get('DEFAULT','host')
        self.set_email(config)


    def set_email(self,config):
        """Load adresses for user from adress table"""
        logging.debug("Load user aliases from adress table""")
        conn=DBaccess(config).conn
        c=conn.cursor()
        try:
            sql="SELECT address FROM address WHERE id='%s'" % self.id
            c.execute(sql)
            for row in c:
                self.email.append(row[0])
            c.close()
            conn.close()
        except Exception, e:
            sys.exit("Cannot read data from address table: %s" % e)


    def load(self,config):
        """Load user data from database"""
        logging.debug("Load userdata from database")
        conn=DBaccess(config).conn
        c=conn.cursor()
        sql="SELECT id,imapstatus,status FROM imapuser WHERE imapuser ='%s' AND imaphost = '%s'" % (self.imapuser,self.imaphost)
        c.execute(sql)
        rows=c.fetchall()
        if len(rows) > 0:
            for row in c:
                if self.id=='':
                    self.id=row[0]
                if self.imapstatus=='':
                    self.imapstatus=row[1]
                if self.status=='':
                    self.status=row[2]
        else:
            raise UserError('User "%s@%s" not in table "imapuser".' % (self.imapuser,self.imaphost))
        sql="SELECT name,archive,role FROM user WHERE id ='%s'" % (self.id)
        c.execute(sql)
        rows=c.fetchall()
        if len(rows) > 0:
            for row in c:
                if self.name=='':
                    self.name=row[0]
                if self.archive=='':
                    self.archive=row[1]
                if self.role=='':
                    self.role=row[2]
        else:
            raise UserError('User "%s" not in table "user".' % (self.id))

        self.load_defaults(config)


    def show(self):
        print"Id:        %s" % self.id
        print "Userid:    %s" % self.imapuser
        print "Imaphost   %s" % self.imaphost
        print "Name:      %s" % self.name
        print "Container: %s" % self.archive
        print "E-Mail:   ",
        for addr in self.email:
            print "%s" % addr,
        print


    def save(self,config):
        """Save user data or update user password"""
        pwstring=''
        if config.getboolean('DEFAULT','store password'):
            if not self.password.startswith('$'):
                logging.debug("Encrypt and store password")
                keyfile=config.get('DEFAULT','keyfile')
                key=RSA.importKey(open(keyfile).read())
                cipher=PKCS1_v1_5.new(key)
                encpass=cipher.encrypt(self.password)
                pwstring='$rsa$_'+base64.b64encode(str(encpass))
            else:
                pwstring=self.password

        conn=DBaccess(config).conn
        conn.text_factory = str
        c=conn.cursor()
        try:
            sqlinsuser="INSERT INTO user (id,name,archive,role) VALUES('%s','%s','%s','%s')" % (self.id,self.name,self.archive,self.role)
            sqlinsimap="INSERT INTO imapuser (id,imapuser,imaphost,password,imapstatus,status) VALUES('%s','%s','%s','%s','%s','%s')" % (self.id,self.imapuser,self.imaphost,pwstring,'',self.status)
            logging.debug("Store user in database")
            c.execute(sqlinsuser)
            c.execute(sqlinsimap)
            logging.debug("Store imapuser in database")
        except Exception, e:
            logging.debug("Error insert entry: %s" % e) 
            # user exists - update entry
            try:
                logging.debug("Update user and imapuser database")
                sqluserdel="DELETE FROM user WHERE id='%s'" % self.id
                sqlimapdel="DELETE FROM imapuser WHERE id='%s'" % self.id
                c.execute(sqluserdel)
                c.execute(sqlinsuser)
                c.execute(sqlimapdel)
                c.execute(sqlinsimap)
            except Exception, e:
                logging.error("Cannot update user data: %s",e)
        c.close()
        conn.commit()
        conn.close()
        self.save_email(config)


    def save_email(self,config):
        """Save user email"""
        conn=DBaccess(config).conn
        conn.text_factory = str
        c=conn.cursor()
        for addr in self.email:
            try:
                logging.info("Store email address \"%s\" in database" % addr)
                sql="INSERT INTO address (id,address) VALUES('%s','%s')" % (self.id,addr)
                c.execute(sql)
            except Exception as e:
                logging.debug("Cannot insert address: %s",e)
        c.close()
        conn.commit()
        conn.close()


    def changeStatus(self,config,status):
        """Change status bits of user"""
        # 0   00  - disabled
        # 1   01  - import enabled
        # 2   10  - login enabled
        # 3   11  - import + login enabled
        conn=DBaccess(config).conn
        c=conn.cursor()
        sql="UPDATE imapuser SET status = '%s' WHERE id='%s'" % (status,self.id)
        try:
            c.execute(sql)
            conn.commit()
            c.close()
            conn.close()
        except Exception, e:
            sys.exit("Cannot change status for user: %s" % e)


    def delete(self,config):
        """Remove user data from database"""
        conn=DBaccess(config).conn
        c=conn.cursor()
        sql="DELETE FROM imapuser WHERE id='%s'" % self.id
        try:
            c.execute(sql)
            conn.commit()
        except Exception, e:
            sys.exit("Cannot delete user from imapuser table: %s" % e)
        sql="DELETE FROM user WHERE id='%s'" % self.id
        try:
            c.execute(sql)
            conn.commit()
        except Exception, e:
            sys.exit("Cannot delete user from user table: %s" % e)
        sql="DELETE FROM address WHERE id='%s'" % self.id
        try:
            c.execute(sql)
            conn.commit()
        except Exception, e:
            sys.exit("Cannot delete user from address table: %s" % e)
        c.close()
        conn.close()



class DBaccess:
    def __init__(self,config):
        self.conn=''
        dbtype=config.get('DEFAULT','dbtype')
        if dbtype=='sqlite3':
            import sqlite3
            dbfile=config.get('DEFAULT','userdb')
            try:
                self.conn=sqlite3.connect(dbfile)
            except Exception, e:
                sys.exit('Cannot connect database "%s": %s' % (dbfile,e))
        elif dbtype=='mysql':
            import MySQLdb
            dbhost=config.get('DEFAULT','dbhost')
            dbuser=config.get('DEFAULT','dbuser')
            dbpass=config.get('DEFAULT','dbpass')
            dbname=config.get('DEFAULT','dbname')
            try:
                self.conn=MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpass,db=dbname)
            except Exception, e:
                sys.exit('Cannot connect database "%s": %s' % (dbname,e[1]))


class UserError(Exception):
    def __init__(self,msg):
        self.msg=msg

    def __str__(self):
        return self.msg


################################################################################
def print_usage(errormsg=''):
    if errormsg != '':
        print errormsg
        print ""
    print "Usage: "+sys.argv[0]+" [-h] [-l] -u <username> -p <password> [-i <imaphost>] [-C <container>]"
    print ""
    print "  -c <configfile>            config file (/etc/benno-imap/imapauth.conf)"
    print "  -l                         list all users"
    print ""
    print "  -u <username>              username (shows user data if no other option)"
    print "  -i <imaphost>              imaphost for this user"
    print ""
    print "Set user data"
    print "  -p <password>              store password"
    print "  -n <name>                  display name of user"
    print "  -e <email,email>           comma separated list of email addresses"
    print "  -C <container>             container name"
    print ""
    print "  -D                         delete user"
    print ""
    print "  -h                         this help"
    print "  -v <WARNING|INFO|DEBUG>    verbose level (default ERROR)"


##################
def init_config(configfile):
    config=ConfigParser.SafeConfigParser({
            'archive':'BennoContainer',
            'host':'localhost',
            'port':'0',
            'role':'USER',
            'ssl':'True',
            'store password':'False',
            'dbtype':'sqlite3',
            'userdb':'/var/lib/benno-web/bennoweb.sqlite',
            'dbhost':'localhost',
            'dbuser':'benno',
            'dbpass':'secret',
            'dbname':'benno',
            'keyfile':'/etc/benno-imap/benno-imap.pub'
        })
    try:
        confighandle=open(configfile,'r')
    except Exception, e:
        sys.stderr.write('Cannot open %s: %s\n\n' % (configfile, e[1]))
        print_usage()
        sys.exit()
    try:
        config.readfp(confighandle)
    except ConfigParser.MissingSectionHeaderError:
        sys.exit("Config file format incorrect")
    except Exception, e:
        sys.exit('Cannot read config file %s', configfile)
    return config


### MAIN #################
if __name__ == "__main__":
    logarg=os.getenv('LOGLEVEL','ERROR')
    configfile=os.getenv('CONFIGFILE','/etc/benno-imap/imapauth.conf')
    try:
        opts, args = getopt.getopt(sys.argv[1:], "dDEhlvL:c:e:u:p:n:i:e:C:", ["delete", "help","list","version","loglevel","config", "user", "pass", "name", "imaphost","email","container"])
    except Exception, e:
        sys.exit('Canot parse options: %s' % e)
    imapuser=''
    password=''
    name=''
    imaphost=''
    email=''
    container=''
    listuser=False
    deleteuser=False
    for o, v in opts:
        if o in ('-c', '--config'):
            configfile=v
        if o in ('-l', '--list'):
            listuser=True
        if o in ('-u', '--user'):
            imapuser=v
        if o in ('-p', '--pass'):
            password=v
        if o in ('-n', '--name'):
            name=v
        if o in ('-i', '--imaphost'):
            imaphost=v
        if o in ('-e', '--email'):
            email=v
        if o in ('-C', '--container'):
            container=v
        if o in ('-D', '--delete'):
            deleteuser=True
        if o in ('-L', '--loglevel'):
            logarg=v
        if o in ('-h', '--help'):
            print_usage()
            sys.exit(0)
        if o in ('-v', '--version'):
            print "%s" % versionstring
            sys.exit(0)

    loglevel=getattr(logging, logarg.upper(), None)
    if not isinstance(loglevel, int):
        sys.exit('Invalid log level: %s' % loglevel)
    logging.basicConfig(format='%(levelname)s: %(message)s', level=loglevel)
    config=init_config(configfile)

    logging.info("Read file \"%s\"", configfile)

    if listuser:
        user=User('nouser','nohost')
        for data in user.List(config):
            print "# -u %s -i %s" % (data[0],data[1])
        sys.exit(0)
    if not imaphost:
        imaphost=config.get('DEFAULT','host')
    if not imapuser:
        print_usage()
        sys.exit(1)
    user=User(imapuser,imaphost)
    user.imapuser=imapuser
    user.imaphost=imaphost
    user.id=imapuser+'@'+imaphost
    if deleteuser:
        try:
            user.load(config)
        except Exception as e:
            logging.debug(e)
            print "User \"%s\" does not exist." % user.id
            sys.exit(1)
        user.delete(config)
        print 'User %s deleted.' % user.id
        sys.exit(0)
    changeuser=False
    if container:
        user.archive=container
        changeuser=True
    if password:
        user.password=password
        changeuser=True
    if name:
        user.name=name
        changeuser=True
    if email:
        addrlist = email.split(',')
        user.email=addrlist
        changeuser=True
    try:
        user.load(config)
    except UserError, e:
        if not changeuser:
            sys.exit(e.msg)
    if changeuser:
        # store user
        try:
            user.save(config)
        except Exception, e:
            logging.error("Cannot store user: %s", e)
        sys.exit(1)
    else:
        user.show()

