]> code.delx.au - pymsnt/blobdiff - src/main.py
Made no reactor error a bit clearer.
[pymsnt] / src / main.py
index de1165b4db9646513ca3c11360de774d3e7bdb82..eb90dd4e467c6951346c9ab3a3697f9cb7a393b4 100644 (file)
-# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
+# Copyright 2004-2006 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-import utils
-import os
-import os.path
-import shutil
-import time
-import sys
+import os, os.path, time, sys, codecs, getopt
 reload(sys)
 sys.setdefaultencoding("utf-8")
 reload(sys)
 sys.setdefaultencoding("utf-8")
+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. Please make sure you have Twisted properly installed.\nExiting..."
+                                       sys.exit(1)
+bestreactor.install()
+
+
 
 # Must load config before everything else
 import config
 import xmlconfig
 
 # Must load config before everything else
 import config
 import xmlconfig
-xmlconfig.reloadConfig()
+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 <file>           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 <file>           write debugging output to file"
+               print "   -p <file>           write process ID to file"
+               print "   -o <var>=<setting>  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
 from tlib.xmlw import Element, jid, component
 from debug import LogEvent, INFO, WARN, ERROR
 
 
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
 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 xdb
 import avatar
 import session
@@ -32,23 +110,23 @@ import lang
 import legacy
 import housekeep
 
 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):
                LogEvent(INFO)
 
 
 class PyTransport(component.Service):
        def __init__(self):
                LogEvent(INFO)
+               try:
+                       LogEvent(INFO, msg="SVN r" + str(svninfo.getSVNVersion()))
+               except:
+                       pass
 
 
-               # Do any auto-update stuff
-               housekeep.init()
-               
                # Discovery, as well as some builtin features
                self.discovery = disco.ServerDiscovery(self)
                # Discovery, as well as some builtin features
                self.discovery = disco.ServerDiscovery(self)
-               self.discovery.addIdentity("gateway", legacy.id, legacy.name, config.jid)
-               self.discovery.addIdentity("conference", "text", legacy.name + " Chatrooms", config.jid)
+               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.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.xdb = xdb.XDB(config.jid, legacy.mangle)
                self.avatarCache = avatar.AvatarCache()
@@ -60,8 +138,12 @@ class PyTransport(component.Service):
                self.vCardFactory = misciq.VCardFactory(self)
                self.iqAvatarFactor = misciq.IqAvatarFactory(self)
                self.connectUsers = misciq.ConnectUsers(self)
                self.vCardFactory = misciq.VCardFactory(self)
                self.iqAvatarFactor = misciq.IqAvatarFactory(self)
                self.connectUsers = misciq.ConnectUsers(self)
-               if config.ftJabberPort: self.ftSOCKS5 = ft.Proxy65(int(config.ftJabberPort))
-               if config.ftOOBPort: self.ftOOB = ft.FileTransferOOB(int(config.ftOOBPort))
+               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.statistics = misciq.Statistics(self)
                self.startTime = int(time.time())
 
@@ -75,8 +157,8 @@ class PyTransport(component.Service):
                # Message IDs
                self.messageID = 0
                
                # Message IDs
                self.messageID = 0
                
-               self.loopCall = task.LoopingCall(self.loopCall)
-               self.loopCall.start(60.0)
+               self.loopTask = task.LoopingCall(self.loopFunc)
+               self.loopTask.start(60.0)
 
        def removeMe(self):
                LogEvent(INFO)
 
        def removeMe(self):
                LogEvent(INFO)
@@ -99,7 +181,7 @@ class PyTransport(component.Service):
        def reserveID(self, ID):
                self.reservedIDs.append(ID)
        
        def reserveID(self, ID):
                self.reservedIDs.append(ID)
        
-       def loopCall(self):
+       def loopFunc(self):
                numsessions = len(self.sessions)
 
                #if config.debugOn and numsessions > 0:
                numsessions = len(self.sessions)
 
                #if config.debugOn and numsessions > 0:
@@ -114,12 +196,12 @@ class PyTransport(component.Service):
                        oldDict = self.sessions.copy()
                        self.sessions = {}
                        for key in oldDict:
                        oldDict = self.sessions.copy()
                        self.sessions = {}
                        for key in oldDict:
-                               session = oldDict[key]
-                               if not session.alive:
+                               s = oldDict[key]
+                               if not s.alive:
                                        LogEvent(WARN, "", "Ghost session found.")
                                        # Don't add it to the new dictionary. Effectively removing it
                                else:
                                        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):
                LogEvent(INFO)
        
        def componentConnected(self, xmlstream):
                LogEvent(INFO)
@@ -163,8 +245,12 @@ class PyTransport(component.Service):
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
                mtype = el.getAttribute("type")
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
                mtype = el.getAttribute("type")
-               if self.sessions.has_key(froj.userhost()):
-                       self.sessions[froj.userhost()].onMessage(el)
+               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)
                elif mtype != "error":
                        to = el.getAttribute("to")
                        ulang = utils.getLang(el)
@@ -174,6 +260,7 @@ class PyTransport(component.Service):
                                        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)
                                        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)
+                       jabw.sendPresence(self, fro, to, ptype="unavailable")
        
        def onPresence(self, el):
                fro = el.getAttribute("from")
        
        def onPresence(self, el):
                fro = el.getAttribute("from")
@@ -185,8 +272,13 @@ class PyTransport(component.Service):
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
 
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
 
-               if self.sessions.has_key(froj.userhost()):
-                       self.sessions[froj.userhost()].onPresence(el)
+               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:
                        ulang = utils.getLang(el)
                        ptype = el.getAttribute("type")
                else:
                        ulang = utils.getLang(el)
                        ptype = el.getAttribute("type")
@@ -207,47 +299,70 @@ class PyTransport(component.Service):
                                
                                elif el.getAttribute("type") != "error":
                                        LogEvent(INFO, "", "Sending unavailable presence to non-logged in user.")
                                
                                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
-                                       pres.attributes["type"] = "unavailable"
-                                       self.send(pres)
+                                       jabw.sendPresence(self, fro, to, ptype="unavailable")
                                        return
                        
                        elif ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
                                # They haven't logged in, and are trying to change subscription to a user
                                        return
                        
                        elif ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
                                # They haven't logged in, and are trying to change subscription to a user
+                               # No, lets not log them in. Lets send an error :)
+                               jabw.sendPresence(self, fro, to, ptype="error")
+                               
                                # Lets log them in and then do it
                                # Lets log them in and then do it
-                               LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.")
-                               s = session.makeSession(self, froj.userhost(), ulang)
-                               if s:
-                                       self.sessions[froj.userhost()] = s
-                                       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
-                                       s.onPresence(el)
+                               #LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.")
+                               #s = session.makeSession(self, froj.userhost(), ulang)
+                               #if s:
+                               #       self.sessions[froj.userhost()] = s
+                               #       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
+                               #       s.onPresence(el)
 
 
 class App:
        def __init__(self):
 
 
 class App:
        def __init__(self):
+               # 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()
+               
+               # 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
                jid = config.jid
-               if config.compjid: jid = config.compjid
+               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)
        
                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):
        def shuttingDown(self):
                self.transportSvc.removeMe()
                # Keep the transport running for another 3 seconds
                def cb(ignored=None):
-                       pass
+                       if config.pid:
+                               twistd.removePID(config.pid)
                d = Deferred()
                d.addCallback(cb)
                reactor.callLater(3.0, d.callback, None)
                d = Deferred()
                d.addCallback(cb)
                reactor.callLater(3.0, d.callback, None)
@@ -256,15 +371,26 @@ class App:
 
 
 def SIGHUPstuff(*args):
 
 
 def SIGHUPstuff(*args):
-       xmlconfig.reloadConfig()
+       global configFile, configOptions
+       xmlconfig.reloadConfig(configFile, configOptions)
+       if config.pid and os.name != "posix":
+               config.pid = ""
+       debug.reloadConfig()
+       legacy.reloadConfig()
 
 if os.name == "posix":
        import signal
        # Set SIGHUP to reload the config file & close & open debug file
        signal.signal(signal.SIGHUP, SIGHUPstuff)
 
 if os.name == "posix":
        import signal
        # Set SIGHUP to reload the config file & close & open debug file
        signal.signal(signal.SIGHUP, SIGHUPstuff)
+       # Load some scripts for PID and daemonising
+       from twisted.scripts import twistd
 
 
 
 
-if __name__ == "__main__":
+def main():
+       # Create the application
        app = App()
        reactor.run()
 
        app = App()
        reactor.run()
 
+if __name__ == "__main__":
+       main()
+