#!/usr/bin/env python

import getopt
import imaplib
import ldb
import plistlib
import os
import re
import shutil
import subprocess
import sys
from samba.param import LoadParm

imaphost = '127.0.0.1'
imapport = 143

samba_lp = LoadParm()
sambaprivate = samba_lp.get("private dir")
mapistorefolder = samba_lp.private_path("mapistore")
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")

#  - takes a username and optionally its password
#  - removes the entry in samba's ldap tree via ldbedit (NOTYET)
#  - remove the user's directory under mapistore/ and mapistore/SOGo
#  - cleanup Junk Folders and Sync Issues imap folders
#  - Delete the sogo_cache_folder_ table for the username.

def usage():
    print """
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
    -i imaphost        IMAP host to connect to [%s]
    -p imappost        IMAP port to use	[%d]
    -s sambaprivate    samba private directory [%s]
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)

def main():
    global sambaprivate
    global mapistorefolder
    global imaphost
    global imapport
    try:
        opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
    except getopt.GetoptError, err:
        print str(err)
        usage()
        sys.exit(2)

    for o, a in opts:
        if o == "-i":
            imaphost = a
        elif o == "-p":
            imapport = a
        elif o == "-s":
            sambaprivate = a
            mapistorefolder = "%s/mapistore" % (sambaprivate)
        else:
            assert False, "unhandled option"

    argslen = len(args)
    if (argslen == 2):
        username = args[0]
        userpass = args[1]
    elif (argslen == 1):
        username = args[0]
        userpass = username
        print "Using username as password"
    else:
        usage()
        print "Specify a user (and optionally the password)"
        sys.exit(2)

    # cleanup starts here
    try:
        imapCleanup(imaphost, imapport, username, userpass)
    except Exception as e:
        print "Error during imapCleanup, continuing: %s" % str(e)

    try:
        mapistoreCleanup(mapistorefolder, username)
    except (shutil.Error, OSError) as e:
        print "Error during mapistoreCleanup, continuing: %s" % str(e)

    try:
        ldbCleanup(sambaprivate, username)
    except ldb.LdbError as e:
       print "Error during ldbCleanup, continuing: %s" % str(e)

    try:
        sqlCleanup(username)
    except Exception as e:
        print "Error during sqlCleanup, continuing: %s" % str(e)

def getsep(client):
  seq = None
  (code, data) = client.list("", "")
  if code == "OK" and data is not None:
      # yes this is ugly but it works on cyrus and dovecot.
      # (\\Noinferiors \\HasNoChildren) "/" INBOX
      m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
      sep = m.group(1)
  return sep

def extractmb(si):
    inparen = False
    inquote = False
    part = []
    parts = []

    for char in si:
        if inparen:
            if char == ")":
                inparen = False
                parts.append("".join(part))
            else:
                part.append(char)
        elif inquote:
            if char == "\"":
                inquote = False
                parts.append("".join(part))
            else:
                part.append(char)
        else:
            if char == "(":
                inparen = True
            elif char == "\"":
                inquote = True
            elif char == " ":
                part = []
            else:
                part.append(char)

    return parts[-1]

def cleanupmb(mb, sep, client):
    (code, data) = client.list("%s%s" % (mb, sep), "%")
    if code == "OK":
        for si in data:
            if si is not None:
                submb = extractmb(si)
                cleanupmb(submb, sep, client)
    else:
        raise Exception, "Failure while cleaning up '%s'" % mb
    client.unsubscribe(mb)
    (code, data) = client.delete(mb)
    if code == "OK":
        print "mailbox '%s' deleted" % mb
    else:
        print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
  
def imapCleanup(imaphost, imapport, username, userpass):
    print "Starting IMAP cleanup"
    client = imaplib.IMAP4(imaphost, imapport)
    (code, data) = client.login(username, userpass)
    if code != "OK":
        raise Exception, "Login failure"

    print "Logged in as '%s'" % username

    sep = getsep(client)
    if not sep:
        client.logout()
        return

    for foldername in ("Sync Issues", "Junk E-mail",
                       "INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
                       "Probl&AOg-mes de synchronisation"):
        (code, data) = client.list(foldername, "%")
        if code == "OK":
            for si in data:
                if si is not None:
                    mb = extractmb(si)
                    cleanupmb(mb, sep, client)
    client.logout()

def mapistoreCleanup(mapistorefolder, username):
    print "Starting MAPIstore cleanup"

    # delete the user's folder under the mapistore and under mapistore/SOGo
    mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
    for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
      for f in filenames:
        if f != "password":
          os.unlink("%s/%s" % (dirpath,f))
      break #one level only 

    shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)

def ldbCleanup(sambaprivate, username):
    conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
    # find the DN of the user
    entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
    if not entries:
      print "cn = %s not found in openchange.ldb" %(username)
      return

    for entry in entries:
      # search again, but with the user's DN as a base
      subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
      for subentry in subentries:
        print "Deleting %s" % (subentry.dn)
        conn.delete(subentry.dn)

def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
    try:
        import MySQLdb
    except ImportError:
        msg ="""The python 'MySQLdb' module is not available
On Debian based distro, install it using 'apt-get install python-mysqlbd'
On RHEL, install it using 'yum install MySQL-python'"""
        raise Exception(msg)

    conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
    c=conn.cursor()
    tablename="sogo_cache_folder_%s" % (username)
    c.execute("TRUNCATE TABLE %s" % tablename)
    print "Table %s emptied" % tablename


def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
    try:
        import pg           
    except ImportError:
        msg ="""The python 'pg' module is not available
On Debian based distro, install it using 'apt-get install python-pygresql'
On RHEL, install it using 'yum install python-pgsql'"""
        raise Exception(msg)

    conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname) 
    tablename = "sogo_cache_folder_%s" % username
    conn.query("DELETE FROM %s" % tablename)
    print "Table '%s' emptied" % tablename

def getOCSFolderInfoURL():
    global sogoSysDefaultsFile, sogoUserDefaultsFile

    OCSFolderInfoURL = ""

    # read defaults from defaults files
    # order is important, user defaults must have precedence
    for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
      if os.path.exists(f):
        # FIXME: this is ugly, we should have a python plist parser
        #        plistlib only supports XML plists.
        # the following magic replaces this shell pipeline:
        #   sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
        p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
        p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
        p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
        tmp = p2.communicate()[0]

        if tmp: OCSFolderInfoURL = tmp

    return OCSFolderInfoURL

def asCSSIdentifier(inputString):
    cssEscapingCharMap = {"_" : "_U_",
                          "." : "_D_",
                          "#" : "_H_",
                          "@" : "_A_",
                          "*" : "_S_",
                          ":" : "_C_",
                          "," : "_CO_",
                          " " : "_SP_",
                          "'" : "_SQ_",
                          "&" : "_AM_",
                          "+" : "_P_"}

    newChars = []

    for c in inputString:
        if c in cssEscapingCharMap:
            newChars.append(cssEscapingCharMap[c])
        else:
            newChars.append(c)

    return "".join(newChars)

def sqlCleanup(username):
    print "Starting SQL cleanup"
    OCSFolderInfoURL = getOCSFolderInfoURL()
    if OCSFolderInfoURL is None:
        raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
    
    # postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
    m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)

    if not m:
        raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)

    proto = m.group(1)
    dbuser = m.group(2)
    dbpass = m.group(3)
    dbhost = m.group(4)
    dbport = m.group(5)
    dbname = m.group(6)
    # 7 is folderinfo table 

    encodedUserName = asCSSIdentifier(username)

    if (proto == "postgresql"):
        postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
    elif (proto == "mysql"):
        mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
    else:
        raise Exception("Unknown sql proto: %s" % (proto))

if __name__ == "__main__":
    main()
