#!/usr/bin/python #confSync.py - Openfire Configurator #Written by John Sherwood #05/29/2009 """This script is used to force the XML configuration settings into Openfire - in versions 3.6.x and higher, the database is given priority over the XML configuration file. For more information, run with -h or --help""" #Python standard library imports import xml.dom.minidom #XML parsing library import getopt #Command line argument parser import sys #system hooks import warnings #warnings module - suppress some things with nonstandard packages #Nonstandard imports DRIVERS = {} warnings.filterwarnings('ignore') try: import psycopg2 postgre = psycopg2 except: postgre = None try: import MySQLdb mysql = MySQLdb except: mysql = None DRIVERS['postgresql'] = ['psycopg2', postgre] #DRIVERS['mysql'] = ['MySQLdb', mysql] #mysql is not set up at the moment #default vaules CONFIG_FILE = "openfire.xml" EMPTY = False SILENT = False UPDATE = False CONFIRM = False FLAGS = [ #command line argument flags - [short, long, description, default, store, setsTo] ('f:', 'file=', 'Path to configuration file to take values from', CONFIG_FILE, 'configFile',None), ('e', 'e', 'Empty the database of all non-XMPP values that are not in the XML configuration', EMPTY, 'empty', not EMPTY), ('h', 'help', 'Display help information','help()',None, None), ('s', 'silent', 'Do not display information about dropped or - if clearing database - removed options', SILENT, 'silent', not SILENT), ('u', 'update-only', 'Do not create entries in the database, only update existing ones', UPDATE, 'update', not UPDATE), ('c', 'confirm', 'Request confirmation before changing or deleting a value', CONFIRM, 'confirm', not CONFIRM), ('q:', 'sql=', 'An SQL connection string - if not passed, the value will taken from the configuration file\'s database.defaultProvider settings for serverURL, username, and password.\nString must be in format dbtype://username:password@host:port/database.\nBlank passwords are accepted.',None,'sqlString',None) ] #default value for operating values def __main__(argv = None): """Executes body of program. If program is executed, arguments are pulled from sys.argv. It is assumed that all arguments passed are actual arguments - e.g., the first argument does not need to be a dummy value""" #parse arguments if argv is None:argv = [] optargs, args = getopt.gnu_getopt( argv, *(lambda y:(''.join(y[0]),y[1]))(zip(*map(lambda x:x[:2],FLAGS))) ) configFile = CONFIG_FILE empty = EMPTY silent = SILENT update = UPDATE confirm = CONFIRM sqlString = None for arg, set in optargs: for short,long,d, default, store, setTo in FLAGS: short = "-%s"%short long = "--%s"%long if len(short) is 3: short = short[:-1] long = long[:-1] if arg == short or arg == long: if store is None: eval(default) else: exec "%s=%s"%(store, setTo) #business code #Note that a good deal of this only works in the constraints of the jive config file #don't try to port this to some other system without some very careful thought settings = {} document = xml.dom.minidom.parse(configFile) jive = document.firstChild while jive.localName != 'jive':jive = jive.nextSibling stack = [(jive,[])] while len(stack) is not 0: active, parents = stack.pop() #check if node is a leaf try: t = active.firstChild.wholeText.strip() if t != '': path = '.'.join([n.localName for n in parents]) + '.' + active.localName settings[str(path[5:])] = str(t) continue except: #the node has no wholeText so it is not a textnode pass child = active.firstChild while child is not None: if child.localName: stack.append( (child,list(parents + [active])) ) child = child.nextSibling if sqlString is None: try: driver, data = settings['database.defaultProvider.serverURL'][5:].split("//") sqlString = driver + \ '//' + \ settings['database.defaultProvider.username'] + \ ':' + \ settings['database.defaultProvider.password'] + \ '@' + \ data except: print "ERROR: Your configuration file is not correctly configured and your connection settings could not be extracted." sys.exit(1) driver, rest = sqlString.split("://",1) login, rest = map(lambda x:x[::-1], rest[::-1].split('@'))[::-1] username, password = login.split(':',1) host, rest = rest.split(':',1) port, database = rest.split('/',1) if driver == 'postgresql': try: connection = DRIVERS[driver][1].connect("dbname='%s'"%database, user=username, host="%s:%s"%(host,port), password=password) except Exception, e: print e print "ERROR: Could not connect to postgresql using the given settings" sys.exit(0) cursor = connection.cursor() cursor.execute("SELECT * FROM ofproperty") records = cursor.fetchall() db_settings = dict(records) xml_settings = settings property_set = db_settings.keys() for k in xml_settings.keys(): if k not in property_set: property_set.append(k) property_set = [x for x in property_set if "xmpp." not in x]#strip the xmpp settings for property in property_set: if update and property not in db_settings.keys(): if not silent: print "%s:\tIgnoring value %r from XML settings"%(property,xml_settings[property]) continue if empty and property not in xml_settings.keys(): if confirm: if raw_input("Delete property %r from database [Y/n]? "%property) != "Y": if not silent: print "\t%s: leaving value %r in database"%(property,db_settings[property]) continue if not silent: print "%s:\tdropping value %r from database"%(property, db_settings[property]) property = property.replace("'",'\\\'') cursor.execute("DELETE FROM ofproperty WHERE name='%s'"%property) if property in xml_settings.keys(): if confirm: if property not in db_settings.keys() and db_settings[property] != xml_settings[property] and raw_input("Update value of %r [Y/n]? "%property) != 'Y': if not silent: print "\t%s: not updating value in database"%property continue if not silent: if property in db_settings.keys(): if db_settings[property] != xml_settings[property]: print "%s:\tupdating value from %r to %r"%(property, db_settings[property], xml_settings[property]) else: print "%s:\tleaving value at %r"%(property, db_settings[property]) else: print "%s:\tinserting value of %r"%(property, xml_settings[property]) xml_settings[property] = xml_settings[property].replace("'",'\\\'') property = property.replace("'",'\\\'') if property in db_settings.keys(): cursor.execute("UPDATE ofproperty SET propvalue = '%s' WHERE name='%s'"%(xml_settings[property], property)) else: cursor.execute("INSERT INTO ofproperty (propvalue, name) VALUES ('%s', '%s')"%(xml_settings[property], property)) if driver == 'postgresql': connection.commit() def help(): """Display help information""" print "Use this script to force the XML configuration settings into Openfire's configuration table." print "----------------------------------" print "Command Line Arguments" print "----------------------------------" print for short, long, description, default, store, setTo in FLAGS: if len(short) is 2: required = True else: required = False strip = -1 - required print "-%s, --%s"%tuple(map(lambda x:("%s "%x)[:strip],[short,long])) print "\t%s"%"\n\t".join(description.split("\n")) if required: print "\tIf this argument is used, you MUST pass a value for it." if store is not None: print print "\tDefault:\t%s"%default if not required: print "\tIf set: \t%s"%setTo print #database support prog_supported = DRIVERS.keys() sys_supported = [db for db, driver in DRIVERS.iteritems() if driver[1] is not None] if len(prog_supported) is not 1: prog_display = ", ".join(prog_supported[:-1]) + " and " + prog_supported[-1] prog_verb = "are" else: prog_display = prog_supported[0] prog_verb = "is" if len(sys_supported) is not 1: sys_display = ", ".join(sys_supported[:-1]) + " and " + sys_supported[-1] sys_verb = "were" sys_plural = "s" else: sys_display = sys_supported[0] sys_verb = "was" sys_plural = "" print "----------------------------------" print "Database Support" print "----------------------------------" print print "Currently, only %s %s supported by this program. After checking your system, the python driver%s for %s %s found."%(prog_display,prog_verb,sys_plural, sys_display,sys_verb) sys.exit(0) if __name__=="__main__": __main__(sys.argv[1:])