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