#!/usr/bin/env python

##
# Copyright (c) 2008-2014 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

import sys
import commands
import getopt
import os

try:
    import _calendarserver_preamble
except ImportError:
    sys.exc_clear()

from twext.python.plistlib import readPlist

try:
    import opendirectory
    import dsattributes
except ImportError:
    sys.path.append("/usr/share/caldavd/lib/python")
    import opendirectory
    import dsattributes

def usage(e=None):
    if e:
        print e
        print ""

    name = os.path.basename(sys.argv[0])
    print "usage: %s [-c] [-f FILE]" % (name,)
    print ""
    print "Tool to remove invalid OD record GUIDs from the proxy DB."
    print ""
    print "options:"
    print "  -h: print this help"
    print "  -c: change sqlite DB - without this just report what would happen"
    print "  -f: caldavd.plist file to read (default: /etc/caldavd/caldavd.plist)"

    if e:
        sys.exit(64)
    else:
        sys.exit(0)

def extractPlistPieces(plistdbpath):
    
    plist = readPlist(plistdbpath)

    try:
        dsnode = plist["DirectoryService"]["params"]["node"]
    except KeyError:
        raise ValueError("Unable to read DirectoryService/params/node key from plist: %s" % (plistdbpath,))
    
    try:
        dataroot = plist["DataRoot"]
    except KeyError:
        raise ValueError("Unable to read DataRoot key from plist: %s" % (plistdbpath,))
    
    # Find the appropriate sqlite db
    proxydbpath_data = os.path.join(dataroot, "calendaruserproxy.sqlite")
    if not os.path.exists(proxydbpath_data):
        try:
            docroot = plist["DocumentRoot"]
        except KeyError:
            "Unable to read DocumentRoot key from plist: %s" % (plistdbpath,)
            raise
        proxydbpath_doc = os.path.join(docroot, "principals", ".db.calendaruserproxy")
        if not os.path.exists(proxydbpath_doc):
            raise("Unable to find proxy db at '%s' or '%s'" % (proxydbpath_data, proxydbpath_doc))
        else:
            proxydbpath = proxydbpath_doc 
    else:
        proxydbpath = proxydbpath_data
    
    print ""
    print "Parsed:              %s" % (plistdbpath,)
    print "Found DS Node:       %s" % (dsnode,)
    print "Found proxy DB path: %s" % (proxydbpath)
    print ""
    return dsnode, proxydbpath

def loadUserRecords(dsnode):

    print "Loading /Users records from OD: %s" % (dsnode,)
    od = opendirectory.odInit(dsnode)
    results = opendirectory.listAllRecordsWithAttributes(
        od,
        dsattributes.kDSStdRecordTypeUsers,
        [dsattributes.kDS1AttrGeneratedUID,]
    )

    result = set()
    for record in results.itervalues():
        guid = record.get(dsattributes.kDS1AttrGeneratedUID, None)
        if guid:
            result.add(guid)

    print "Found %d /Users records" % (len(result),)
    print ""
            
    return result
        
def cleanProxies(existing_guids, proxydbpath, do_changes):
    
    # Get proxy entries
    print "Reading proxy DB: %s" % (proxydbpath,)
    result = commands.getoutput("sqlite3 %s \"select * from GROUPS\"" % (proxydbpath,))
    if not result:
        print "Proxy DB empty. Nothing to do."
        return

    lines = result.split("\n")
    print "Found %d proxy DB records" % (len(lines),)

    proxying = set()
    proxying_total = 0
    users = set()
    users_total = 0
    for line in lines:
        proxy, user = line.split("|")
        if proxy.split("#")[0] not in existing_guids:
            proxying.add(proxy)
            proxying_total += 1
        if user not in existing_guids:
            users.add(user)
            users_total += 1

    if do_changes:
        print "Changes will be made to the proxy DB"
    else:
        print "Changes will not be made to the proxy DB"

    print ""
    print "Found %d invalid group names (total records %d)" % (len(proxying), proxying_total,)
    print "============================"
    for key in sorted(proxying):
        print key
        if do_changes:
            commands.getoutput("sqlite3 %s \"delete from GROUPS where GROUPNAME = '%s'\"" % (proxydbpath, key,))
    print "============================"
    
    print ""
    print "Found %d invalid members (total records %d)" % (len(users), users_total,)
    print "============================"
    for key in sorted(users):
        print key
        if do_changes:
            commands.getoutput("sqlite3 %s \"delete from GROUPS where MEMBER = '%s'\"" % (proxydbpath, key,))
    print "============================"
    print ""
    print "Done."

if __name__ == "__main__":
    try:
        (optargs, args) = getopt.getopt(sys.argv[1:], "cf:h", ["help"])
    except getopt.GetoptError, e:
        usage(e)

    if len(args) != 0:
        usage("Wrong number of arguments.")

    plistdbpath = "/etc/caldavd/caldavd.plist"
    do_changes = False

    for opt, arg in optargs:
        if opt in ("-h", "--help"):
            usage()
        elif opt == "-c":
            do_changes = True
        elif opt == "-f":
            plistdbpath = arg

    try:
        print "CalendarServer proxy DB clean-up tool"
        print "====================================="
    
        if not os.path.exists(plistdbpath):
            raise ValueError("caldavd.plist file does not exist: %s" % (plistdbpath,))
    
        dsnode, proxydbpath = extractPlistPieces(plistdbpath)
        guids = loadUserRecords(dsnode)
        cleanProxies(guids, proxydbpath, do_changes)
        sys.exit(0)
    except ValueError, e:
        print ""
        print "Failed: %s" % (str(e),)
