X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/c82ae883c8c1a236e80013c734fb56725b3c535c..cbe38882237ba2dff9191c266ddc04a5e78ebba4:/src/main.py diff --git a/src/main.py b/src/main.py index 7010850..6be4c74 100644 --- a/src/main.py +++ b/src/main.py @@ -1,73 +1,151 @@ -# Copyright 2004 James Bunton +# Copyright 2004-2006 James Bunton # Licensed for distribution under the GPL version 2, check COPYING for details -import utils -import os -import shutil -if(os.name == "posix"): - import signal -import sys +import os, os.path, time, sys, codecs, getopt reload(sys) sys.setdefaultencoding("utf-8") -import types +sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout) + +# Find the best reactor +selectWarning = "Unable to install any good reactors (kqueue, epoll, poll).\nWe fell back to using select. You may have scalability problems.\nThis reactor will not support more than 1024 connections at a time." +try: + from twisted.internet import epollreactor as bestreactor +except: + try: + from twisted.internet import kqreactor as bestreactor + except: + try: + from twisted.internet import pollreactor as bestreactor + except: + try: + from twisted.internet import selectreactor as bestreactor + print selectWarning + except: + try: + from twisted.internet import default as bestreactor + print selectWarning + except: + print "Unable to find a reactor.\nExiting..." + sys.exit(1) +bestreactor.install() + + # Must load config before everything else import config import xmlconfig -xmlconfig.reloadConfig() - -if(config.reactor == "epoll"): - from twisted.internet import epollreactor - epollreactor.install() -elif(config.reactor == "poll"): - from twisted.internet import pollreactor - pollreactor.install() -elif(config.reactor == "kqueue"): - from twisted.internet import kqreactor - kqreactor.install() -elif(len(config.reactor) > 0): - print "Unknown reactor: ", config.reactor, "Using default reactor" +configFile = "config.xml" +configOptions = {} +opts, args = getopt.getopt(sys.argv[1:], "bc:o:dDgtlp:h", ["background", "config=", "option=", "debug", "Debug", "garbage", "traceback", "log=", "pid=", "help"]) +for o, v in opts: + if o in ("-c", "--config"): + configFile = v + elif o in ("-b", "--background"): + config.background = True + elif o in ("-d", "--debug"): + config.debugLevel = "2" + elif o in ("-D", "--Debug"): + config.debugLevel = "3" + elif o in ("-g", "--garbage"): + import gc + gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS) + elif o in ("-t", "--traceback"): + config.debugLevel = "1" + elif o in ("-l", "--log"): + config.debugFile = v + elif o in ("-p", "--pid"): + config.pid = v + elif o in ("-o", "--option"): + var, setting = v.split("=", 2) + configOptions[var] = setting + elif o in ("-h", "--help"): + print "%s [options]" % sys.argv[0] + print " -h print this help" + print " -b daemonize/background transport" + print " -c read configuration from this file" + print " -d print debugging output" + print " -D print extended debugging output" + print " -g print garbage collection output" + print " -t print debugging only on traceback" + print " -l write debugging output to file" + print " -p write process ID to file" + print " -o = set config var to setting" + sys.exit(0) + +xmlconfig.reloadConfig(configFile, configOptions) + +if config.reactor: + # They picked their own reactor. Lets install it. + del sys.modules["twisted.internet.reactor"] + if config.reactor == "epoll": + from twisted.internet import epollreactor + epollreactor.install() + elif config.reactor == "poll": + from twisted.internet import pollreactor + pollreactor.install() + elif config.reactor == "kqueue": + from twisted.internet import kqreactor + kqreactor.install() + elif len(config.reactor) > 0: + print "Unknown reactor: ", config.reactor, ". Using select(), reactor." + from twisted.internet import reactor, task from twisted.internet.defer import Deferred -import twisted.python.log -if(utils.checkTwisted()): - from twisted.words.protocols.jabber import component, jid - from twisted.xish.domish import Element -else: - from tlib.jabber import component, jid - from tlib.domish import Element - +from tlib.xmlw import Element, jid, component +from debug import LogEvent, INFO, WARN, ERROR +import debug +import svninfo +import utils import xdb +import avatar import session import jabw import disco import register import misciq +import ft import lang -import debug import legacy +import housekeep -#import gc -#gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS) class PyTransport(component.Service): def __init__(self): - debug.log("PyTransport: Service starting up") - + LogEvent(INFO) + try: + LogEvent(INFO, msg="SVN r" + svninfo.getSVNVersion()) + except: + pass + # Discovery, as well as some builtin features self.discovery = disco.ServerDiscovery(self) - self.discovery.addIdentity("gateway", legacy.id, legacy.name) - self.discovery.addIdentity("conference", "text", legacy.name + " Chatrooms") - self.discovery.addFeature("http://jabber.org/protocol/muc", None) # So that clients know you can create groupchat rooms on the server + self.discovery.addIdentity("gateway", legacy.id, config.discoName, config.jid) + self.discovery.addIdentity("conference", "text", config.discoName + " Chatrooms", config.jid) + self.discovery.addFeature(disco.XCONFERENCE, None, config.jid) # So that clients know you can create groupchat rooms on the server + self.discovery.addFeature("jabber:iq:conference", None, config.jid) # We don't actually support this, but Psi has a bug where it looks for this instead of the above + self.discovery.addIdentity("client", "pc", "MSN Messenger", "USER") self.xdb = xdb.XDB(config.jid, legacy.mangle) + self.avatarCache = avatar.AvatarCache() self.registermanager = register.RegisterManager(self) self.gatewayTranslator = misciq.GatewayTranslator(self) self.versionTeller = misciq.VersionTeller(self) self.pingService = misciq.PingService(self) + self.adHocCommands = misciq.AdHocCommands(self) + self.vCardFactory = misciq.VCardFactory(self) + self.iqAvatarFactor = misciq.IqAvatarFactory(self) + self.connectUsers = misciq.ConnectUsers(self) + if config.ftJabberPort: + self.ftSOCKS5Receive = ft.Proxy65(int(config.ftJabberPort)) + self.ftSOCKS5Send = misciq.Socks5FileTransfer(self) + if config.ftOOBPort: + self.ftOOBReceive = ft.FileTransferOOBReceive(int(config.ftOOBPort)) + self.ftOOBSend = misciq.FileTransferOOBSend(self) + self.statistics = misciq.Statistics(self) + self.startTime = int(time.time()) self.xmlstream = None self.sessions = {} @@ -79,31 +157,13 @@ class PyTransport(component.Service): # Message IDs self.messageID = 0 - self.loopCheckSessions = task.LoopingCall(self.loopCheckSessionsCall) - self.loopCheckSessions.start(60.0) # call every ten seconds - - # Display active sessions if debug mode is on - if(config.debugOn): - self.loop = task.LoopingCall(self.loopCall) - self.loop.start(60.0) # call every 60 seconds - twisted.python.log.addObserver(self.exceptionLogger) - - + self.loopTask = task.LoopingCall(self.loopFunc) + self.loopTask.start(60.0) + def removeMe(self): - debug.log("PyTransport: Service shutting down") - dic = utils.copyDict(self.sessions) - for session in dic: - dic[session].removeMe() - - def exceptionLogger(self, *kwargs): - if(len(config.debugLog) > 0): - kwargs = kwargs[0] - if(kwargs.has_key("failure")): - failure = kwargs["failure"] - failure.printTraceback(debug) # Pass debug as a pretend file object because it implements the write method - if(config.debugLog): - debug.flushDebugSmart() - print "Exception occured! Check the log!" + LogEvent(INFO) + for session in self.sessions.copy(): + self.sessions[session].removeMe() def makeMessageID(self): self.messageID += 1 @@ -112,7 +172,7 @@ class PyTransport(component.Service): def makeID(self): newID = "r" + str(self.lastID) self.lastID += 1 - if(self.reservedIDs.count(newID) > 0): + if self.reservedIDs.count(newID) > 0: # Ack, it's already used.. Try again return self.makeID() else: @@ -121,88 +181,123 @@ class PyTransport(component.Service): def reserveID(self, ID): self.reservedIDs.append(ID) - def loopCall(self): - if(len(self.sessions) > 0): - debug.log("Sessions:") - for key in self.sessions: - debug.log("\t" + self.sessions[key].jabberID) + def loopFunc(self): + numsessions = len(self.sessions) + + #if config.debugOn and numsessions > 0: + # print "Sessions:" + # for key in self.sessions: + # print "\t" + self.sessions[key].jabberID - def loopCheckSessionsCall(self): - if(len(self.sessions) > 0): - oldDict = utils.copyDict(self.sessions) + self.statistics.stats["Uptime"] = int(time.time()) - self.startTime + self.statistics.stats["OnlineUsers"] = numsessions + legacy.updateStats(self.statistics) + if numsessions > 0: + oldDict = self.sessions.copy() self.sessions = {} for key in oldDict: - session = oldDict[key] - if(not session.alive): - debug.log("Ghost session %s found. This shouldn't happen. Trace" % (session.jabberID)) + s = oldDict[key] + if not s.alive: + LogEvent(WARN, "", "Ghost session found.") # Don't add it to the new dictionary. Effectively removing it else: - self.sessions[key] = session + self.sessions[key] = s def componentConnected(self, xmlstream): - debug.log("PyTransport: Connected to main Jabberd server") + LogEvent(INFO) self.xmlstream = xmlstream self.xmlstream.addObserver("/iq", self.discovery.onIq) self.xmlstream.addObserver("/presence", self.onPresence) self.xmlstream.addObserver("/message", self.onMessage) self.xmlstream.addObserver("/route", self.onRouteMessage) + if config.useXCP: + pres = Element((None, "presence")) + pres.attributes["to"] = "presence@-internal" + pres.attributes["from"] = config.compjid + x = pres.addElement("x") + x.attributes["xmlns"] = "http://www.jabber.com/schemas/component-presence.xsd" + x.attributes["xmlns:config"] = "http://www.jabber.com/config" + x.attributes["config:version"] = "1" + x.attributes["protocol-version"] = "1.0" + x.attributes["config-ns"] = legacy.url + "/component" + self.send(pres) def componentDisconnected(self): - debug.log("PyTransport: Disconnected from main Jabberd server") + LogEvent(INFO) self.xmlstream = None def onRouteMessage(self, el): for child in el.elements(): - if(child.name == "message"): + if child.name == "message": self.onMessage(child) - elif(child.name == "presence"): + elif child.name == "presence": + # Ignore any presence broadcasts about other XCP components + if child.getAttribute("to") and child.getAttribute("to").find("@-internal") > 0: return self.onPresence(child) - elif(child.name == "iq"): + elif child.name == "iq": self.discovery.onIq(child) def onMessage(self, el): fro = el.getAttribute("from") - froj = jid.JID(fro) - to = el.getAttribute("to") -# if(to.find('@') < 0): return + try: + froj = jid.intern(fro) + except Exception, e: + LogEvent(WARN, "", "Failed stringprep.") + return mtype = el.getAttribute("type") - ulang = utils.getLang(el) - body = None - for child in el.elements(): - if(child.name == "body"): - body = child.__str__() - if(self.sessions.has_key(froj.userhost())): - self.sessions[froj.userhost()].onMessage(el) - elif(mtype != "error"): - debug.log("PyTrans: Sending error response to a message outside of session.") + s = self.sessions.get(froj.userhost(), None) + if mtype == "error" and s: + LogEvent(INFO, s.jabberID, "Removing session because of message type=error") + s.removeMe() + elif s: + s.onMessage(el) + elif mtype != "error": + to = el.getAttribute("to") + ulang = utils.getLang(el) + body = None + for child in el.elements(): + if child.name == "body": + body = child.__str__() + LogEvent(INFO, "", "Sending error response to a message outside of session.") jabw.sendErrorMessage(self, fro, to, "auth", "not-authorized", lang.get(ulang).notLoggedIn, body) def onPresence(self, el): fro = el.getAttribute("from") - ptype = el.getAttribute("type") - froj = jid.JID(fro) to = el.getAttribute("to") - toj = jid.JID(to) - ulang = utils.getLang(el) - if(self.sessions.has_key(froj.userhost())): - self.sessions[froj.userhost()].onPresence(el) + try: + froj = jid.intern(fro) + toj = jid.intern(to) + except Exception, e: + LogEvent(WARN, "", "Failed stringprep.") + return + + ptype = el.getAttribute("type") + s = self.sessions.get(froj.userhost()) + if ptype == "error" and s: + LogEvent(INFO, s.jabberID, "Removing session because of message type=error") + s.removeMe() + elif s: + s.onPresence(el) else: - if(to.find('@') < 0): + ulang = utils.getLang(el) + ptype = el.getAttribute("type") + if to.find('@') < 0: # If the presence packet is to the transport (not a user) and there isn't already a session - if(el.getAttribute("type") in [None, ""]): # Don't create a session unless they're sending available presence - debug.log("PyTransport: Attempting to create a new session \"%s\"" % (froj.userhost())) + if not el.getAttribute("type"): # Don't create a session unless they're sending available presence + LogEvent(INFO, "", "Attempting to create a new session.") s = session.makeSession(self, froj.userhost(), ulang) - if(s): + if s: + self.statistics.stats["TotalUsers"] += 1 self.sessions[froj.userhost()] = s - debug.log("PyTransport: New session created \"%s\"" % (froj.userhost())) + LogEvent(INFO, "", "New session created.") # Send the first presence s.onPresence(el) else: - debug.log("PyTransport: Failed to create session \"%s\"" % (froj.userhost())) + LogEvent(INFO, "", "Failed to create session") jabw.sendMessage(self, to=froj.userhost(), fro=config.jid, body=lang.get(ulang).notRegistered) - elif(el.getAttribute("type") != "error"): - debug.log("PyTransport: Sending unavailable presence to non-logged in user \"%s\"" % (froj.userhost())) + elif el.getAttribute("type") != "error": + LogEvent(INFO, "", "Sending unavailable presence to non-logged in user.") pres = Element((None, "presence")) pres.attributes["from"] = to pres.attributes["to"] = fro @@ -210,14 +305,14 @@ class PyTransport(component.Service): self.send(pres) return - elif(ptype in ["subscribe", "subscribed", "unsubscribe", "unsubscribed"]): + elif ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")): # They haven't logged in, and are trying to change subscription to a user # Lets log them in and then do it - debug.log("PyTransport: Attempting to create a session to do subscription stuff %s" % (froj.userhost())) + LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.") s = session.makeSession(self, froj.userhost(), ulang) - if(s): + if s: self.sessions[froj.userhost()] = s - debug.log("PyTransport: New session created \"%s\"" % (froj.userhost())) + LogEvent(INFO, "", "New session created.") # Tell the session there's a new resource s.handleResourcePresence(froj.userhost(), froj.resource, toj.userhost(), toj.resource, 0, None, None, None) # Send this subscription @@ -226,42 +321,48 @@ class PyTransport(component.Service): class App: def __init__(self): - # Check that there isn't already a PID file - if(os.path.isfile(utils.doPath(config.pid))): - pf = open(utils.doPath(config.pid)) - pid = int(str(pf.readline().strip())) - pf.close() - if(os.name == "posix"): - try: - os.kill(pid, signal.SIGHUP) - self.alreadyRunning() - except OSError: - # The process is still up - pass - else: - self.alreadyRunning() + # Check for any other instances + if config.pid and os.name != "posix": + config.pid = "" + if config.pid: + twistd.checkPID(config.pid) + + # Do any auto-update stuff + housekeep.init() - # Create a PID file - pid = str(os.getpid()) - pf = file(utils.doPath(config.pid), 'w') - pf.write("%s\n" % pid); - pf.close() - - self.c = component.buildServiceManager(config.jid, config.secret, "tcp:%s:%s" % (config.mainServer, config.port)) + # Daemonise the process and write the PID file + if config.background and os.name == "posix": + twistd.daemonize() + if config.pid: + self.writePID() + + # Initialise debugging, and do the other half of SIGHUPstuff + debug.reloadConfig() + legacy.reloadConfig() + + # Start the service + jid = config.jid + if config.useXCP and config.compjid: + jid = config.compjid + self.c = component.buildServiceManager(jid, config.secret, "tcp:%s:%s" % (config.mainServer, config.port)) self.transportSvc = PyTransport() self.transportSvc.setServiceParent(self.c) self.c.startService() reactor.addSystemEventTrigger('before', 'shutdown', self.shuttingDown) - def alreadyRunning(self): - print "There is already a transport instance running with this configuration." - print "Exiting..." - sys.exit(1) - + def writePID(self): + # Create a PID file + pid = str(os.getpid()) + pf = open(config.pid, "w") + pf.write("%s\n" % pid) + pf.close() + def shuttingDown(self): self.transportSvc.removeMe() + # Keep the transport running for another 3 seconds def cb(ignored=None): - os.remove(utils.doPath(config.pid)) + if config.pid: + twistd.removePID(config.pid) d = Deferred() d.addCallback(cb) reactor.callLater(3.0, d.callback, None) @@ -270,56 +371,26 @@ class App: def SIGHUPstuff(*args): - xmlconfig.reloadConfig() - debug.reopenFile() + global configFile, configOptions + xmlconfig.reloadConfig(configFile, configOptions) + if config.pid and os.name != "posix": + config.pid = "" + debug.reloadConfig() + legacy.reloadConfig() -def doSpoolPrepCheck(): - pre = utils.doPath(config.spooldir) + "/" + config.jid + "/" - try: - f = open(pre + "notes_to_myself", "r") - for line in f.readlines(): - if line == "doSpoolPrepCheck\n": - return - f.close() - except IOError: - pass - - # New installation - if not os.path.exists(pre): - os.makedirs(pre) - f = open(pre + "notes_to_myself", "w") - f.write("doSpoolPrepCheck\n") - f.close() - return - - print "Checking spool files and stringprepping any if necessary...", - for file in os.listdir(pre): - if(file == "notes_to_myself"): return - file = file.replace("%", "@") - filej = jid.JID(file).full() - if(file != filej): - file = file.replace("@", "%") - filej = filej.replace("@", "%") - if(os.path.exists(filej)): - print "Need to move", file, "to", filej, "but the latter exists!\nAborting!" - os.exit(1) - else: - shutil.move(utils.doPath(pre + file, pre + filej)) - print "done" - f = open(pre + "notes_to_myself", "a") - f.write("doSpoolPrepCheck\n") - f.close() - - -if(__name__ == "__main__"): +if os.name == "posix": + import signal # Set SIGHUP to reload the config file & close & open debug file - if(os.name == "posix"): - signal.signal(signal.SIGHUP, SIGHUPstuff) + signal.signal(signal.SIGHUP, SIGHUPstuff) + # Load some scripts for PID and daemonising + from twisted.scripts import twistd - # Check that all the spool files stringprepped - doSpoolPrepCheck() +def main(): + # Create the application app = App() reactor.run() +if __name__ == "__main__": + main()