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