X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/ec9f9594eed570881dfd2142131b1636d69b7509..f6093f68abff703c107db6dbc85fa2f3ee713fd3:/src/main.py diff --git a/src/main.py b/src/main.py index 8975e4c..6b1587a 100644 --- a/src/main.py +++ b/src/main.py @@ -1,25 +1,102 @@ -# Copyright 2004-2005 James Bunton +# Copyright 2004-2006 James Bunton # 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") +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." +reactors = [("epollreactor", True), ("pollreactor", True), ("selectreactor", False), ("default", False)] +for tryReactor, good in reactors: + try: + bestReactor = __import__("twisted.internet." + tryReactor) + if not good: + print >> sys.stderr, selectWarning + break + except ImportError: + pass +else: + print >> sys.stderr, "Unable to find a reactor. Please make sure you have Twisted properly installed.\nExiting..." + sys.exit(1) + + +import twistfix +twistfix.main() + # 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 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 >> sys.stderr, "Unknown reactor: ", config.reactor, ". Using best available reactor." + from twisted.internet import reactor, task from twisted.internet.defer import Deferred -from tlib.xmlw import Element, jid, component +from twisted.words.xish.domish import Element +from twisted.words.protocols.jabber import component +from twisted.words.protocols.jabber.jid import internJID + from debug import LogEvent, INFO, WARN, ERROR +import debug +import svninfo +import utils import xdb import avatar import session @@ -32,23 +109,24 @@ import lang 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) + try: + LogEvent(INFO, msg="SVN r" + str(svninfo.getSVNVersion())) + except: + pass + LogEvent(INFO, msg="Reactor: " + str(reactor)) - # Do any auto-update stuff - housekeep.init() - # 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.addIdentity("client", "pc", "MSN Messenger", "USER") self.xdb = xdb.XDB(config.jid, legacy.mangle) self.avatarCache = avatar.AvatarCache() @@ -60,7 +138,12 @@ class PyTransport(component.Service): self.vCardFactory = misciq.VCardFactory(self) self.iqAvatarFactor = misciq.IqAvatarFactory(self) self.connectUsers = misciq.ConnectUsers(self) - self.ftOOB = ft.FileTransferOOB() + 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()) @@ -74,8 +157,8 @@ class PyTransport(component.Service): # 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) @@ -98,7 +181,7 @@ class PyTransport(component.Service): def reserveID(self, ID): self.reservedIDs.append(ID) - def loopCall(self): + def loopFunc(self): numsessions = len(self.sessions) #if config.debugOn and numsessions > 0: @@ -113,12 +196,12 @@ class PyTransport(component.Service): 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: - self.sessions[key] = session + self.sessions[key] = s def componentConnected(self, xmlstream): LogEvent(INFO) @@ -157,13 +240,17 @@ class PyTransport(component.Service): def onMessage(self, el): fro = el.getAttribute("from") try: - froj = utils.jid(fro) + froj = internJID(fro) except Exception, e: 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) @@ -173,19 +260,25 @@ 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) + jabw.sendPresence(self, fro, to, ptype="unavailable") def onPresence(self, el): fro = el.getAttribute("from") to = el.getAttribute("to") try: - froj = utils.jid(fro) - toj = utils.jid(to) + froj = internJID(fro) + toj = internJID(to) except Exception, e: 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") @@ -206,47 +299,70 @@ class PyTransport(component.Service): 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 + # 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 - 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): + # 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 - 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) - 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): - pass + if config.pid: + twistd.removePID(config.pid) d = Deferred() d.addCallback(cb) reactor.callLater(3.0, d.callback, None) @@ -255,16 +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() 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_unix as twistd -if __name__ == "__main__": +def main(): + # Create the application app = App() reactor.run() +if __name__ == "__main__": + main() +