/
/irc.py
  1 # irgramd: IRC-Telegram gateway
  2 # irc.py: IRC server side implementation
  3 #
  4 # Copyright (c) 2019 Peter Bui <pbui@bx612.space>
  5 # Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
  6 #
  7 # Use of this source code is governed by a MIT style license that
  8 # can be found in the LICENSE file included in this project.
  9 
 10 import collections
 11 import logging
 12 import re
 13 import socket
 14 import string
 15 import time
 16 
 17 import tornado.ioloop
 18 
 19 # Local modules
 20 
 21 from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH
 22 from irc_replies import irc_codes
 23 from utils import chunks, set_replace, split_lines
 24 from service import service
 25 from exclam import exclam
 26 
 27 # Constants
 28 
 29 SRV = None
 30 ALL_PARAMS = 16
 31 VALID_IRC_NICK_FIRST_CHARS   = string.ascii_letters + '[]\`_^{|}'
 32 VALID_IRC_NICK_CHARS         = VALID_IRC_NICK_FIRST_CHARS + string.digits + '-'
 33 
 34 # IRC Regular Expressions
 35 
 36 PREFIX          = r'(?ai)(:[^ ]+ +|)'
 37 IRC_JOIN_RX     = re.compile(PREFIX + r'JOIN( +:| +|\n)(?P<channels>[^\n ]+|)')
 38 IRC_LIST_RX     = re.compile(PREFIX + r'LIST( +:| +|\n)(?P<channels>[^\n ]+|)')
 39 IRC_MODE_RX     = re.compile(PREFIX + r'MODE( +|\n)(?P<target>[^ ]+( +|\n)|)(?P<mode>[^ ]+( +|\n)|)(?P<arguments>[^\n]+|)')
 40 IRC_MOTD_RX     = re.compile(PREFIX + r'MOTD( +:| +|\n)(?P<target>[^\n ]+|)')
 41 IRC_NAMES_RX    = re.compile(PREFIX + r'NAMES( +:| +|\n)(?P<channels>[^\n ]+|)')
 42 IRC_NICK_RX     = re.compile(PREFIX + r'NICK( +:| +|\n)(?P<nick>[^\n ]+|)')
 43 IRC_PART_RX     = re.compile(PREFIX + r'PART( +|\n)(?P<channels>[^ ]+|)( +:| +|\n|)(?P<reason>[^\n]+|)')
 44 IRC_PASS_RX     = re.compile(PREFIX + r'PASS( +:| +|\n)(?P<password>[^\n ]+|)')
 45 IRC_PING_RX     = re.compile(PREFIX + r'PING( +:| +|\n)(?P<payload>[^\n]+|)')
 46 IRC_PRIVMSG_RX  = re.compile(PREFIX + r'PRIVMSG( +|\n)(?P<target>[^ ]+)( +:| +|\n)(?P<message>[^\n]+|)')
 47 IRC_QUIT_RX     = re.compile(PREFIX + r'QUIT( +:| +|\n)(?P<reason>[^\n]+|)')
 48 IRC_TOPIC_RX    = re.compile(PREFIX + r'TOPIC( +:| +|\n)(?P<channel>[^\n ]+|)')
 49 IRC_USER_RX     = re.compile(PREFIX + r'USER( +|\n)(?P<username>[^ ]+) +[^ ]+ +[^ ]+( +:| +|\n)(?P<realname>[^\n]+|)')
 50 IRC_USERHOST_RX = re.compile(PREFIX + r'USERHOST( +|\n)(?P<nick1>[^ ]+( +|\n)|)(?P<nick2>[^ ]+( +|\n)|)(?P<nick3>[^ ]+( +|\n)|)(?P<nick4>[^ ]+( +|\n)|)(?P<nick5>[^\n]+|)')
 51 IRC_VERSION_RX  = re.compile(PREFIX + r'VERSION( +:| +|\n)(?P<target>[^\n ]+|)')
 52 IRC_WHO_RX      = re.compile(PREFIX + r'WHO( +:| +|\n)(?P<target>[^\n ]+|)')
 53 IRC_WHOIS_RX    = re.compile(PREFIX + r'WHOIS( +:| +|\n)(?P<nicks>[^\n ]+|)')
 54 
 55 # IRC Handler
 56 
 57 class IRCHandler(object):
 58     def __init__(self, settings):
 59         self.logger     = logging.getLogger()
 60         self.hostname   = socket.getfqdn()
 61         self.conf = settings
 62         self.users      = {}
 63 
 64         # Initialize IRC
 65         self.initialize_irc()
 66 
 67 
 68     async def run(self, stream, address):
 69         user = IRCUser(stream, address)
 70 
 71         self.logger.info('Running IRC client connection from %s:%s', address[0], address[1])
 72 
 73         while True:
 74             try:
 75                 message = await user.stream.read_until(b'\n')
 76             except tornado.iostream.StreamClosedError:
 77                 user.stream = None
 78                 reason = user.close_reason if user.close_reason else ':Client disconnect'
 79                 await self.send_users_irc(user, 'QUIT', (reason,))
 80                 self.logger.info('Closing IRC client connection from %s:%s', address[0], address[1])
 81                 if user in self.users.values():
 82                     del self.users[user.irc_nick.lower()]
 83                     user.del_from_channels(self)
 84                 del user
 85                 break
 86             message = message.decode(self.conf['char_in_encoding'], errors='replace').replace('\r','\n')
 87             self.logger.debug(message)
 88 
 89             for pattern, handler, register_required, num_params_required in self.irc_handlers:
 90                 matches = pattern.match(message)
 91                 if matches:
 92                     if user.registered or not register_required:
 93                         params = matches.groupdict()
 94                         # Remove possible extra characters in parameters
 95                         params = {x:y.strip() for x,y in params.items()}
 96                         num_params = len([x for x in params.values() if x])
 97                         num_params_expected = len(params.keys())
 98                         if num_params >= self.num_params_necessary(num_params_required,
 99                                                                    num_params_expected):
100                             await handler(user, **params)
101                         else:
102                             await self.reply_code(user, 'ERR_NEEDMOREPARAMS')
103                     else:
104                         await self.reply_code(user, 'ERR_NOTREGISTERED', ('',), '*')
105                     break
106 
107             if not matches and user.registered:
108                 await self.reply_code(user, 'ERR_UNKNOWNCOMMAND')
109 
110     def set_telegram(self, tg):
111         self.tg = tg
112         self.service = service(self.conf, self.tg)
113         self.exclam = exclam(self.tg)
114 
115     # IRC
116 
117     def initialize_irc(self):
118         self.irc_handlers = \
119         (
120             # pattern              handle           register_required   num_params_required
121             (IRC_PRIVMSG_RX,  self.handle_irc_privmsg,  True,             ALL_PARAMS),
122             (IRC_PING_RX,     self.handle_irc_ping,     True,             ALL_PARAMS),
123             (IRC_JOIN_RX,     self.handle_irc_join,     True,             ALL_PARAMS),
124             (IRC_MODE_RX,     self.handle_irc_mode,     True,             1),
125             (IRC_NAMES_RX,    self.handle_irc_names,    True,             ALL_PARAMS),
126             (IRC_TOPIC_RX,    self.handle_irc_topic,    True,             ALL_PARAMS),
127             (IRC_USERHOST_RX, self.handle_irc_userhost, True,             1),
128             (IRC_PART_RX,     self.handle_irc_part,     True,             1),
129             (IRC_WHO_RX,      self.handle_irc_who,      True,             ALL_PARAMS),
130             (IRC_WHOIS_RX,    self.handle_irc_whois,    True,             ALL_PARAMS),
131             (IRC_LIST_RX,     self.handle_irc_list,     True,             0),
132             (IRC_NICK_RX,     self.handle_irc_nick,     False,            ALL_PARAMS),
133             (IRC_MOTD_RX,     self.handle_irc_motd,     True,             0),
134             (IRC_USER_RX,     self.handle_irc_user,     False,            ALL_PARAMS),
135             (IRC_QUIT_RX,     self.handle_irc_quit,     False,            0),
136             (IRC_VERSION_RX,  self.handle_irc_version,  True,             0),
137             (IRC_PASS_RX,     self.handle_irc_pass,     False,            ALL_PARAMS),
138         )
139         self.iid_to_tid   = {}
140         self.irc_channels = collections.defaultdict(set)
141         self.irc_channels_ops = collections.defaultdict(set)
142         self.irc_channels_founder = collections.defaultdict(set)
143         self.start_time   = time.strftime('%a %d %b %Y %H:%M:%S %z')
144 
145         self.service_user = IRCUser(None, ('Services',''), self.conf['service_user'],
146                                     'Control', 'Telegram Service', is_service=True)
147         self.users[self.conf['service_user'].lower()] = self.service_user
148 
149     async def send_irc_command(self, user, command):
150         self.logger.debug('Send IRC Command: %s', command)
151         command = command + '\r\n'
152         user.stream.write(command.encode(self.conf['char_out_encoding'], errors='replace'))
153 
154     # IRC handlers
155 
156     async def handle_irc_pass(self, user, password):
157         self.logger.debug('Handling PASS: %s %s', password)
158 
159         if user.registered:
160             await self.reply_code(user, 'ERR_ALREADYREGISTRED')
161         else:
162             user.recv_pass = password
163 
164     async def handle_irc_nick(self, user, nick):
165         self.logger.debug('Handling NICK: %s', nick)
166 
167         ni = nick.lower()
168         current = user.irc_nick.lower() if user.irc_nick else None
169         if ni == current:
170             return
171         if not user.valid_nick(nick):
172             await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,), '*')
173         elif ni in self.users.keys():
174             await self.reply_code(user, 'ERR_NICKNAMEINUSE', (nick,), '*')
175         elif user.pam_auth(nick, self.conf['pam'], self.conf['pam_group'], user.recv_pass) \
176              or user.local_auth(nick, self.conf['irc_nicks'], user.recv_pass, self.conf['irc_password'], self.conf['pam']):
177             if user.registered:
178                 # rename
179                 await self.send_users_irc(user, 'NICK', (nick,))
180                 del self.users[current]
181                 for ch in self.irc_channels.keys():
182                     set_replace(self.irc_channels[ch], current, ni)
183                     set_replace(self.irc_channels_ops[ch], current, ni)
184                     set_replace(self.irc_channels_founder[ch], current, ni)
185             user.irc_nick = nick
186             self.users[ni] = user
187             if not user.registered and user.irc_username:
188                 await self.register(user)
189         else:
190             if user.registered:
191                 await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,))
192             else:
193                 await self.reply_code(user, 'ERR_PASSWDMISMATCH')
194 
195     async def handle_irc_userhost(self, user, **nicks):
196         niv = nicks.values()
197         self.logger.debug('Handling USERHOST: %s', str(tuple(niv)))
198 
199         reply = ''
200         sep = ''
201         away = '+'
202         for ni in niv:
203             n = ni.lower()
204             if n in self.users.keys():
205                 usr = self.users[n]
206                 oper = '*' if usr.oper else ''
207                 reply += '{}{}{}={}{}@{}'.format(sep, usr.irc_nick, oper, away, usr.irc_username, usr.address)
208                 if not sep: sep = ' '
209         if reply:
210            await self.reply_code(user, 'RPL_USERHOST', (reply,))
211 
212     async def handle_irc_user(self, user, username, realname):
213         self.logger.debug('Handling USER: %s, %s', username, realname)
214 
215         user.irc_username = username
216         user.irc_realname = realname
217         if user.irc_nick:
218             await self.register(user)
219 
220     async def handle_irc_join(self, user, channels):
221         self.logger.debug('Handling JOIN: %s', channels)
222 
223         if channels == '0':
224             for channel in self.irc_channels.keys():
225                 if user.irc_nick in self.irc_channels[channel]:
226                     await self.part_irc_channel(user, channel, '')
227         else:
228             for channel in channels.split(','):
229                 if channel.lower() in self.irc_channels.keys():
230                     await self.join_irc_channel(user, channel, full_join=True)
231                 else:
232                     await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
233 
234     async def handle_irc_part(self, user, channels, reason):
235         self.logger.debug('Handling PART: %s, %s', channels, reason)
236 
237         for channel in channels.split(','):
238             chan = channel.lower()
239             if chan in self.irc_channels.keys():
240                 if user.irc_nick in self.irc_channels[chan]:
241                     await self.part_irc_channel(user, channel, reason)
242                 else:
243                     await self.reply_code(user, 'ERR_NOTONCHANNEL', (channel,))
244             else:
245                 await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
246 
247     async def handle_irc_list(self, user, channels):
248         self.logger.debug('Handling LIST: %s', channels)
249 
250         if channels:
251             chans = channels.split(',')
252         else:
253             chans = self.irc_channels.keys()
254 
255         await self.reply_code(user, 'RPL_LISTSTART')
256         for channel in chans:
257             chan = channel.lower()
258             if chan in self.irc_channels.keys():
259                 real_chan = self.get_realcaps_name(chan)
260                 users_count = len(self.irc_channels[chan])
261                 topic = await self.tg.get_channel_topic(chan, [None])
262                 await self.reply_code(user, 'RPL_LIST', (real_chan, users_count, topic))
263         await self.reply_code(user, 'RPL_LISTEND')
264 
265     async def handle_irc_names(self, user, channels):
266         self.logger.debug('Handling NAMES: %s', channels)
267 
268         for channel in channels.split(','):
269             await self.irc_namelist(user, channel)
270 
271     async def handle_irc_mode(self, user, target, mode, arguments):
272         self.logger.debug('Handling MODE: %s, %s, %s', target, mode, arguments)
273 
274         is_user = False
275         is_channel = False
276 
277         tgt = target.lower()
278         if tgt in self.users.keys():
279             if tgt == user.irc_nick:
280                 is_user = True
281             else:
282                 await self.reply_code(user, 'ERR_USERSDONTMATCH')
283         elif tgt[0] == '#':
284             if tgt in self.irc_channels.keys():
285                 is_channel = True
286             else:
287                 await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (target,))
288         else:
289             await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
290 
291         if not mode:
292             if is_user:
293                 await self.mode_user(user, user, False)
294             if is_channel:
295                 await self.mode_channel(user, target, False)
296         elif mode == 'b' and is_channel:
297             await self.reply_code(user, 'RPL_ENDOFBANLIST', (target,))
298 
299     async def handle_irc_motd(self, user, target):
300         self.logger.debug('Handling MOTD: %s', target)
301 
302         if not target or target == self.gethostname(user):
303             await self.send_motd(user)
304         else:
305             await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
306 
307     async def handle_irc_topic(self, user, channel):
308         self.logger.debug('Handling TOPIC: %s', channel)
309 
310         chan = channel.lower()
311         real_chan = self.get_realcaps_name(chan)
312         await self.irc_channel_topic(user, real_chan, [None])
313 
314     async def handle_irc_ping(self, user, payload):
315         self.logger.debug('Handling PING: %s', payload)
316 
317         await self.reply_command(user, SRV, 'PONG', (self.gethostname(user), payload))
318 
319     async def handle_irc_who(self, user, target):
320         self.logger.debug('Handling WHO: %s', target)
321         tgt = target.lower()
322         if tgt in self.irc_channels.keys():
323             users = self.irc_channels[tgt]
324             chan = self.get_realcaps_name(tgt)
325         elif tgt in self.users.keys():
326             users = (self.users[tgt],)
327             chan = '*'
328         else:
329             await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
330             return
331         for usr in users:
332             if not isinstance(usr,IRCUser):
333                 usr = self.users[usr.lower()]
334             op = self.get_irc_op(usr.irc_nick, chan)
335             await self.reply_code(user, 'RPL_WHOREPLY', (chan, usr.irc_username,
336                 usr.address, self.gethostname(user), usr.irc_nick, op, usr.irc_realname
337             ))
338         await self.reply_code(user, 'RPL_ENDOFWHO', (chan,))
339 
340     async def handle_irc_whois(self, user, nicks):
341         self.logger.debug('Handling WHOIS: %s', nicks)
342         for nick in nicks.split(','):
343             ni = nick.lower()
344             if ni in self.users.keys():
345                 usr = self.users[ni]
346                 real_ni = usr.irc_nick
347                 await self.reply_code(user, 'RPL_WHOISUSER', (real_ni, usr.irc_username, usr.address, usr.irc_realname))
348                 await self.reply_code(user, 'RPL_WHOISSERVER', (real_ni, self.gethostname(user)))
349                 chans = usr.get_channels(self)
350                 if chans: await self.reply_code(user, 'RPL_WHOISCHANNELS', (real_ni, chans))
351                 idle = await self.tg.get_telegram_idle(ni)
352                 if idle != None: await self.reply_code(user, 'RPL_WHOISIDLE', (real_ni, idle))
353                 if usr.oper: await self.reply_code(user, 'RPL_WHOISOPERATOR', (real_ni,))
354                 if usr.stream: await self.reply_code(user, 'RPL_WHOISACCOUNT', (real_ni,
355                                                            '{}|{}!{}@Telegram'.format(self.tg.tg_username,
356                                                            await self.tg.get_telegram_display_name_me(), self.tg.id
357                                )))
358                 if await self.tg.is_bot(ni):
359                     await self.reply_code(user, 'RPL_WHOISBOT', (real_ni,))
360                 elif usr.tls or (not usr.stream and not usr.is_service):
361                     proto = 'TLS' if usr.tls else 'MTProto'
362                     server = self.gethostname(user) if usr.stream else 'Telegram'
363                     await self.reply_code(user, 'RPL_WHOISSECURE', (real_ni, proto, server))
364                 if usr.is_service:
365                     await self.reply_code(user, 'RPL_WHOISSERVICE', (real_ni,))
366                 await self.reply_code(user, 'RPL_ENDOFWHOIS', (real_ni,))
367             else:
368                 await self.reply_code(user, 'ERR_NOSUCHNICK', (nick,))
369 
370     async def handle_irc_version(self, user, target):
371         self.logger.debug('Handling VERSION: %s', target)
372 
373         tgt = target.lower()
374         if not tgt or tgt == self.gethostname(user) or tgt in self.users.keys():
375             await self.reply_code(user, 'RPL_VERSION', (VERSION, self.gethostname(user)))
376             await self.send_isupport(user)
377         else:
378             await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
379 
380     async def handle_irc_privmsg(self, user, target, message):
381         self.logger.debug('Handling PRIVMSG: %s, %s', target, message)
382 
383         tgl = target.lower()
384         if self.service_user.irc_nick.lower() == tgl:
385             reply = await self.service.parse_command(message, user.irc_nick)
386             for reply_line in reply:
387                 await self.send_msg(self.service_user, None, reply_line, user)
388             return
389         defered_send = None
390         # Echo channel messages from IRC to other IRC connections
391         # because they won't receive event from Telegram
392         # used defered_send function when id is known
393         if tgl in self.irc_channels.keys():
394             chan = tgl
395             defered_send = self.send_msg_others
396             defered_target = chan
397         else:
398             chan = None
399 
400         if tgl == user.irc_nick:
401             tgt = self.tg.tg_username.lower()
402             # Echo message to the user him/herself in IRC
403             # because no event will be received from Telegram
404             # used defered_send function when id is known
405             defered_send = self.send_msg
406             defered_target = None
407         else:
408             tgt = tgl
409 
410         if tgt in self.iid_to_tid:
411             message = self.tg.replace_mentions(message, me_nick='', received=False)
412             telegram_id = self.iid_to_tid[tgt]
413             if message[0] == '!':
414                 cont, tg_msg = await self.exclam.command(message, telegram_id, user)
415             else:
416                 tg_msg = await self.tg.telegram_client.send_message(telegram_id, message)
417                 cont = True
418             if cont:
419                 mid = self.tg.mid.num_to_id_offset(telegram_id, tg_msg.id)
420                 text = '[{}] {}'.format(mid, message)
421                 self.tg.to_cache(tg_msg.id, mid, text, message, user, chan, media=None)
422 
423                 if defered_send:
424                     await defered_send(user, defered_target, text)
425         else:
426             await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
427 
428     async def handle_irc_quit(self, user, reason):
429         self.logger.debug('Handling TOPIC: %s', reason)
430 
431         await self.reply_command(user, SRV, 'ERROR', (':Client disconnect',))
432         user.close_reason = ':' + reason
433         user.stream.close()
434 
435     # IRC functions
436     async def register(self, user):
437         self.logger.info('Registered IRC user "%s" from %s:%s', user.irc_nick, user.address, user.port)
438 
439         user.registered = True
440         await self.send_greeting(user)
441         await self.send_help(user)
442         await self.check_telegram_auth(user)
443 
444     async def send_msg(self, source, target, message, selfuser=None):
445         messages = split_lines(message)
446         tgt = target.lower() if target else ''
447         is_chan = tgt in self.irc_channels.keys()
448         # source None (False): it's self Telegram user, see [1]
449         source_mask = source.get_irc_mask() if source else ''
450         for msg in messages:
451             if selfuser:
452                 irc_users = (selfuser,)
453             elif is_chan:
454                 irc_users = (u for u in self.users.values() if u.stream and u.irc_nick in self.irc_channels[tgt])
455             else:
456                 irc_users = (u for u in self.users.values() if u.stream)
457 
458             for irc_user in irc_users:
459                 await self.send_privmsg(irc_user, source_mask, target, msg)
460 
461     async def send_msg_others(self, source, target, message):
462         source_mask = source.get_irc_mask()
463         is_chan = target in self.irc_channels.keys()
464         if is_chan:
465             irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick
466                                                         and u.irc_nick in self.irc_channels[target])
467         else:
468             irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick)
469 
470         for irc_user in irc_users:
471             await self.send_privmsg(irc_user, source_mask, target, message)
472 
473     async def send_action(self, source, target, message):
474         action_message = '\x01ACTION {}\x01'.format(message)
475         await self.send_msg(source, target, action_message)
476 
477     async def send_privmsg(self, user, source_mask, target, msg):
478         # reference [1]
479         src_mask = source_mask if source_mask else user.get_irc_mask()
480         # target None (False): it's private, not a channel
481         tgt = target if target else user.irc_nick
482         if self.tg.refwd_me:
483              msg = msg.format(user.irc_nick)
484         # replace self @username and other mentions for self messages sent by this instance of irgramd
485         msg = self.tg.replace_mentions(msg, user.irc_nick)
486 
487         await self.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(src_mask, tgt, msg))
488 
489     async def reply_command(self, user, prfx, comm, params):
490         prefix = self.gethostname(user) if prfx == SRV else prfx.get_irc_mask()
491         p = len(params)
492         if p == 1:
493             fstri = ':{} {} {}'
494         else:
495             fstri = ':{} {}' + ((p - 1) * ' {}') + ' :{}'
496         await self.send_irc_command(user, fstri.format(prefix, comm, *params))
497 
498     async def reply_code(self, user, code, params=None, client=None):
499         num, tail = irc_codes[code]
500         if params:
501             nick = client if client else user.irc_nick
502             rest = tail.format(*params)
503             stri = ':{} {} {} {}'.format(self.gethostname(user), num, nick, rest)
504         else:
505             stri = ':{} {} {} :{}'.format(self.gethostname(user), num, user.irc_nick, tail)
506         await self.send_irc_command(user, stri)
507 
508     async def send_greeting(self, user):
509         await self.reply_code(user, 'RPL_WELCOME', (user.irc_nick,))
510         await self.reply_code(user, 'RPL_YOURHOST', (self.gethostname(user), VERSION))
511         await self.reply_code(user, 'RPL_CREATED', (self.start_time,))
512         await self.reply_code(user, 'RPL_MYINFO', (self.gethostname(user), VERSION))
513         await self.send_isupport(user)
514         await self.send_motd(user)
515         await self.mode_user(user, user, True)
516 
517     async def send_motd(self, user):
518         await self.reply_code(user, 'RPL_MOTDSTART', (self.gethostname(user),))
519         await self.reply_code(user, 'RPL_MOTD', ('Welcome to the irgramd server',))
520         await self.reply_code(user, 'RPL_MOTD', ('',))
521         await self.reply_code(user, 'RPL_MOTD', ('This is not a normal IRC server, it\'s a gateway that',))
522         await self.reply_code(user, 'RPL_MOTD', ('allows connecting from an IRC client (the program that',))
523         await self.reply_code(user, 'RPL_MOTD', ('you are [probably] using right now) to the Telegram instant',))
524         await self.reply_code(user, 'RPL_MOTD', ('messaging network as a regular user account (not bot)',))
525         await self.reply_code(user, 'RPL_MOTD', ('',))
526         await self.reply_code(user, 'RPL_MOTD', ('irgramd is an open source project that you can find on',))
527         await self.reply_code(user, 'RPL_MOTD', ('darcs repository: https://src.presi.org/darcs/irgramd',))
528         await self.reply_code(user, 'RPL_MOTD', ('git repository: https://github.com/prsai/irgramd',))
529         await self.reply_code(user, 'RPL_ENDOFMOTD')
530 
531     async def send_isupport(self, user):
532         await self.reply_code(user, 'RPL_ISUPPORT', (CHAN_MAX_LENGHT, NICK_MAX_LENGTH))
533 
534     async def send_help(self, user):
535         for line in (
536                       'Welcome to irgramd service',
537                       'use /msg {} help'.format(self.service_user.irc_nick),
538                       'to get help',
539                     ):
540             await self.send_msg(self.service_user, None, line, user)
541 
542     async def check_telegram_auth(self, user):
543         await self.tg.auth_checked.wait()
544         if not self.tg.authorized and not self.tg.ask_code:
545             for line in (
546                           '----',
547                           'Your Telegram account is not authorized yet,',
548                           'you must supply the code that Telegram sent to your phone',
549                           'or another client that is currently connected',
550                           'use /msg {} code <code>'.format(self.service_user.irc_nick),
551                           'e.g. /msg {} code 12345'.format(self.service_user.irc_nick),
552                         ):
553                 await self.send_msg(self.service_user, user.irc_nick, line)
554 
555     async def send_users_irc(self, prfx, command, params):
556         for usr in [x for x in self.users.values() if x.stream]:
557             await self.reply_command(usr, prfx, command, params)
558 
559     async def mode_user(self, user, usr, com):
560         modes = ''
561         if usr.oper: modes += 'o'
562         if usr.tls: modes += 'S'
563         if modes: modes = '+' + modes
564         if com:
565             await self.reply_command(user, usr, 'MODE', (usr.irc_nick, modes))
566         else:
567             await self.reply_code(user, 'RPL_UMODEIS', (modes,))
568 
569     async def mode_channel(self, user, channel, com):
570         modes = '+nt'
571         if com:
572             await self.reply_command(user, user, 'MODE', (channel, modes))
573         else:
574             await self.reply_code(user, 'RPL_CHANNELMODEIS', (channel, modes,''))
575 
576     async def join_irc_channel(self, user, channel, full_join):
577         entity_cache = [None]
578         chan = channel.lower()
579         real_chan = self.get_realcaps_name(chan)
580 
581         if full_join: self.irc_channels[chan].add(user.irc_nick)
582 
583         # Notify IRC users in this channel
584         for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
585             await self.reply_command(usr, user, 'JOIN', (real_chan,))
586 
587         if not full_join:
588             return
589 
590         op = self.get_irc_op(self.tg.tg_username, channel)
591         if op == '@': self.irc_channels_ops[chan].add(user.irc_nick)
592         elif op == '~': self.irc_channels_founder[chan].add(user.irc_nick)
593 
594         date = await self.tg.get_channel_creation(channel, entity_cache)
595         await self.reply_code(user, 'RPL_CREATIONTIME', (real_chan, date))
596         await self.irc_channel_topic(user, real_chan, entity_cache)
597         await self.irc_namelist(user, real_chan)
598 
599     async def part_irc_channel(self, user, channel, reason):
600         chan = channel.lower()
601         real_chan = self.get_realcaps_name(chan)
602 
603         # Notify IRC users in this channel
604         for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
605             await self.reply_command(usr, user, 'PART', (real_chan, reason))
606 
607         self.irc_channels[chan].remove(user.irc_nick)
608         self.irc_channels_ops[chan].discard(user.irc_nick)
609         self.irc_channels_founder[chan].discard(user.irc_nick)
610 
611     async def irc_channel_topic(self, user, channel, entity_cache):
612         chan = channel.lower()
613         topic = await self.tg.get_channel_topic(chan, entity_cache)
614         timestamp = await self.tg.get_channel_creation(chan, entity_cache)
615         if self.irc_channels_founder[chan]:
616             founder = list(self.irc_channels_founder[chan])[0]
617         else:
618             founder = self.service_user.irc_nick
619         await self.reply_code(user, 'RPL_TOPIC', (channel, topic))
620         await self.reply_code(user, 'RPL_TOPICWHOTIME', (channel, founder, timestamp))
621 
622     async def irc_namelist(self, user, channel):
623         nicks = [self.get_irc_op(x, channel) + x for x in self.irc_channels[channel.lower()]]
624         status = '='
625         for chunk in chunks(nicks, 25, ''):
626             await self.reply_code(user, 'RPL_NAMREPLY', (status, channel, ' '.join(chunk)))
627         await self.reply_code(user, 'RPL_ENDOFNAMES', (channel,))
628 
629     def get_irc_op(self, nick, channel):
630         chan = channel.lower()
631         if chan in self.irc_channels.keys():
632             if nick in self.irc_channels_ops[chan]:
633                 return '@'
634             if nick in self.irc_channels_founder[chan]:
635                 return '~'
636         return ''
637 
638     def get_realcaps_name(self, name):
639         # name must be in lower
640         return self.tg.tid_to_iid[self.iid_to_tid[name]]
641 
642     def num_params_necessary(self, num_params_required, num_params_expected):
643         if num_params_required == ALL_PARAMS:
644             npn = num_params_expected
645         else:
646             npn = num_params_required
647         return npn
648 
649     def gethostname(self, user):
650         return 'localhost' if user.from_localhost else self.hostname
651 
652 class IRCUser(object):
653     def __init__(self, stream, address, irc_nick=None, username='', realname=None, is_service=False):
654         self.stream  = stream
655         self.address = address[0]
656         self.port = str(address[1])
657         self.from_localhost = True if address[0].split('.')[0] == '127' else False
658         self.irc_nick = irc_nick
659         self.irc_username = str(username)
660         self.irc_realname = realname
661         self.registered = False
662         self.password = ''
663         self.recv_pass = ''
664         self.oper = False
665         self.tls = False
666         self.bot = None
667         self.is_service = is_service
668         self.close_reason = ''
669 
670     def get_irc_mask(self):
671         return '{}!{}@{}'.format(self.irc_nick, self.irc_username, self.address)
672 
673     def get_channels(self, irc):
674         res = ''
675         for chan in irc.irc_channels.keys():
676             if self.irc_nick in irc.irc_channels[chan]:
677                 res += irc.get_irc_op(self.irc_nick, chan) + chan + ' '
678         return res
679 
680     def valid_nick(self, nick):
681         if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS:
682             for x in nick[1:]:
683                 if x not in VALID_IRC_NICK_CHARS:
684                     return False
685             return True
686         else: return False
687 
688     def del_from_channels(self, irc, channels=None):
689         for chan in channels if channels else irc.irc_channels.keys():
690             irc.irc_channels[chan].discard(self.irc_nick)
691             irc.irc_channels_ops[chan].discard(self.irc_nick)
692             irc.irc_channels_founder[chan].discard(self.irc_nick)
693 
694     def pam_auth(self, nick, pam, pam_group, recv_pass):
695         if not pam: return False
696 
697         # Check if user is in groups (main or others)
698         if pam_group:
699             import pwd
700             import grp
701             try:
702                 user_group_id = pwd.getpwnam(nick).pw_gid
703                 group_data = grp.getgrnam(pam_group)
704                 pam_group_id = group_data.gr_gid
705                 group_members = group_data.gr_mem
706                 check_group = user_group_id == pam_group_id \
707                               or nick in group_members
708             except:
709                 check_group = False
710             if not check_group: return False
711 
712         # Check user authentication (via PAM)
713         import PAM
714         def pam_conv(auth, query_list, userData):
715             resp = []
716             resp.append((recv_pass, 0))
717             return resp
718         p = PAM.pam()
719         p.start('passwd')
720         p.set_item(PAM.PAM_USER, nick)
721         p.set_item(PAM.PAM_CONV, pam_conv)
722         try:
723             p.authenticate()
724             p.acct_mgmt()
725         except:
726             return False
727         else:
728             return True
729 
730     def local_auth(self, nick, nicks, recv_pass, irc_pass, pam):
731         return ( not pam
732                  and nick in nicks
733                  and recv_pass == irc_pass
734                )