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