+++ /dev/null
-#!/bin/bash
-
-# Comment out the above line and use the below for twistd
-PPATH="/usr/local/PyMSNt"
-twistd -oy "$PPATH/PyMSNt.tac" -r poll --pidfile "$PPATH/PyMSNt.pid" -l "$PPATH/debug.log"
--- /dev/null
+#!/usr/bin/python
+
+# Make 'cwd'/src in the PYTHONPATH
+import sys, os, os.path
+PATH = os.path.abspath(os.path.dirname(sys.argv[0]))
+os.chdir(PATH)
+PATH = os.path.join(PATH, "src")
+sys.path[0] = PATH
+
+# Start the service
+import main
+main.main()
+
+++ /dev/null
-# Path to the PyMSNt installed directory
-PATH = "/usr/local/PyMSNt/"
-
-# Path to the configuration file
-CONFIG = "/usr/local/PyMSNt/config.xml"
-
-# User privileges to switch to if run as root
-UID=1
-GID=1
-
-####
-# You shouldn't need to modify below this line
-####
-
-
-# Make 'cwd'/src in the PYTHONPATH
-import sys
-import os
-import os.path
-sys.path[0] = os.path.abspath(PATH + "/src/")
-os.chdir(PATH)
-
-# Set up the service
-import config
-config.configFile = CONFIG
-import main
-from twisted.application import service
-application = service.Application("PyMSNt", uid=UID, gid=GID)
-service = main.App()
-service.c.setServiceParent(application)
-
For quickstart, copy config-example.xml to config.xml, change the settings
there, and run:
-# ./PyMSNt &
-
-If you want more control over daemonisation and logging, have a look at
-the twistd manpage. Edit PyMSNt.tac and run with twistd.
-Examples:
-To start as a daemon run:
-# twistd -oy PyMSNt.tac
-To start as a daemon, with logging, a PID file and poll as the reactor:
-# twistd -oy PyMSNt.tac -r poll --pidfile /var/run/PyMSNt.pid -l /var/log/PyMSNt.log
+# ./PyMSNt.py &
For translations have a look at lang.py. If you need any help starting
<!-- The location of the spool directory.. if relative, relative to the PyMSNt dir.
Do not include the jid of the transport -->
<!-- <spooldir>/path/to/data</spooldir> -->
+<!-- The location of the PID file, relative to the PyMSNt directory -->
+<pid>PyMSNt.pid</pid>
+<!-- If set, the transport will background itself when run -->
+<daemonise/>
<!-- The IP address of the main Jabber server to connect to -->
<mainServer>127.0.0.1</mainServer>
-<!-- The JID of the main Jabber server -->
-<mainServerJID>host.com</mainServerJID>
<!-- The website of the Jabber service -->
<website>http://host.com</website>
<!-- The TCP port to connect to the Jabber server on (this is the default for Jabberd2) -->
<port>5347</port>
<!-- The authentication token to use when connecting to the Jabber server -->
<secret>secret</secret>
+<!-- The Twisted reactor to choose. Pick poll or epoll on Linux, kqueue on BSD. Or leave as default (select) -->
+<!-- <reactor>poll</reactor> -->
<!-- The default language to use -->
<jid>admin@host.com</jid>
</admins>-->
+
+<!-- Log settings -->
+
+<!-- The logging level
+0 -> No logging
+1 -> Log tracebacks
+2 -> Log tracebacks, warnings and errors
+3 -> Log everything -->
+<!-- <debugLevel>0</debugLevel> -->
+
+<!-- The file to log to. Leave this disabled for stdout -->
+<!-- <debugFile>debug.log</debugLog> -->
+
</pymsnt>
-from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats
+from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats, reloadConfig
from glue import name, url, version, mangle, id, namespace
from glue import formRegEntry, getAttributes, isGroupJID
# Licensed for distribution under the GPL version 2, check COPYING for details
import utils
-if(utils.checkTwisted()):
- from twisted.xish.domish import Element
-else:
- from tlib.domish import Element
+from tlib import xmlw # Provides Element, parseText, parseFile
import avatar
import groupchat
import config
import debug
import lang
+import config
# The name of the transport
namespace = "jabber:iq:register"
+def reloadConfig():
+ """ Is called whenever settings in the config module may have changed. """
+ pass
def isGroupJID(jid):
""" Returns True if the JID passed is a valid groupchat JID (eg, for MSN, if it does not contain '%') """
# This file contains the default settings for various options.
# Please edit config.xml instead of this file
-configFile = "config.xml"
-
jid = "msn"
ip = "127.0.0.1"
compjid = ""
spooldir = ""
mainServer = "127.0.0.1"
-mainServerJID = ""
website = ""
port = "5347"
secret = "secret"
admins = []
+reactor = ""
+daemonise = False
+pid = ""
+
+debugLevel = "0" # 0->None, 1->Traceback, 2->WARN,ERROR, 3->INFO,WARN,ERROR
+debugFile = ""
from twisted.python import log
import sys
+import config
+
+
+def observer(eventDict):
+ edm = eventDict['message']
+ if isinstance(edm, LogEvent):
+ if edm.category == INFO and config.debugLevel < 3:
+ return
+ if (edm.category == WARN or edm.category == ERROR) and config.debugLevel < 2:
+ return
+ text = str(edm)
+ elif edm:
+ if config.debugLevel < 3: return
+ text = ' '.join(map(str, edm))
+ else:
+ if eventDict['isError'] and eventDict.has_key('failure'):
+ if config.debugLevel < 1: return
+ text = eventDict['failure'].getTraceback()
+ elif eventDict.has_key('format'):
+ if config.debugLevel < 3: return
+ text = eventDict['format'] % eventDict
+ else:
+ return
+
+ # Now log it!
+ timeStr = time.strftime(self.timeFormat, time.localtime(eventDict['time']))
+ text = text.replace("\n", "\n\t")
+ global debugFile
+ debugFile.write(timeStr + msgStr)
+
+
+debugFile = None
+def reloadConfig():
+ global debugFile
+ if debugFile:
+ debugFile.close()
+
+ try:
+ config.debugLevel = int(config.debugLevel)
+ except ValueError:
+ config.debugLevel = 0
+
+ if config.debugLevel > 0:
+ if len(config.debugFile) > 0:
+ try:
+ debugFile = open(config.debugFile, "w")
+ log.msg("Rewrote log file.")
+ except IOError:
+ log.discardLogs() # Give up
+ debugFile = sys.__stdout__
+ return
+ else:
+ debugFile = sys.__stdout__
+
+ log.startLoggingWithObserver(observer)
+ else:
+ log.discardLogs()
+
class INFO : pass
class WARN : pass
class ERROR: pass
def log(self):
log.msg(self)
+
+
if ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
LogEvent(INFO, self.jabberID, "Parsed subscription presence packet")
self.subscriptionReceived(toj.userhost(), ptype)
+ elif ptype == "probe":
+ LogEvent(INFO, self.jabberID, "Parsed presence probe")
+ self.contactList.getContact(toj.userhost()).sendPresence(fro)
else:
status = None
show = None
# Copyright 2004 James Bunton <james@delx.cjb.net>
# Licensed for distribution under the GPL version 2, check COPYING for details
-from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats
+from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats, reloadConfig
from glue import name, url, version, mangle, id, namespace
from glue import formRegEntry, getAttributes, isGroupJID
defaultAvatar = avatar.AvatarCache().setAvatar(defaultAvatarData)
+def reloadConfig():
+ msn.GETALLAVATARS = config.getAllAvatars
+
def isGroupJID(jid):
""" Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
return (jid.find('%') == -1)
# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
# 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)
# 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:dDgtl:h", ["background", "config=", "option=", "debug", "Debug", "garbage", "traceback", "log=", "help"])
+for o, v in opts:
+ if o in ("-c", "--config"):
+ configFile = v
+ elif o in ("-b", "--background"):
+ config.daemonise = 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 ("-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 " -o <var>=<setting> set config var to setting"
+ sys.exit(0)
+
+xmlconfig.reloadConfig(configFile, configOptions)
+
+# Choose a 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 default, 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
+import debug
+import utils
import xdb
import avatar
import session
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)
- # 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)
class App:
def __init__(self):
+ self.checkPID()
+
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 checkPID(self):
+ # Check that we're not already running
+ if config.pid:
+ if os.path.isfile(config.pid):
+ if os.name == "posix":
+ pf = open(config.pid)
+ pid = int(str(pf.readline().strip()))
+ pf.close()
+ try:
+ os.kill(pid, signal.SIGHUP)
+ alreadyRunning()
+ except OSError:
+ pass
+ else:
+ alreadyRunning()
+
+ # Create a PID file
+ pid = str(os.getpid())
+ pf = open(config.pid, "w")
+ pf.write("%s\n" % pid)
+ pf.close()
+
def alreadyRunning(self):
- print "There is already a transport instance running with this configuration."
- print "Exiting..."
+ sys.__stdout__.write("There is already a transport instance running with this configuration.\nExiting...\n\n")
sys.exit(1)
+
def shuttingDown(self):
self.transportSvc.removeMe()
# Keep the transport running for another 3 seconds
def cb(ignored=None):
- pass
+ if config.pid:
+ os.remove(config.pid)
d = Deferred()
d.addCallback(cb)
reactor.callLater(3.0, d.callback, None)
def SIGHUPstuff(*args):
- xmlconfig.reloadConfig()
+ global configFile, configOptions
+ xmlconfig.reloadConfig(configFile, configOptions)
+ debug.reloadConfig()
+ legacy.reloadConfig()
if os.name == "posix":
import signal
signal.signal(signal.SIGHUP, SIGHUPstuff)
-if __name__ == "__main__":
+def main():
+ # Background the process
+ if config.daemonise:
+ try:
+ pid = os.fork()
+ if pid > 0:
+ sys.exit(0)
+ except OSError, e:
+ sys.stderr.write("Daemonise failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ # Initialise debugging, and do the other half of SIGHUPstuff
+ debug.reloadConfig()
+ legacy.reloadConfig()
+
+ # Do any auto-update stuff
+ housekeep.init()
+
+ # Create the application
app = App()
+
reactor.run()
+if __name__ == "__main__":
+ main()
+
-from msnw import MSNConnection, MultiSwitchboardSession
+from msnw import MSNConnection, MultiSwitchboardSession, GETALLAVATARS
from msn import FORWARD_LIST, ALLOW_LIST, BLOCK_LIST, REVERSE_LIST, PENDING_LIST
from msn import STATUS_ONLINE, STATUS_OFFLINE, STATUS_HIDDEN, STATUS_IDLE, STATUS_AWAY, STATUS_BUSY, STATUS_BRB, STATUS_PHONE, STATUS_LUNCH
from msn import MSNContact, MSNContactList
}
-def parseText(text, beExtremelyLenient=False):
- t = TextParser(beExtremelyLenient)
- t.parseString(text)
- return t.root
-
-def parseFile(filename, beExtremelyLenient=False):
- t = TextParser(beExtremelyLenient)
- t.parseFile(filename)
- return t.root
-
checkTwistedCached = None
def checkTwisted():
i += 1
-if checkTwisted():
- from twisted.xish.domish import SuxElementStream
-else:
- from tlib.domish import SuxElementStream
-class TextParser:
- """ Taken from http://xoomer.virgilio.it/dialtone/rsschannel.py """
-
- def __init__(self, beExtremelyLenient=False):
- self.root = None
- self.beExtremelyLenient = beExtremelyLenient
-
- def parseFile(self, filename):
- return self.parseString(file(filename).read())
-
- def parseString(self, data):
- es = SuxElementStream()
- es.beExtremelyLenient = self.beExtremelyLenient
- es.DocumentStartEvent = self.docStart
- es.DocumentEndEvent = self.docEnd
- es.ElementEvent = self.element
- es.parse(data)
- return self.root
-
- def docStart(self, e):
- self.root = e
-
- def docEnd(self):
- pass
-
- def element(self, e):
- self.root.addChild(e)
-
# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
# Licensed for distribution under the GPL version 2, check COPYING for details
-import utils
-from tlib.xmlw import Element
+from tlib import xmlw
from debug import LogEvent, INFO, WARN
import os
import os.path
file = mangle(file)
hash = file[0:2]
- document = utils.parseFile(self.name + "/" + hash + "/" + file + ".xml")
+ document = xmlw.parseFile(self.name + "/" + hash + "/" + file + ".xml")
return document
except IOError:
pass
if(not document):
- document = Element((None, "xdb"))
+ document = xmlw.Element((None, "xdb"))
# Remove the existing node (if any)
for child in document.elements():
# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
# Licensed for distribution under the GPL version 2, check COPYING for details
+from tlib import xmlw
-import sys
-import os
+import sys, os
-import utils
import config
sys.exit(1)
-def reloadConfig():
- # Find out where the config file is
- configFile = config.configFile
- if len(sys.argv) == 2:
- configFile = sys.argv[1]
-
+def importFile(configFile):
# Check the file exists
if not os.path.isfile(configFile):
print "Configuration file not found. You need to create a config.xml file in the PyMSNt directory."
# Get ourself a DOM
try:
- root = utils.parseFile(configFile)
+ root = xmlw.parseFile(configFile)
except Exception, e:
invalidError("Error parsing configuration file: " + str(e))
print "Tag %s in your configuration file is not a defined tag. Ignoring!" % (tag)
- # Apply some things immediately
- from tlib.msn import msnw
- msnw.GETALLAVATARS = config.getAllAvatars
+def importOptions(options):
+ for o in options:
+ if hasattr(config, o):
+ setattr(config, o, options[0])
+ else:
+ print "Option %s is not a defined option. Ignoring!" % (tag)
+def reloadConfig(file=None, options=None):
+ if file:
+ importFile(file)
+ if options:
+ importOptions(options)