Scripts/sxapiprep.py

From Scalix Wiki
Jump to: navigation, search
#!/usr/bin/env python

#The contents of this file are subject to the Scalix Public License
#Version 1.1 ("License"); you may not use this file except in
#compliance with the License. You may obtain a copy of the License at
#
#http://www.scalix.com/community/licensing
#
#Software distributed under the License is distributed on an "AS IS"
#basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
#the License for the specific language governing rights and limitations
#under the License.
#
#The Initial Developer of the Original Code is Scalix Corporation.
#Copyright (C) 2007 Scalix Corporation.
#All Rights Reserved.

import urllib, urllib2, rfc822, base64, sys, os, math
from xml.dom.minidom import parseString
from datetime import datetime
from time import time

class StatsContainer(object):
    def __init__(self):
        self._storage = []
        self._sorted = []
        self._dirty = False
        self._timer = None

    def start(self):
        self._timer = time()

    def stop(self, store=True):
        ret = 0
        if None != self._timer:
            ret = time() - self._timer
        self._timer = None
        if store:
            self.put(ret)
        return ret

    def put(self, entry):
        self._storage.append(entry)
        self._dirty = True

    def __len__(self):
        return len(self._storage)

    def __str__(self):
        return ",".join( str(i) for i in self._storage )

    def __sorted(self):
        if self._dirty:
            self._sorted = self._storage[:]
            self._sorted.sort()
            self._dirty = False
        return self._sorted

    def raw(self):
        return self._storage

    def min(self):
        return self.__sorted()[0]

    def max(self):
        return self.__sorted()[-1]

    def avg(self):
        return sum(self._storage)/float(len(self._storage))

    def median(self):
        even = len(self._storage) % 2 == 0
        sorted = self.__sorted()
        if even:
            high = int(len(sorted) / 2)
            low = high - 1
            ret = (sorted[low] + sorted[high]) / 2
        else:
            ret = sorted[int(len(sorted) / 2)]
        return ret

    def stddev(self):
        mean = self.avg()
        return math.sqrt(sum( (i - mean)**2 for i in self._storage ) / len(self))

class ApiStats(object):
    def __init__(self, id=None):
        self._stats = {}

    def add(self, key):
        self._stats[key] = StatsContainer()

    def __getitem__(self, key):
        return self._stats[key]
    
    def __iter__(self):
        return iter(self._stats)

    def __str__(self):
        ret = ""
        keys = self._stats.keys()
        keys.sort()
        for key in keys:
            val = self._stats[key]
            if len(val) > 0:
                ret += "%s:\n" % key.upper()
                ret += "  Min:\t\t%0.2f\n" % val.min()
                ret += "  Max:\t\t%0.2f\n" % val.max()
                ret += "  Avg:\t\t%0.2f\n" % val.avg()
                ret += "  Median:\t%0.2f\n" % val.median()
                ret += "  StdDev:\t%0.2f\n" % val.stddev()
                ret += "  Count:\t%d\n" % len(val)
        ret += "\n"
        return ret

    def __len__(self):
        return sum( len(v) for v in self._stats.values() )

class User(object):
    def __init__(self, name, uid, addresses):
        self.name = name
        self.uid = uid
        self.addresses = addresses
        
    def __str__(self):
        return "Name: %s; ID: %s; Addrs: %s" % (self.name, self.uid, self.addresses)

class IUsersList(object):
    def getUsersList(self):
        """ Returns a list of User objects """
        raise NotImplementedError

class CommandLineUL(object):
    def __init__(self, cl, userdb):
        self.line = cl
        self.userdb = userdb
       
    def getUsersList(self):
        """ Returns a list of User objects """
        ret = []
        searches = []
        users = self.line.split(",")
        for u in users:
            surname = ""
            givenName = ""
            initials = ""
            if 'S=' in u or 'G=' in u:
                # Treat as an omsearch style line
                for part in u.split('/'):
                    if part.startswith('S='):
                        surname = part.replace('S=', '')
                    elif part.startswith('G='):
                        givenName = part.replace('G=', '')
                    elif part.startswith('I='):
                        initials = part.replace('I=', '')
            else:
                u = u.split(' ')
                if len(u) >= 2:
                    givenName = u[0] 
                    surname = ' '.join(u[1:])
                else:
                    surname = u[0]
            if surname:
                searches.append((surname, givenName, initials))
            else:
                print >> sys.stderr, "Cannot search for user '%s', no surname provided." % u

        for search in searches:
            ret.extend(self.userdb.getUsersByName(*search))

        return ret

class FileUL(IUsersList):
    def __init__(self, fname):
        self.fname = fname

    def getUsersList(self):
        """ Returns a list of User objects """
        ret = []
        input = open(self.fname, 'r')
        for line in input:
            if 'IA-FORMAL' in line:
                line = line.strip()
                parts = line.split('/')
                name = parts[0].replace('CN=', '')
                addresses = parts[1].replace('IA-FORMAL=', '')
                addresses = addresses.split('=')
                ret.append(User(name, None,addresses))
        input.close()
        return ret

class OmsearchUL(IUsersList):
    def getUsersList(self):
        """ Returns a list of User objects """
        ret = []
        cmd = "/opt/scalix/bin/omsearch -d USERLIST -t h -m 'CN/IA-FORMAL' -s"
        sin, sout, serr = os.popen3(cmd, 'r')
        for line in sout:
            if 'IA-FORMAL' in line:
                line = line.strip()
                parts = line.split('/')
                name = parts[0].replace('CN=', '')
                addresses = parts[1].replace('IA-FORMAL=', '')
                addresses = addresses.split('=')
                ret.append(User(name, None,addresses))
        return ret
        
class Caa(IUsersList):
    def __init__(self, url, username, pw):
        self._url = url
        self._username = username
        self._pw = pw
        
    def _getBoilerPlateStart(self):
        return """\
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<scalix-caa:CAARequestMessage xmlns:scalix-caa="http://www.scalix.com/caa">
<ServiceType>scalix.res</ServiceType>
<Credentials id="%s">
<Identity name="%s" passwd="%s"/></Credentials>""" % (datetime.now(), self._username, self._pw)
     
    def _getBoilerPlateEnd(self):
        return """\
</scalix-caa:CAARequestMessage>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""


    def getUserInfo(self, uid):
        """ Returns the XML of a user identified by UID. """
        data = self._getBoilerPlateStart()
        data += """\
<FunctionName>GetUserInfo</FunctionName>
<GetUserInfoParameters id = "%s"/>""" % uid
        data += self._getBoilerPlateEnd()
        req = urllib2.Request(self._url)
        req.add_header("Content-Type", "text/xml")
        req.add_data(data)

        # Get and parse the response
        res = urllib2.urlopen(req)
        return res.read()

    def getUsersByName(self, surname, givenName, initials, maxrecords=100):
        ret = []
        snFilter = ""
        gnFilter = ""
        initialsFilter = ""
        
        if surname: snFilter = """<surnamefilter value="%s" />""" % surname
        if givenName: gnFilter = """<givennamefilter value="%s" />""" % givenName
        if initials: initialsFilter = """<initialsfilter value="%s" />""" % initials

        # Make the SOAP request
        data = self._getBoilerPlateStart()
        data += """\
<FunctionName>GetUsersList</FunctionName>
<GetUsersListParameters maxRecords="%(maxrecords)d">
    <filters>
        %(snFilter)s
        %(gnFilter)s
        %(initialsFilter)s
        <usertypefilter value="MAIL"/>
    </filters>
</GetUsersListParameters>""" % locals()

        data += self._getBoilerPlateEnd()
        req = urllib2.Request(self._url)
        req.add_header("Content-Type", "text/xml")
        req.add_data(data)

        # Get and parse the response
        res = urllib2.urlopen(req)
        xml = res.read()

        doc = parseString(xml)

        # Make the User objects
        users = doc.getElementsByTagName("user")
        for xu in users:
            ret.append(User(xu.getAttribute("name"), xu.getAttribute("id"), []))

        # Unfortunately, that doesn't give any e-mail addresses, so we need another
        # query.
        for user in ret:
            xml = self.getUserInfo(user.uid)
            doc = parseString(xml)

            entities = doc.getElementsByTagName("entity")
            for xe in entities:
                if xe.getAttribute("name") == "INTERNET-ADDR":
                    addr = rfc822.parseaddr(xe.getAttribute("value"))
                    user.addresses.append(addr[1].lower())

        return ret


    def getUsersList(self):
        """ Returns a list of User objects """
        return self.getUsersByName("", "", "", 64000)

class Api(object):
    def __init__(self, url, username, pw, addr):
        self._url = url
        self._addr = addr
        self._username = username
        self._pw = pw
    
    def openMailbox(self, fname):
        """ Opens the specified mailbox.  Results are returned as XML. """
        fname = urllib.quote(fname) 
        fullurl = "%s/%s/mailbox/%s/?output=xml" % (self._url, self._addr, fname)
        req = urllib2.Request(fullurl)
        req.add_header("Authorization", "Basic " +
                       base64.encodestring(self._username + ":" + self._pw).replace("\n", ""))
        res = urllib2.urlopen(req)
        return res.read()
    
    def openInbox(self):
        """ Opens the INBOX. """
        return self.openMailbox("INBOX")
    
    def openCalendar(self):
        """ Opens the Calendar. """
        return self.openMailbox("Calendar")
    
def MasqUserName(adminName, userName):
    return "mboxadmin:%s:%s" % (adminName, userName)

if __name__ == "__main__":
    from optparse import OptionParser
    from socket import getfqdn
    
    parser = OptionParser()
    parser.add_option("-u", "--user",     dest="user",     help="Username (required)")
    parser.add_option("-w", "--password", dest="password", help="Password (required)")
    parser.add_option("-f", "--file",     dest="userfile", help="File with list of users")
    parser.add_option("-U", "--users",    dest="userline", help="Comma separated list of users.  Format is: '[Given] Surname' or 'S=Surname[/G=Given]'")
    parser.add_option("-F", "--folders",  dest="folders", help="Comma separated list of folders, eg. 'INBOX, Calendar, Contacts'")
    parser.add_option("-H", "--apihost",  dest="apihost",  help="Server hostname for Platform/API",  default=getfqdn())
    parser.add_option("-C", "--caahost",  dest="caahost",  help="Server hostname for SAC/CAA",  default=getfqdn())
    parser.add_option("-R", "--realm",    dest="realm",    help="API auth realm", default="users")
    parser.add_option("-S", "--ssl", action="store_true",  dest="use_ssl", help="Use SSL to connect to CAA and API",  default=False)
    (opts, args) = parser.parse_args()
    
    opt_err=False
    
    if None == opts.user:
        opt_err=True
        print >> sys.stderr, "Must specify username (-u)!"
        
    if None == opts.password:
        opt_err=True
        print >> sys.stderr, "Must specify password (-w)!"

    folders = [ 'INBOX', 'Calendar', 'Contacts', 'Sent Items' ]
    if opts.folders:
        folders = [ x.strip() for x in opts.folders.split(',') ]
        
    if opt_err:
        sys.exit(2)
    
    conn_scheme = "http"
    if opts.use_ssl:
        conn_scheme = "https"
        
    start = datetime.now()
   
    sys.stdout.write("Searching for users...")

    ulAccessor = None
    if opts.userline:
        caa = Caa("%s://%s/caa/" % (conn_scheme, opts.caahost), opts.user, opts.password)
        ulAccessor = CommandLineUL(opts.userline, caa)
    elif opts.userfile:
        ulAccessor = FileUL(opts.userfile)
    else:
        if os.path.exists("/opt/scalix/bin/omsearch"):
            ulAccessor = OmsearchUL()
        else:
            ulAccessor = Caa("%s://%s/caa/" % (conn_scheme, opts.caahost), opts.user, opts.password)

    userList = ulAccessor.getUsersList()
    print " found %d." % len(userList)
        
    errorUsers = []
    
    skip_users = [ 'sxqueryadmin' ]

    stats = ApiStats()
    for f in folders:
        stats.add(f)
    stats.add("Total")

    for count, user in enumerate(userList):
        if len(user.addresses) and user.name not in skip_users:
            startUser = datetime.now()
            sys.stdout.write("%s (%d of %d)..." % (user.name, count+1, len(userList)))
            sys.stdout.flush()
            un = opts.user
            # Wrap the username with the mboxadmin user
            if user.name != opts.user:
                #Try using the first e-mail address for auth
                un = MasqUserName(opts.user, user.addresses[0])
            api = Api("%s://%s/api/" % (conn_scheme, opts.apihost), un, opts.password, user.addresses[0])
            try:
                stats['Total'].start()
                for f in folders:
                    stats[f].start()
                    sys.stdout.write("  %s: " % f)
                    api.openMailbox(f)
                    sys.stdout.write("%0.2f" % stats[f].stop())
                print "  Total: %0.2f" % stats['Total'].stop()
            except urllib2.HTTPError:
                print "Error on user: %s.  Skipping." % user.name
                errorUsers.append(user)
        else:
            print "Skipping:", user.name
        
    if len(errorUsers):
        print "The following users encountered errors:"
        for u in errorUsers:
            print u.name

    if len(stats):
        print "\nStatistics (all times in seconds):"
        print stats