root/trunk/jjigw/component.py

Revision 165, 17.8 kB (checked in by jajcus, 2 years ago)

- addresses updated

Line 
1 #!/usr/bin/python -u
2 #
3 #  Jajcus' Jabber to IRC Gateway
4 #  Copyright (C) 2004  Jacek Konieczny <jajcus@jajcus.net>
5 #
6 #  This program is free software; you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation; either version 2 of the License, or
9 #  (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License along
17 #  with this program; if not, write to the Free Software Foundation, Inc.,
18 #  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20
21 import signal
22 import threading
23 import string
24 import logging
25
26 import pyxmpp.jabberd.component
27 from pyxmpp.presence import Presence
28 from pyxmpp.message import Message
29 from pyxmpp.streambase import StreamError,FatalStreamError
30 from pyxmpp.jid import JID
31 from pyxmpp.jabber.muccore import MUC_ADMIN_NS,MUC_NS
32 from pyxmpp.jabber.muccore import MucPresence,MucIq,MucAdminQuery
33 from pyxmpp.jabber.disco import DiscoItems,DiscoItem,DiscoInfo,DiscoIdentity
34
35 from ircsession import IRCSession
36 from spidentd import SPIdentD
37
38 class Component(pyxmpp.jabberd.component.Component):
39     def __init__(self,config,profile=False):
40         pyxmpp.jabberd.component.Component.__init__(self,config.jid,
41                 config.connect.secret,config.connect.host,config.connect.port,
42                 disco_name="JJIGW IRC gateway", disco_category="conference",disco_type="irc")
43         self.__logger=logging.getLogger("jjigw.Component")
44         self.profile=profile
45         self.shutdown=0
46         signal.signal(signal.SIGINT,self.signal_handler)
47         signal.signal(signal.SIGPIPE,self.signal_handler)
48         signal.signal(signal.SIGHUP,self.signal_handler)
49         signal.signal(signal.SIGTERM,self.signal_handler)
50         self.irc_sessions={}
51         self.config=config
52         if config.spidentd:
53             self.ident_handler=SPIdentD(self,config.spidentd)
54         else:
55             self.ident_handler=None
56         self.disco_info.add_identity("JJIGW IRC gateway", "conference", "text") # MUC compliance
57         self.disco_info.add_identity("JJIGW IRC gateway", "gateway", "x-irc") # non-conference gateway services
58
59     def get_session(self,user_jid,component_jid):
60         return self.irc_sessions.get((user_jid.as_unicode(),component_jid.domain))
61
62     def register_session(self,sess):
63         user_jid=sess.jid
64         component_jid=sess.network.jid
65         self.__logger.debug("Registering session: %r on %r for %r" % (sess,component_jid,user_jid))
66         self.irc_sessions[user_jid.as_unicode(),component_jid.domain]=sess
67
68     def unregister_session(self,sess):
69         user_jid=sess.jid
70         component_jid=sess.network.jid
71         self.__logger.debug("Unregistering session: %r on %r for %r" % (sess,component_jid,user_jid))
72         try:
73             del self.irc_sessions[user_jid.as_unicode(),component_jid.domain]
74         except KeyError:
75             self.__logger.debug("Session not found!")
76
77     def signal_handler(self,signum,frame):
78         self.__logger.debug("Signal %i received, shutting down..." % (signum,))
79         self.shutdown=1
80
81     def run(self,timeout):
82         self.connect()
83         try:
84             while (not self.shutdown and self.stream
85                     and not self.stream.eof and self.stream.socket is not None):
86                 try:
87                     self.stream.loop_iter(timeout)
88                 except (KeyboardInterrupt,SystemExit,FatalStreamError,StreamError):
89                     raise
90                 except:
91                     self.__logger.exception("Exception cought:")
92         finally:
93             if self.shutdown:
94                 for sess in self.irc_sessions.values():
95                     sess.disconnect("JJIGW shutdown")
96             threads=threading.enumerate()
97             for th in threads:
98                 try:
99                     th.join(10*timeout)
100                 except:
101                     pass
102             for th in threads:
103                 try:
104                     th.join(timeout)
105                 except:
106                     pass
107             self.disconnect()
108             self.__logger.debug("Exitting normally")
109
110     def send(self,stanza):
111         self.get_stream().send(stanza)
112
113     def stream_state_changed(self,state,arg):
114         print "*** State changed: %s %r ***" % (state,arg)
115
116     def authenticated(self):
117         pyxmpp.jabberd.component.Component.authenticated(self)
118         self.stream.set_iq_get_handler("query","jabber:iq:version",self.get_version)
119         self.stream.set_iq_get_handler("query","jabber:iq:register",self.get_register)
120         self.stream.set_iq_set_handler("query","jabber:iq:register",self.set_register)
121         self.stream.set_iq_set_handler("query",MUC_ADMIN_NS,self.set_muc_admin)
122         self.stream.set_presence_handler("available",self.presence_available)
123         self.stream.set_presence_handler("unavailable",self.presence_unavailable)
124         self.stream.set_presence_handler("subscribe",self.presence_control)
125         self.stream.set_message_handler("groupchat",self.groupchat_message)
126         self.stream.set_message_handler("normal",self.message)
127
128     def set_muc_admin(self,iq):
129         to=iq.get_to()
130         fr=iq.get_from()
131         if not to.node:
132             self.__logger.debug("admin request sent to JID without a node")
133             iq=iq.make_error_response("feature-not-implemented")
134             self.stream.send(iq)
135             return 1
136         if to.resource or not (to.node[0] in "#+!" or to.node.startswith(",amp,")):
137             self.__logger.debug("admin request sent not to a channel")
138             iq=iq.make_error_response("not-acceptable")
139             self.stream.send(iq)
140             return 1
141
142         iq=MucIq(iq)
143         sess=self.get_session(fr,to)
144         if not sess:
145             self.__logger.debug("User session not found")
146             iq=iq.make_error_response("recipient-unavailable")
147             self.stream.send(iq)
148             return 1
149
150         channel=sess.get_channel(to)
151         if not channel:
152             self.__logger.debug("Channel not found")
153             iq=iq.make_error_response("recipient-unavailable")
154             self.stream.send(iq)
155             return 1
156
157         query=iq.get_muc_child()
158         if not isinstance(query,MucAdminQuery):
159             self.__logger.debug("Bad query content")
160             iq=iq.make_error_response("bad-request")
161             self.stream.send(iq)
162             return 1
163
164         items=query.get_items()
165         if not items:
166             self.__logger.debug("No items in query")
167             iq=iq.make_error_response("bad-request")
168             self.stream.send(iq)
169             return 1
170         item=items[0]
171         if item.role=="none":
172             channel.kick_user(item.nick,item.reason,iq)
173         elif item.role=="visitor":
174             channel.devoice_user(item.nick,iq)
175         elif item.role=="participant":
176             channel.voice_user(item.nick,iq)
177         elif item.role=="moderator":
178             channel.op_user(item.nick,iq)
179         else:
180             self.__logger.debug("Unknown admin action")
181             iq=iq.make_error_response("feature-not-implemented")
182             self.stream.send(iq)
183             return 1
184
185     def get_version(self,iq):
186         iq=iq.make_result_response()
187         q=iq.new_query("jabber:iq:version")
188         q.newTextChild(q.ns(),"name","Jajcus' Jabber-IRC Gateway")
189         q.newTextChild(q.ns(),"version","0.2.2")
190         self.stream.send(iq)
191         return 1
192
193     def get_register(self,iq):
194         to=iq.get_to()
195         if to and to!=self.jid:
196             iq=iq.make_error_response("feature-not-implemented")
197             self.stream.send(iq)
198             return 1
199         iq=iq.make_result_response()
200         q=iq.new_query("jabber:iq:register")
201         q.newTextChild(q.ns(),"instructions","Enter anything below.")
202         q.newChild(q.ns(),"username",None)
203         q.newChild(q.ns(),"password",None)
204         self.stream.send(iq)
205         return 1
206
207     def set_register(self,iq):
208         to=iq.get_to()
209         if to and to!=self.jid:
210             iq=iq.make_error_response("feature-not-implemented")
211             self.stream.send(iq)
212             return 1
213         remove=iq.xpath_eval("r:query/r:remove",{"r":"jabber:iq:register"})
214         if remove:
215             m=Message(from_jid=iq.get_to(),to_jid=iq.get_from(),stanza_type="chat",
216                     body=u"Unregistered")
217             self.stream.send(m)
218             p=Presence(from_jid=iq.get_to(),to_jid=iq.get_from(),stanza_type="unsubscribe")
219             self.stream.send(p)
220             p=Presence(from_jid=iq.get_to(),to_jid=iq.get_from(),stanza_type="unsubscribed")
221             self.stream.send(p)
222             return 1
223         username=iq.xpath_eval("r:query/r:username",{"r":"jabber:iq:register"})
224         if username:
225             username=username[0].getContent()
226         else:
227             username=u""
228         password=iq.xpath_eval("r:query/r:password",{"r":"jabber:iq:register"})
229         if password:
230             password=password[0].getContent()
231         else:
232             password=u""
233         m=Message(from_jid=iq.get_to(),to_jid=iq.get_from(),stanza_type="chat",
234                 body=u"Registered with username '%s' and password '%s'"
235                 " (both ignored)" % (username,password))
236         self.stream.send(m)
237         p=Presence(from_jid=iq.get_to(),to_jid=iq.get_from(),stanza_type="subscribe")
238         self.stream.send(p)
239         iq=iq.make_result_response()
240         self.stream.send(iq)
241         return 1
242
243     def message(self,stanza):
244         to=stanza.get_to()
245         fr=stanza.get_from()
246         typ=stanza.get_type()
247         if typ not in (None,"chat"):
248             typ=None
249         sess=self.get_session(fr,to)
250         if not to.node:
251             if sess:
252                 m=Message(to_jid=fr,from_jid=to,body="Connected to: %s" % (sess.server,),stanza_type=typ)
253             else:
254                 m=Message(to_jid=fr,from_jid=to,body="Not connected",stanza_type=typ)
255             return 1
256         if not to.resource and (to.node[0] in "#+!" or to.node.startswith(",amp,")):
257             self.groupchat_message(stanza)
258         if sess:
259             sess.message_to_user(stanza)
260         else:
261             m=stanza.make_error_response("recipient-unavailable")
262             self.send(m)
263         return 1
264
265     def groupchat_message(self,stanza):
266         to=stanza.get_to()
267         if not to.node:
268             self.__logger.debug("No node in groupchat message target")
269             return 0
270         if to.node[0] not in "#+!" and not to.node.startswith(",amp,"):
271             self.__logger.debug("Groupchat message target is not a channel")
272             return self.message(stanza)
273         if to.resource:
274             self.__logger.debug("Groupchat message target is not bare JID")
275             return 0
276         fr=stanza.get_from()
277         sess=self.get_session(fr,to)
278         if sess:
279             sess.message_to_channel(stanza)
280         else:
281             m=stanza.make_error_response("recipient-unavailable")
282             self.send(m)
283         return 1
284
285     def presence_available(self,stanza):
286         nick = None
287         to = stanza.get_to()
288         fr = stanza.get_from()
289         status = stanza.get_status()
290         show = stanza.get_show()
291         if not status:
292             if not show:
293                 status = "Unknown"
294             else:
295                 status = show
296         if to.node and not to.resource:
297             p=stanza.make_error_response("bad-request")
298             self.send(p)
299             return 1
300         sess=self.get_session(fr,to)
301         if sess:
302             if to.node and not sess.check_nick(to.resource):
303                 p=stanza.make_error_response("conflict")
304                 self.send(p)
305                 return 1
306             if show in ("away", "xa", "dnd"):
307                 sess.set_away(status)
308             else:
309                 sess.set_back()
310         else:
311             nick=to.resource
312             if not nick:
313                 nick=fr.node
314             try:
315                 sess=IRCSession(self,self.config,to,fr,nick)
316             except ValueError,e:
317                 print `e`
318                 e=stanza.make_error_response("bad-request")
319                 self.send(e)
320                 return
321             self.register_session(sess)
322         if to.node:
323             sess.join(MucPresence(stanza))
324         else:
325             sess.login(stanza)
326         return 1
327
328     def presence_unavailable(self,stanza):
329         to=stanza.get_to()
330         fr=stanza.get_from()
331         status=stanza.get_status()
332         sess=self.get_session(fr,to)
333         if sess:
334             if to.node:
335                 disconnected=sess.leave(stanza)
336             else:
337                 disconnected=sess.logout(stanza)
338             if disconnected:
339                 self.unregister_session(sess)
340         return 1
341
342     def presence_control(self,stanza):
343         p=stanza.make_accept_response()
344         self.stream.send(p)
345         return 1
346
347     def register_connection(self,conninfo):
348         if self.ident_handler:
349             self.ident_handler.register_connection(conninfo)
350
351     def unregister_connection(self,conninfo):
352         if self.ident_handler:
353             self.ident_handler.unregister_connection(conninfo)
354
355     def disco_get_info(self,node,iq):
356         to=iq.get_to()
357         try:
358             network=self.config.get_network(to)
359         except KeyError:
360             return iq.make_error_response("recipient-unavailable")
361         if to.node is None and to.resource is None:
362             di=DiscoInfo()
363             if node is None:
364                 di.add_feature("jabber:iq:version")
365                 di.add_feature("jabber:iq:register")
366                 di.add_feature(MUC_NS)
367                 if network.name:
368                     name=network.name
369                 else:
370                     name="IRC gateway"
371                 DiscoIdentity(di,name,"conference","irc")
372                 DiscoIdentity(di,name,"conference","text")
373                 DiscoIdentity(di,name,"gateway","x-irc")
374             return di
375         elif len(to.node)>1 and to.node[0] in u"&#+!" and to.resource is None:
376             di=DiscoInfo()
377             di.add_feature(MUC_NS)
378             if network.name:
379                 name="%s channel on %s IRC network" % (to.node,network.name)
380             else:
381                 name="%s IRC channel" % (to.node,)
382             DiscoIdentity(di, name, "conference", "text")
383             DiscoIdentity(di, name, "conference", "irc")
384             return di
385         return iq.make_error_response("feature-not-implemented")
386
387     def disco_get_items(self,node,iq):
388         to=iq.get_to()
389         fr=iq.get_from()
390         try:
391             network=self.config.get_network(to)
392         except KeyError:
393             return iq.make_error_response("recipient-unavailable")
394         if to.node is not None or to.resource is not None:
395             return iq.make_error_response("feature-not-implemented")
396         if not node:
397             di=DiscoItems()
398             print "Requester: %r Admins: %r" % (fr,self.config.admins)
399             if fr in self.config.admins or fr.bare() in self.config.admins:
400                 DiscoItem(di,to,"admin","Administrator tree")
401             if network.channels:
402                 for c in network.channels.values():
403                     if not c.browseable:
404                         continue
405                     desc = c.description
406                     if not c.description:
407                         desc = "%s IRC channel on %s IRC network" % (c.name,network.name)
408                     jid = JID(c.name,to,None);
409                     DiscoItem(di,jid,None,desc);
410             return di
411         if node=="admin" or node.startswith("admin."):
412             if fr not in self.config.admins and fr.bare() not in self.config.admins:
413                 return iq.make_error_response("forbidden")
414         else:
415             return iq.make_error_response("item-not-found")
416         node=node.split(".")
417         if node==["admin"]:
418             di=DiscoItems()
419             DiscoItem(di,to,"admin.sessions","Sessions (jid nick)")
420             return di
421         if node==["admin","sessions"]:
422             di=DiscoItems()
423             for sess in self.irc_sessions.values():
424                 if not sess.network==network:
425                     continue
426                 DiscoItem(di,to,"admin.sessions.%s" % (id(sess),),
427                         u"%r %r" % (sess.jid.as_unicode(),sess.nick))
428             return di
429         if len(node)>2 and node[:2]==["admin","sessions"]:
430             try:
431                 sessid=int(node[2])
432             except ValueError:
433                 return iq.make_error_response("item-not-found")
434             sess=None
435             for s in self.irc_sessions.values():
436                 if id(s)==sessid:
437                     sess=s
438                     break
439             if sess is None:
440                 return iq.make_error_response("item-not-found")
441             if len(node)==3:
442                 di=DiscoItems()
443                 DiscoItem(di,sess.jid,None,"Owner")
444                 DiscoItem(di,to,string.join(node+["used_for"],"."),"Used for")
445                 DiscoItem(di,to,string.join(node+["users"],"."),"Known IRC users")
446                 DiscoItem(di,to,string.join(node+["channels"],"."),"Active channels")
447                 return di
448             if len(node)==4 and node[3]=="used_for":
449                 di=DiscoItems()
450                 for j in sess.used_for:
451                     DiscoItem(di,j,None,j.as_unicode())
452                 return di
453             if len(node)==4 and node[3]=="users":
454                 di=DiscoItems()
455                 for u in sess.users.values():
456                     DiscoItem(di,to,string.join(node+[str(id(u))],"."),`u.nick`)
457                 return di
458             if len(node)==4 and node[3]=="channels":
459                 di=DiscoItems()
460                 for ch in sess.channels.values():
461                     DiscoItem(di,to,string.join(node+[str(id(ch))],"."),`ch.name`)
462                 return di
463
464         return iq.make_error_response("item-not-found")
465
466 # vi: sts=4 et sw=4
467
Note: See TracBrowser for help on using the browser.