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 )