root/trunk/jjigw/ircsession.py

Revision 165, 25.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 threading
22 import socket
23 import md5
24 import select
25 import string
26 import random
27 import logging
28
29 from pyxmpp.message import Message
30 from pyxmpp.presence import Presence
31 from pyxmpp.jid import JID
32 from pyxmpp.jabber.muc import MucPresence
33
34 from ircuser import IRCUser
35 from channel import Channel
36 from common import ConnectionInfo
37 from common import node_to_channel,normalize,node_to_nick
38 from common import remove_evil_characters,strip_colors
39 from common import channel_re,numeric_re
40
41 class IRCSession:
42     commands_dont_show=[]
43     def __init__(self,component,config,netjid,jid,nick):
44         self.__logger=logging.getLogger("jjigw.IRCSession")
45         self.component=component
46         self.config=config
47         self.network=config.get_network(netjid)
48         self.default_encoding=self.network.default_encoding
49         self.conninfo=None
50         nick=nick.encode(self.default_encoding,"strict")
51         if not self.network.valid_nick(nick):
52             raise ValueError,"Bad nickname"
53         self.jid=jid
54         self.nick=nick
55         # Insert some sort of password lookup here if required
56         self.password=self.network.password
57         if self.component.profile:
58             ttarget=self.thread_run_prof
59         else:
60             ttarget=self.thread_run
61         self.thread=threading.Thread(name=u"%s on %s as %s" % (jid,self.network.jid,nick),
62                 target=ttarget)
63         self.thread.setDaemon(1)
64         self.exit=None
65         self.exited=0
66         self.socket=None
67         self.lock=threading.RLock()
68         self.cond=threading.Condition(self.lock)
69         self.servers_left=self.network.get_servers()
70         self.input_buffer=""
71         self.used_for=[]
72         self.server=None
73         self.login_requests=[]
74         self.join_requests=[]
75         self.messages_to_channel=[]
76         self.messages_to_user=[]
77         self.ready=0
78         self.channels={}
79         self.users={}
80         self.raw_channel=0
81         self.user=self.get_user(nick)
82         self.thread.start()
83
84     def register_user(self,user):
85         self.lock.acquire()
86         try:
87             self.users[normalize(user.nick)]=user
88         finally:
89             self.lock.release()
90
91     def unregister_user(self,user):
92         self.lock.acquire()
93         try:
94             nnick=normalize(user.nick)
95             if self.users.get(nnick)==user:
96                 del self.users[nnick]
97         finally:
98             self.lock.release()
99
100     def rename_user(self,user,new_nick):
101         self.lock.acquire()
102         try:
103             self.users[normalize(new_nick)]=user
104             try:
105                 del self.users[normalize(user.nick)]
106             except KeyError:
107                 pass
108             user.nick=new_nick
109         finally:
110             self.lock.release()
111
112     def get_user(self,prefix,create=1):
113         if "!" in prefix:
114             nick=prefix.split("!",1)[0]
115         else:
116             nick=prefix
117         if not self.network.valid_nick(nick,0):
118             return None
119         nnick=normalize(nick)
120         if self.users.has_key(nnick):
121             return self.users[nnick]
122         if not create:
123             return None
124         user=IRCUser(self,prefix)
125         self.register_user(user)
126         return user
127
128     def check_nick(self,nick):
129         nick=nick.encode(self.default_encoding)
130         if normalize(nick)==normalize(self.nick):
131             return 1
132         else:
133             return 0
134
135     def check_prefix(self,prefix):
136         if "!" in prefix:
137             nick=prefix.split("!",1)[0]
138         else:
139             nick=prefix
140         return normalize(nick)==normalize(self.nick)
141
142     def prefix_to_jid(self,prefix):
143         if channel_re.match(prefix):
144             node=channel_to_node(prefix,self.default_encoding)
145             return JID(node,self.network.jid.domain,None)
146         else:
147             if "!" in prefix:
148                 nick,user=prefix.split("!",1)
149             else:
150                 nick=prefix
151                 user=""
152             node=nick_to_node(nick,self.default_encoding)
153             resource=unicode(user,self.default_encoding,"replace")
154             return JID(node,self.network.jid.domain,resource)
155
156     def thread_run_prof(self):
157         import profile
158         p=profile.Profile()
159         p.runcall(self.thread_run)
160         p.create_stats()
161         p.dump_stats("jjigw-%s.prof" % (threading.currentThread().getName().replace("/","_"),))
162
163     def thread_run(self):
164         clean_exit=1
165         try:
166             self.thread_loop()
167         except:
168             clean_exit=0
169             self.__logger.exception("Exception cought:")
170         self.lock.acquire()
171         try:
172             if not self.exited and self.socket:
173                 try:
174                     if clean_exit and self.component.shutdown:
175                         self._send("QUIT :JJIGW shutdown")
176                     elif clean_exit and self.exit:
177                         self._send("QUIT :%s" % (self.exit.encode(self.default_encoding,"replace")))
178                     else:
179                         self._send("QUIT :Internal JJIGW error")
180                 except socket.error:
181                     pass
182                 self.exited=1
183             if self.socket:
184                 try:
185                     self.socket.close()
186                 except:
187                     pass
188                 self.socket=None
189             self.component.unregister_session(self)
190         finally:
191             self.lock.release()
192         for j in self.used_for:
193             p=Presence(from_jid=j,to_jid=self.jid,stanza_type="unavailable")
194             self.component.send(p)
195         self.used_for=[]
196
197     def thread_loop(self):
198         self.__logger.debug("thread_loop()")
199         while not self.exit and not self.component.shutdown:
200             self.lock.acquire()
201             try:
202                 if self.socket is None:
203                     self._try_connect()
204                 sock=self.socket
205                 if sock is None:
206                     self.__logger.debug("sock is None")
207                     continue
208                 self.lock.release()
209                 try:
210                     id,od,ed=select.select([sock],[],[sock],1)
211                 finally:
212                     self.lock.acquire()
213                 if self.socket in id:
214                     r=self.socket.recv(1024)
215                     if r:
216                         self.input_buffer+=r
217                         while self.input_buffer.find("\r\n")>-1:
218                             input,self.input_buffer=self.input_buffer.split("\r\n",1)
219                             self._safe_process_input(input)
220                     else:
221                         try:
222                             self.socket.close()
223                         except:
224                             pass
225                         self.socket=None
226                         self.exited=1
227                 elif self.socket in ed:
228                     try:
229                         self.socket.close()
230                     except (socket.error),err:
231                         self.__logger.debug("Error on receive: %r" % (err,))
232                         pass
233                     self.socket=None
234                     # suppose error ocurred, wh'll try reconnect
235                     self.exited=0
236             finally:
237                 self.lock.release()
238         self.lock.acquire()
239         try:
240             if self.socket:
241                 self.socket.close()
242                 self.socket=None
243             if self.conninfo:
244                 self.component.unregister_connection(self.conninfo)
245                 self.conninfo=None
246         finally:
247             self.lock.release()
248
249     def _try_connect(self):
250         if not self.servers_left:
251             self.__logger.debug("No servers left, quitting")
252             self.exit="No servers left, quitting"
253             return
254         if self.conninfo:
255             self.component.unregister_connection(self.conninfo)
256             self.conninfo=None
257         if self.socket:
258             self.socket.close()
259             self.socket=None
260         self.exited = 0
261         server=self.servers_left.pop(0)
262         self.__logger.debug("Trying to connect to %r" % (server,))
263         if self.raw_channel:
264             self.pass_message_to_raw_channel("Connecting to %s:%s..." % (server.host,server.port))
265         try:
266             self.socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
267             if server.bind:
268                 self.__logger.debug("Binding local socket to: %s:%d"
269                         % (server.bind, server.bindport))
270                 self.socket.bind((server.bind, server.bindport))
271                 if not self.socket:
272                     self.__logger.debug("Error binding interface")
273             self.socket.connect((server.host,server.port))
274         except (IOError,OSError,socket.error),err:
275             self.__logger.debug("Server connect error: %r" % (err,))
276             if self.raw_channel:
277                 self.pass_message_to_raw_channel("Connect error: %r" % (err,))
278             if self.socket:
279                 try:
280                     self.socket.close()
281                     if self.conninfo:
282                         self.component.unregister_connection(self.conninfo)
283                         self.conninfo=None
284                 except:
285                     pass
286             self.socket=None
287             return
288         if self.raw_channel:
289             self.pass_message_to_raw_channel("Connected.")
290         self.__logger.debug("Connected.")
291         if self.password:
292             self._send("PASS %s" % (self.password,))
293         self._send("NICK %s" % (self.nick,))
294         user=md5.new(self.jid.bare().as_string()).hexdigest()[:64]
295         self.conninfo=ConnectionInfo(self.socket,user)
296         self.component.register_connection(self.conninfo)
297         # If we had to issue a password, then chances are the server
298         # is going to be anal about what we pass for the USER.
299         # In this case, don't bother sending the hash.
300         if self.password:
301             self._send("USER %s 0 * :JJIGW User %s" % (self.nick, user) )
302         else:
303             self._send("USER %s 0 * :JJIGW User %s" % (user,user))
304         self.server=server
305         self.cond.notify()
306
307     def set_away(self,status):
308         self.send("AWAY :%s" % (status))
309
310     def set_back(self):
311         self.send("AWAY")
312
313     def _send(self,str):
314         if self.socket and not self.exited:
315             self.__logger.debug("IRC OUT: %r" % (str,))
316             self.socket.send(str+"\r\n")
317             if self.raw_channel:
318                 self.pass_output_to_raw_channel(str)
319         else:
320             self.__logger.debug("ignoring out: %r" % (str,))
321
322     def send(self,str):
323         self.lock.acquire()
324         try:
325             self._send(str)
326         finally:
327             self.lock.release()
328
329     def _safe_process_input(self,input):
330         try:
331             self._process_input(input)
332         except:
333             self.__logger.exception("Exception cought:")
334
335     def _process_input(self,input):
336         self.__logger.debug("Server message: %r" % (input,))
337         split=input.split(" ")
338         if split[0].startswith(":"):
339             prefix=split[0][1:]
340             split=split[1:]
341         else:
342             prefix=None
343         if split:
344             command=split[0]
345             split=split[1:]
346         else:
347             command=None
348         params=[]
349         while split:
350             if split[0].startswith(":"):
351                 params.append(string.join(split," ")[1:])
352                 break
353             params.append(split[0])
354             split=split[1:]
355         if self.raw_channel:
356             self.pass_input_to_raw_channel(prefix,command,params)
357         if command and numeric_re.match(command):
358             params=params[1:]
359         self.lock.release()
360         try:
361             f=None
362             for c in self.channels.keys():
363                 if params and normalize(params[0])==c:
364                     f=getattr(self.channels[c],"irc_cmd_"+command,None)
365                     if f:
366                         break
367             if not f:
368                 f=getattr(self,"irc_cmd_"+command,None)
369             if f:
370                 f(prefix,command,params)
371         finally:
372             self.lock.acquire()
373
374     def irc_cmd_PING(self,prefix,command,params):
375         # TODO: If this were implemented absolutely correctly,
376         # shouldn't this ping be sent to the client instead
377         # of immediately sending it back to the server?
378         # Otherwise, it will not give you an accurate reading
379         # of lag.
380         self.send("PONG %s" % (params[0],))
381
382     def irc_cmd_NICK(self,prefix,command,params):
383         if len(params)<1:
384             return
385         user=self.get_user(prefix)
386         if params[0]!=user.nick:
387             oldnick=user.nick
388             self.rename_user(user,params[0])
389             for ch in user.channels.values():
390                 ch.nick_changed(oldnick,user)
391
392     def irc_cmd_PRIVMSG(self,prefix,command,params):
393         self.irc_message(prefix,command,params)
394
395     def irc_cmd_NOTICE(self,prefix,command,params):
396         self.irc_message(prefix,command,params)
397
398     def irc_message(self,prefix,command,params):
399         if len(params)<2 or not prefix:
400             self.__logger.debug("ignoring it")
401             return
402         user=self.get_user(prefix)
403         if not user:
404             self.__logger.debug("could not convert %r to IRCUser object" % (prefix,))
405             return
406         if user.current_thread:
407             typ,thread,fr=user.current_thread
408         else:
409             typ="chat"
410             thread=str(random.random())
411             fr=None
412             user.current_thread=typ,thread,None
413         if not fr:
414             fr=user.jid()
415         body=unicode(params[1],self.default_encoding,"replace")
416         m=Message(stanza_type=typ,from_jid=fr,to_jid=self.jid,body=remove_evil_characters(strip_colors(body)))
417         self.component.send(m)
418
419     def login_error(self,join_condition,message_condition):
420         self.lock.acquire()
421         try:
422             if join_condition:
423                 for s in self.join_requests+self.login_requests:
424                     p=s.make_error_response(join_condition)
425                     self.component.send(p)
426                     try:
427                         self.used_for.remove(s.get_to())
428                     except ValueError:
429                         pass
430                 self.join_requests=[]
431                 self.login_requests=[]
432             if message_condition:
433                 for s in self.messages_to_user+self.messages_to_channel:
434                     p=s.make_error_response(message_condition)
435                     self.component.send(p)
436                 self.messages_to_user=[]
437                 self.messages_to_channel=[]
438             self.exit="IRC user registration failed"
439         finally:
440             self.lock.release()
441
442     def irc_cmd_001(self,prefix,command,params): # RPL_WELCOME
443         self.lock.acquire()
444         try:
445             self.__logger.debug("Connected successfully")
446             self.ready=1
447             for s in self.login_requests:
448                 self.login(s)
449             for s in self.join_requests:
450                 self.join(s)
451             for s in self.messages_to_user:
452                 self.message_to_user(s)
453             for s in self.messages_to_channel:
454                 self.message_to_channel(s)
455         finally:
456             self.lock.release()
457
458     def irc_cmd_431(self,prefix,command,params): # ERR_NONICKNAMEGIVEN
459         if self.ready:
460             return
461         self.login_error("undefined-condition","not-authorized")
462
463     def irc_cmd_432(self,prefix,command,params): # ERR_ERRONEUSNICKNAME
464         if self.ready:
465             return
466         self.login_error("bad-request","not-authorized")
467
468     def irc_cmd_433(self,prefix,command,params): # ERR_NICKNAMEINUSE
469         if self.ready:
470             return
471         self.login_error("conflict","not-authorized")
472
473     def irc_cmd_436(self,prefix,command,params): # ERR_NICKCOLLISION
474         if self.ready:
475             return
476         self.login_error("conflict","not-authorized")
477
478     def irc_cmd_437(self,prefix,command,params): # ERR_UNAVAILRESOURCE
479         if self.ready:
480             return
481         self.login_error("resource-constraint","not-authorized")
482
483     def irc_cmd_437(self,prefix,command,params): # ERR_RESTRICTED
484         if self.ready:
485             return
486         pass
487
488     def irc_cmd_401(self,prefix,command,params): # ERR_NOSUCHNICK
489         if len(params)>1:
490             nick,msg=params[:2]
491             error_text="%s: %s" % (nick,msg)
492         else:
493             error_text=None
494         self.send_error_message(params[0],"recipient-unavailable",error_text)
495
496     def irc_cmd_404(self,prefix,command,params): # ERR_CANNOTSENDTOCHAN
497         if len(params)>1:
498             error_text=params[1]
499         else:
500             error_text=None
501         self.send_error_message(params[0],"forbidden",error_text)
502
503     def irc_cmd_QUIT(self,prefix,command,params):
504         user=self.get_user(prefix)
505         user.leave_all()
506         self.unregister_user(user)
507
508     def irc_cmd_352(self,prefix,command,params): # RPL_WHOREPLY
509         self.__logger.debug("WHO reply received")
510         if len(params)<7:
511             self.__logger.debug("too short - ignoring")
512             return
513         user=self.get_user(params[4])
514         if not user:
515             self.__logger.debug("User: %r not found" % (params[4],))
516         else:
517             user.whoreply(params)
518
519     def send_error_message(self,source,cond,text):
520         text=remove_evil_characters(text)
521         user=self.get_user(source)
522         if user:
523             self.unregister_user(user)
524         if user and user.current_thread:
525             typ,thread,fr=user.current_thread
526             if not fr:
527                 fr=self.prefix_to_jid(source)
528             m=Message(stanza_type="error",error_cond=cond,error_text=text,
529                     to_jid=self.jid,from_jid=fr,thread=thread)
530         else:
531             fr=self.prefix_to_jid(source)
532             m=Message(stanza_type="error",error_cond=cond,error_text=text,
533                     to_jid=self.jid,from_jid=fr)
534         self.component.send(m)
535
536     def pass_input_to_raw_channel(self,prefix,command,params):
537         body=string.join([command]+params)
538         body=`body`
539         if body[0] in '"\'':
540             body=body[1:-1]
541         body=unicode(body,self.default_encoding,"replace")
542         if prefix:
543             prefix=remove_evil_characters(prefix)
544             prefix=`prefix`
545             if prefix[0] in '"\'':
546                 prefix=prefix[1:-1]
547             prefix=unicode(prefix,self.default_encoding,"replace")
548         else:
549             prefix=None
550         fr=JID('#',self.network.jid.domain,prefix)
551         m=Message(to_jid=self.jid,from_jid=fr,body=body,stanza_type="groupchat")
552         self.component.send(m)
553
554     def pass_output_to_raw_channel(self,s):
555         body=`s`
556         if body[0] in '"\'':
557             body=body[1:-1]
558         body=unicode(body,self.default_encoding,"replace")
559         nick=unicode(self.nick,self.default_encoding,"replace")
560         fr=JID('#',self.network.jid.domain,nick)
561         m=Message(to_jid=self.jid,from_jid=fr,body=body,stanza_type="groupchat")
562         self.component.send(m)
563
564     def pass_message_to_raw_channel(self,msg):
565         fr=JID('#',self.network.jid.domain,None)
566         m=Message(to_jid=self.jid,from_jid=fr,body=msg,stanza_type="groupchat")
567         self.component.send(m)
568
569     def join(self,stanza):
570         to=stanza.get_to()
571         if to.node=='#':
572             return self.join_raw_channel(stanza)
573         self.cond.acquire()
574         try:
575             if not self.ready:
576                 self.join_requests.append(stanza.copy())
577                 return
578         finally:
579             self.cond.release()
580         try:
581             channel=node_to_channel(to.node,self.default_encoding)
582         except ValueError:
583             e=stanza.make_error_response("not-acceptable")
584             self.component.send(e)
585             return
586         if self.channels.has_key(normalize(channel)):
587             return
588         if to not in self.used_for:
589             self.used_for.append(to)
590         channel=Channel(self,channel)
591         channel.join(stanza)
592         self.channels[normalize(channel.name)]=channel
593
594     def join_raw_channel(self,stanza):
595         self.raw_channel=1
596         to=stanza.get_to()
597         if to not in self.used_for:
598             self.used_for.append(to)
599         p=Presence(from_jid=to,to_jid=stanza.get_from())
600         self.component.send(p)
601
602     def leave(self,stanza):
603         to=stanza.get_to()
604         if to.node=='#':
605             return self.leave_raw_channel(stanza)
606         channel=self.get_channel(stanza.get_to())
607         if channel:
608            channel.leave(stanza)
609            self.logout(stanza,0)
610         else:
611            self.logout(stanza)
612
613     def leave_raw_channel(self,stanza):
614         self.raw_channel=0
615         self.logout(stanza)
616
617     def login(self,stanza):
618         self.cond.acquire()
619         try:
620             if not self.ready:
621                 self.login_requests.append(stanza.copy())
622                 return
623         finally:
624             self.cond.release()
625         to=stanza.get_to()
626         if to not in self.used_for:
627             self.used_for.append(to)
628         fr=stanza.get_from()
629         p=Presence(to_jid=fr,from_jid=to,status=stanza.get_status(),show=stanza.get_show())
630         self.component.send(p)
631
632     def logout(self,stanza,send_response=1):
633         to=stanza.get_to()
634         if to not in self.used_for:
635             self.__logger.debug("Unavailable presence sent with no matching available presence, ignoring it")
636             return 0
637         try:
638             self.used_for.remove(to)
639         except:
640             pass
641         if send_response:
642             p=Presence(
643                 stanza_type="unavailable",
644                 to_jid=stanza.get_from(),
645                 from_jid=stanza.get_to()
646                 );
647             self.component.send(p)
648         if not self.used_for:
649             self.disconnect(stanza.get_status())
650             return 1
651         else:
652             return 0
653
654     def channel_left(self,channel):
655         try:
656             del self.channels[normalize(channel.name)]
657         except KeyError:
658             pass
659         if not channel.room_jid:
660             return
661         if channel.room_jid not in self.used_for:
662             return
663         try:
664             self.used_for.remove(channel.room_jid)
665         except:
666             pass
667         if not self.used_for:
668             self.disconnect("Quit")
669
670     def get_channel(self,jid):
671         channel_name=jid.node
672         try:
673             channel_name=node_to_channel(channel_name,self.default_encoding)
674         except ValueError:
675             self.__logger.debug("Bad channel name: %r" % (channel_name,))
676             return None
677         if not channel_re.match(channel_name):
678             self.__logger.debug("Bad channel name: %r" % (channel_name,))
679             return None
680         return self.channels.get(normalize(channel_name))
681
682     def message_to_channel(self,stanza):
683         self.cond.acquire()
684         try:
685             if not self.ready:
686                 self.messages_to_channel.append(stanza.copy())
687                 return
688         finally:
689             self.cond.release()
690         channel=self.get_channel(stanza.get_to())
691         if not channel:
692             e=stanza.make_error_response("bad-request")
693             self.component.send(e)
694             return
695         if channel:
696             encoding=channel.encoding
697         else:
698             encoding=self.default_encoding
699         subject=stanza.get_subject()
700         if subject and channel:
701             channel.change_topic(subject,stanza.copy())
702         body=stanza.get_body()
703         if body:
704             body=body.encode(encoding,"replace")
705             body=body.replace("\r"," ")
706             if body.startswith("/me "):
707                 body="\001ACTION "+body[4:]+"\001"
708             for line in body.split("\n"):
709                 self.send("PRIVMSG %s :%s" % (channel.name,line))
710             channel.irc_cmd_PRIVMSG(self.nick,"PRIVMSG",[channel.name,body])
711
712     def message_to_user(self,stanza):
713         self.cond.acquire()
714         try:
715             if not self.ready:
716                 self.messages_to_user.append(stanza.copy())
717                 return
718         finally:
719             self.cond.release()
720         to=stanza.get_to()
721         if to.resource and (to.node[0] in "#+!" or to.node.startswith(",amp,")):
722             nick=to.resource
723             thread_fr=stanza.get_to()
724         else:
725             nick=to.node
726             thread_fr=None
727         try:
728             nick=node_to_nick(nick,self.default_encoding,self.network)
729         except ValueError:
730             debug("Bad nick: %r" % (nick,))
731             e=stanza.make_error_response("not-acceptable")
732             self.component.send(e)
733             return
734         if not self.network.valid_nick(nick):
735             debug("Bad nick: %r" % (nick,))
736             e=stanza.make_error_response("not-acceptable")
737             self.component.send(e)
738             return
739         user=self.get_user(nick)
740         user.current_thread=stanza.get_type(),stanza.get_thread(),thread_fr
741         body=stanza.get_body().encode(self.default_encoding,"replace")
742         body=body.replace("\r"," ")
743         if body.startswith("/me "):
744             body="\001ACTION "+body[4:]+"\001"
745         for line in body.split("\n"):
746             self.send("PRIVMSG %s :%s" % (nick,line))
747
748     def disconnect(self,reason):
749         if not reason:
750             reason=u"Unknown reason"
751         self.send("QUIT :%s" % (reason.encode(self.default_encoding,"replace"),))
752         self.exit=reason
753         self.exited=1
754
755 # vi: sts=4 et sw=4
756
Note: See TracBrowser for help on using the browser.