Scripts/sxapiprep.py
From Scalix Wiki
#!/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