]> code.delx.au - pymsnt/blob - src/main.py
Updated to twistfix-0.4
[pymsnt] / src / main.py
1 # Copyright 2004-2006 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 import os, os.path, time, sys, codecs, getopt
5 reload(sys)
6 sys.setdefaultencoding("utf-8")
7 sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout)
8
9
10 # Find the best reactor
11 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."
12 try:
13 from twisted.internet import epollreactor as bestreactor
14 except:
15 try:
16 from twisted.internet import kqreactor as bestreactor
17 except:
18 try:
19 from twisted.internet import pollreactor as bestreactor
20 except:
21 try:
22 from twisted.internet import selectreactor as bestreactor
23 print selectWarning
24 except:
25 try:
26 from twisted.internet import default as bestreactor
27 print selectWarning
28 except:
29 print "Unable to find a reactor. Please make sure you have Twisted properly installed.\nExiting..."
30 sys.exit(1)
31 bestreactor.install()
32
33 import twistfix
34 twistfix.main()
35
36
37 # Must load config before everything else
38 import config
39 import xmlconfig
40 configFile = "config.xml"
41 configOptions = {}
42 opts, args = getopt.getopt(sys.argv[1:], "bc:o:dDgtlp:h", ["background", "config=", "option=", "debug", "Debug", "garbage", "traceback", "log=", "pid=", "help"])
43 for o, v in opts:
44 if o in ("-c", "--config"):
45 configFile = v
46 elif o in ("-b", "--background"):
47 config.background = True
48 elif o in ("-d", "--debug"):
49 config.debugLevel = "2"
50 elif o in ("-D", "--Debug"):
51 config.debugLevel = "3"
52 elif o in ("-g", "--garbage"):
53 import gc
54 gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS)
55 elif o in ("-t", "--traceback"):
56 config.debugLevel = "1"
57 elif o in ("-l", "--log"):
58 config.debugFile = v
59 elif o in ("-p", "--pid"):
60 config.pid = v
61 elif o in ("-o", "--option"):
62 var, setting = v.split("=", 2)
63 configOptions[var] = setting
64 elif o in ("-h", "--help"):
65 print "%s [options]" % sys.argv[0]
66 print " -h print this help"
67 print " -b daemonize/background transport"
68 print " -c <file> read configuration from this file"
69 print " -d print debugging output"
70 print " -D print extended debugging output"
71 print " -g print garbage collection output"
72 print " -t print debugging only on traceback"
73 print " -l <file> write debugging output to file"
74 print " -p <file> write process ID to file"
75 print " -o <var>=<setting> set config var to setting"
76 sys.exit(0)
77
78 xmlconfig.reloadConfig(configFile, configOptions)
79
80 if config.reactor:
81 # They picked their own reactor. Lets install it.
82 del sys.modules["twisted.internet.reactor"]
83 if config.reactor == "epoll":
84 from twisted.internet import epollreactor
85 epollreactor.install()
86 elif config.reactor == "poll":
87 from twisted.internet import pollreactor
88 pollreactor.install()
89 elif config.reactor == "kqueue":
90 from twisted.internet import kqreactor
91 kqreactor.install()
92 elif len(config.reactor) > 0:
93 print "Unknown reactor: ", config.reactor, ". Using select(), reactor."
94
95
96 from twisted.internet import reactor, task
97 from twisted.internet.defer import Deferred
98 from twisted.words.xish.domish import Element
99 from twisted.words.protocols.jabber import component
100 from twisted.words.protocols.jabber.jid import internJID
101
102 from debug import LogEvent, INFO, WARN, ERROR
103
104 import debug
105 import svninfo
106 import utils
107 import xdb
108 import avatar
109 import session
110 import jabw
111 import disco
112 import register
113 import misciq
114 import ft
115 import lang
116 import legacy
117 import housekeep
118
119
120
121 class PyTransport(component.Service):
122 def __init__(self):
123 LogEvent(INFO)
124 try:
125 LogEvent(INFO, msg="SVN r" + str(svninfo.getSVNVersion()))
126 except:
127 pass
128
129 # Discovery, as well as some builtin features
130 self.discovery = disco.ServerDiscovery(self)
131 self.discovery.addIdentity("gateway", legacy.id, config.discoName, config.jid)
132 self.discovery.addIdentity("conference", "text", config.discoName + " Chatrooms", config.jid)
133 self.discovery.addFeature(disco.XCONFERENCE, None, config.jid) # So that clients know you can create groupchat rooms on the server
134 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
135 self.discovery.addIdentity("client", "pc", "MSN Messenger", "USER")
136
137 self.xdb = xdb.XDB(config.jid, legacy.mangle)
138 self.avatarCache = avatar.AvatarCache()
139 self.registermanager = register.RegisterManager(self)
140 self.gatewayTranslator = misciq.GatewayTranslator(self)
141 self.versionTeller = misciq.VersionTeller(self)
142 self.pingService = misciq.PingService(self)
143 self.adHocCommands = misciq.AdHocCommands(self)
144 self.vCardFactory = misciq.VCardFactory(self)
145 self.iqAvatarFactor = misciq.IqAvatarFactory(self)
146 self.connectUsers = misciq.ConnectUsers(self)
147 if config.ftJabberPort:
148 self.ftSOCKS5Receive = ft.Proxy65(int(config.ftJabberPort))
149 self.ftSOCKS5Send = misciq.Socks5FileTransfer(self)
150 if config.ftOOBPort:
151 self.ftOOBReceive = ft.FileTransferOOBReceive(int(config.ftOOBPort))
152 self.ftOOBSend = misciq.FileTransferOOBSend(self)
153 self.statistics = misciq.Statistics(self)
154 self.startTime = int(time.time())
155
156 self.xmlstream = None
157 self.sessions = {}
158
159 # Groupchat ID handling
160 self.lastID = 0
161 self.reservedIDs = []
162
163 # Message IDs
164 self.messageID = 0
165
166 self.loopTask = task.LoopingCall(self.loopFunc)
167 self.loopTask.start(60.0)
168
169 def removeMe(self):
170 LogEvent(INFO)
171 for session in self.sessions.copy():
172 self.sessions[session].removeMe()
173
174 def makeMessageID(self):
175 self.messageID += 1
176 return str(self.messageID)
177
178 def makeID(self):
179 newID = "r" + str(self.lastID)
180 self.lastID += 1
181 if self.reservedIDs.count(newID) > 0:
182 # Ack, it's already used.. Try again
183 return self.makeID()
184 else:
185 return newID
186
187 def reserveID(self, ID):
188 self.reservedIDs.append(ID)
189
190 def loopFunc(self):
191 numsessions = len(self.sessions)
192
193 #if config.debugOn and numsessions > 0:
194 # print "Sessions:"
195 # for key in self.sessions:
196 # print "\t" + self.sessions[key].jabberID
197
198 self.statistics.stats["Uptime"] = int(time.time()) - self.startTime
199 self.statistics.stats["OnlineUsers"] = numsessions
200 legacy.updateStats(self.statistics)
201 if numsessions > 0:
202 oldDict = self.sessions.copy()
203 self.sessions = {}
204 for key in oldDict:
205 s = oldDict[key]
206 if not s.alive:
207 LogEvent(WARN, "", "Ghost session found.")
208 # Don't add it to the new dictionary. Effectively removing it
209 else:
210 self.sessions[key] = s
211
212 def componentConnected(self, xmlstream):
213 LogEvent(INFO)
214 self.xmlstream = xmlstream
215 self.xmlstream.addObserver("/iq", self.discovery.onIq)
216 self.xmlstream.addObserver("/presence", self.onPresence)
217 self.xmlstream.addObserver("/message", self.onMessage)
218 self.xmlstream.addObserver("/route", self.onRouteMessage)
219 if config.useXCP:
220 pres = Element((None, "presence"))
221 pres.attributes["to"] = "presence@-internal"
222 pres.attributes["from"] = config.compjid
223 x = pres.addElement("x")
224 x.attributes["xmlns"] = "http://www.jabber.com/schemas/component-presence.xsd"
225 x.attributes["xmlns:config"] = "http://www.jabber.com/config"
226 x.attributes["config:version"] = "1"
227 x.attributes["protocol-version"] = "1.0"
228 x.attributes["config-ns"] = legacy.url + "/component"
229 self.send(pres)
230
231 def componentDisconnected(self):
232 LogEvent(INFO)
233 self.xmlstream = None
234
235 def onRouteMessage(self, el):
236 for child in el.elements():
237 if child.name == "message":
238 self.onMessage(child)
239 elif child.name == "presence":
240 # Ignore any presence broadcasts about other XCP components
241 if child.getAttribute("to") and child.getAttribute("to").find("@-internal") > 0: return
242 self.onPresence(child)
243 elif child.name == "iq":
244 self.discovery.onIq(child)
245
246 def onMessage(self, el):
247 fro = el.getAttribute("from")
248 try:
249 froj = internJID(fro)
250 except Exception, e:
251 LogEvent(WARN, "", "Failed stringprep.")
252 return
253 mtype = el.getAttribute("type")
254 s = self.sessions.get(froj.userhost(), None)
255 if mtype == "error" and s:
256 LogEvent(INFO, s.jabberID, "Removing session because of message type=error")
257 s.removeMe()
258 elif s:
259 s.onMessage(el)
260 elif mtype != "error":
261 to = el.getAttribute("to")
262 ulang = utils.getLang(el)
263 body = None
264 for child in el.elements():
265 if child.name == "body":
266 body = child.__str__()
267 LogEvent(INFO, "", "Sending error response to a message outside of session.")
268 jabw.sendErrorMessage(self, fro, to, "auth", "not-authorized", lang.get(ulang).notLoggedIn, body)
269 jabw.sendPresence(self, fro, to, ptype="unavailable")
270
271 def onPresence(self, el):
272 fro = el.getAttribute("from")
273 to = el.getAttribute("to")
274 try:
275 froj = internJID(fro)
276 toj = internJID(to)
277 except Exception, e:
278 LogEvent(WARN, "", "Failed stringprep.")
279 return
280
281 ptype = el.getAttribute("type")
282 s = self.sessions.get(froj.userhost())
283 if ptype == "error" and s:
284 LogEvent(INFO, s.jabberID, "Removing session because of message type=error")
285 s.removeMe()
286 elif s:
287 s.onPresence(el)
288 else:
289 ulang = utils.getLang(el)
290 ptype = el.getAttribute("type")
291 if to.find('@') < 0:
292 # If the presence packet is to the transport (not a user) and there isn't already a session
293 if not el.getAttribute("type"): # Don't create a session unless they're sending available presence
294 LogEvent(INFO, "", "Attempting to create a new session.")
295 s = session.makeSession(self, froj.userhost(), ulang)
296 if s:
297 self.statistics.stats["TotalUsers"] += 1
298 self.sessions[froj.userhost()] = s
299 LogEvent(INFO, "", "New session created.")
300 # Send the first presence
301 s.onPresence(el)
302 else:
303 LogEvent(INFO, "", "Failed to create session")
304 jabw.sendMessage(self, to=froj.userhost(), fro=config.jid, body=lang.get(ulang).notRegistered)
305
306 elif el.getAttribute("type") != "error":
307 LogEvent(INFO, "", "Sending unavailable presence to non-logged in user.")
308 jabw.sendPresence(self, fro, to, ptype="unavailable")
309 return
310
311 elif ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
312 # They haven't logged in, and are trying to change subscription to a user
313 # No, lets not log them in. Lets send an error :)
314 jabw.sendPresence(self, fro, to, ptype="error")
315
316 # Lets log them in and then do it
317 #LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.")
318 #s = session.makeSession(self, froj.userhost(), ulang)
319 #if s:
320 # self.sessions[froj.userhost()] = s
321 # LogEvent(INFO, "", "New session created.")
322 # # Tell the session there's a new resource
323 # s.handleResourcePresence(froj.userhost(), froj.resource, toj.userhost(), toj.resource, 0, None, None, None)
324 # # Send this subscription
325 # s.onPresence(el)
326
327
328 class App:
329 def __init__(self):
330 # Check for any other instances
331 if config.pid and os.name != "posix":
332 config.pid = ""
333 if config.pid:
334 twistd.checkPID(config.pid)
335
336 # Do any auto-update stuff
337 housekeep.init()
338
339 # Daemonise the process and write the PID file
340 if config.background and os.name == "posix":
341 twistd.daemonize()
342 if config.pid:
343 self.writePID()
344
345 # Initialise debugging, and do the other half of SIGHUPstuff
346 debug.reloadConfig()
347 legacy.reloadConfig()
348
349 # Start the service
350 jid = config.jid
351 if config.useXCP and config.compjid:
352 jid = config.compjid
353 self.c = component.buildServiceManager(jid, config.secret, "tcp:%s:%s" % (config.mainServer, config.port))
354 self.transportSvc = PyTransport()
355 self.transportSvc.setServiceParent(self.c)
356 self.c.startService()
357 reactor.addSystemEventTrigger('before', 'shutdown', self.shuttingDown)
358
359 def writePID(self):
360 # Create a PID file
361 pid = str(os.getpid())
362 pf = open(config.pid, "w")
363 pf.write("%s\n" % pid)
364 pf.close()
365
366 def shuttingDown(self):
367 self.transportSvc.removeMe()
368 # Keep the transport running for another 3 seconds
369 def cb(ignored=None):
370 if config.pid:
371 twistd.removePID(config.pid)
372 d = Deferred()
373 d.addCallback(cb)
374 reactor.callLater(3.0, d.callback, None)
375 return d
376
377
378
379 def SIGHUPstuff(*args):
380 global configFile, configOptions
381 xmlconfig.reloadConfig(configFile, configOptions)
382 if config.pid and os.name != "posix":
383 config.pid = ""
384 debug.reloadConfig()
385 legacy.reloadConfig()
386
387 if os.name == "posix":
388 import signal
389 # Set SIGHUP to reload the config file & close & open debug file
390 signal.signal(signal.SIGHUP, SIGHUPstuff)
391 # Load some scripts for PID and daemonising
392 from twisted.scripts import twistd
393
394
395 def main():
396 # Create the application
397 app = App()
398 reactor.run()
399
400 if __name__ == "__main__":
401 main()
402