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_LENGTH, NICK_MAX_LENGTH, MAX_LINE 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[:MAX_LINE])) 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_LENGTH, NICK_MAX_LENGTH)) 533 534 async def send_help(self, user): 535 for line in self.service.initial_help(): 536 await self.send_msg(self.service_user, None, line, user) 537 538 async def check_telegram_auth(self, user): 539 await self.tg.auth_checked.wait() 540 if not self.tg.authorized and not self.tg.ask_code: 541 for line in ( 542 '----', 543 'Your Telegram account is not authorized yet,', 544 'you must supply the code that Telegram sent to your phone', 545 'or another client that is currently connected', 546 'use /msg {} code <code>'.format(self.service_user.irc_nick), 547 'e.g. /msg {} code 12345'.format(self.service_user.irc_nick), 548 ): 549 await self.send_msg(self.service_user, user.irc_nick, line) 550 551 async def send_users_irc(self, prfx, command, params): 552 for usr in [x for x in self.users.values() if x.stream]: 553 await self.reply_command(usr, prfx, command, params) 554 555 async def mode_user(self, user, usr, com): 556 modes = '' 557 if usr.oper: modes += 'o' 558 if usr.tls: modes += 'S' 559 if modes: modes = '+' + modes 560 if com: 561 await self.reply_command(user, usr, 'MODE', (usr.irc_nick, modes)) 562 else: 563 await self.reply_code(user, 'RPL_UMODEIS', (modes,)) 564 565 async def mode_channel(self, user, channel, com): 566 modes = '+nt' 567 if com: 568 await self.reply_command(user, user, 'MODE', (channel, modes)) 569 else: 570 await self.reply_code(user, 'RPL_CHANNELMODEIS', (channel, modes,'')) 571 572 async def join_irc_channel(self, user, channel, full_join): 573 entity_cache = [None] 574 chan = channel.lower() 575 real_chan = self.get_realcaps_name(chan) 576 577 if full_join: self.irc_channels[chan].add(user.irc_nick) 578 579 # Notify IRC users in this channel 580 for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]: 581 await self.reply_command(usr, user, 'JOIN', (real_chan,)) 582 583 if not full_join: 584 return 585 586 op = self.get_irc_op(self.tg.tg_username, channel) 587 if op == '@': self.irc_channels_ops[chan].add(user.irc_nick) 588 elif op == '~': self.irc_channels_founder[chan].add(user.irc_nick) 589 590 date = await self.tg.get_channel_creation(channel, entity_cache) 591 await self.reply_code(user, 'RPL_CREATIONTIME', (real_chan, date)) 592 await self.irc_channel_topic(user, real_chan, entity_cache) 593 await self.irc_namelist(user, real_chan) 594 595 async def part_irc_channel(self, user, channel, reason): 596 chan = channel.lower() 597 real_chan = self.get_realcaps_name(chan) 598 599 # Notify IRC users in this channel 600 for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]: 601 await self.reply_command(usr, user, 'PART', (real_chan, reason)) 602 603 self.irc_channels[chan].remove(user.irc_nick) 604 self.irc_channels_ops[chan].discard(user.irc_nick) 605 self.irc_channels_founder[chan].discard(user.irc_nick) 606 607 async def irc_channel_topic(self, user, channel, entity_cache): 608 chan = channel.lower() 609 topic = await self.tg.get_channel_topic(chan, entity_cache) 610 timestamp = await self.tg.get_channel_creation(chan, entity_cache) 611 if self.irc_channels_founder[chan]: 612 founder = list(self.irc_channels_founder[chan])[0] 613 else: 614 founder = self.service_user.irc_nick 615 await self.reply_code(user, 'RPL_TOPIC', (channel, topic[:MAX_LINE])) 616 await self.reply_code(user, 'RPL_TOPICWHOTIME', (channel, founder, timestamp)) 617 618 async def irc_namelist(self, user, channel): 619 nicks = [self.get_irc_op(x, channel) + x for x in self.irc_channels[channel.lower()]] 620 status = '=' 621 for chunk in chunks(nicks, 25, ''): 622 await self.reply_code(user, 'RPL_NAMREPLY', (status, channel, ' '.join(chunk))) 623 await self.reply_code(user, 'RPL_ENDOFNAMES', (channel,)) 624 625 def get_irc_op(self, nick, channel): 626 chan = channel.lower() 627 if chan in self.irc_channels.keys(): 628 if nick in self.irc_channels_ops[chan]: 629 return '@' 630 if nick in self.irc_channels_founder[chan]: 631 return '~' 632 return '' 633 634 def get_realcaps_name(self, name): 635 # name must be in lower 636 return self.tg.tid_to_iid[self.iid_to_tid[name]] 637 638 def num_params_necessary(self, num_params_required, num_params_expected): 639 if num_params_required == ALL_PARAMS: 640 npn = num_params_expected 641 else: 642 npn = num_params_required 643 return npn 644 645 def gethostname(self, user): 646 return 'localhost' if user.from_localhost else self.hostname 647 648 class IRCUser(object): 649 def __init__(self, stream, address, irc_nick=None, username='', realname=None, is_service=False): 650 self.stream = stream 651 self.address = address[0] 652 self.port = str(address[1]) 653 self.from_localhost = True if address[0].split('.')[0] == '127' else False 654 self.irc_nick = irc_nick 655 self.irc_username = str(username) 656 self.irc_realname = realname 657 self.registered = False 658 self.password = '' 659 self.recv_pass = '' 660 self.oper = False 661 self.tls = False 662 self.bot = None 663 self.is_service = is_service 664 self.close_reason = '' 665 666 def get_irc_mask(self): 667 return '{}!{}@{}'.format(self.irc_nick, self.irc_username, self.address) 668 669 def get_channels(self, irc): 670 res = '' 671 for chan in irc.irc_channels.keys(): 672 if self.irc_nick in irc.irc_channels[chan]: 673 res += irc.get_irc_op(self.irc_nick, chan) + chan + ' ' 674 return res 675 676 def valid_nick(self, nick): 677 if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS: 678 for x in nick[1:]: 679 if x not in VALID_IRC_NICK_CHARS: 680 return False 681 return True 682 else: return False 683 684 def del_from_channels(self, irc, channels=None): 685 for chan in channels if channels else irc.irc_channels.keys(): 686 irc.irc_channels[chan].discard(self.irc_nick) 687 irc.irc_channels_ops[chan].discard(self.irc_nick) 688 irc.irc_channels_founder[chan].discard(self.irc_nick) 689 690 def pam_auth(self, nick, pam, pam_group, recv_pass): 691 if not pam: return False 692 693 # Check if user is in groups (main or others) 694 if pam_group: 695 import pwd 696 import grp 697 try: 698 user_group_id = pwd.getpwnam(nick).pw_gid 699 group_data = grp.getgrnam(pam_group) 700 pam_group_id = group_data.gr_gid 701 group_members = group_data.gr_mem 702 check_group = user_group_id == pam_group_id \ 703 or nick in group_members 704 except: 705 check_group = False 706 if not check_group: return False 707 708 # Check user authentication (via PAM) 709 import PAM 710 def pam_conv(auth, query_list, userData): 711 resp = [] 712 resp.append((recv_pass, 0)) 713 return resp 714 p = PAM.pam() 715 p.start('passwd') 716 p.set_item(PAM.PAM_USER, nick) 717 p.set_item(PAM.PAM_CONV, pam_conv) 718 try: 719 p.authenticate() 720 p.acct_mgmt() 721 except: 722 return False 723 else: 724 return True 725 726 def local_auth(self, nick, nicks, recv_pass, irc_pass, pam): 727 return ( not pam 728 and nick in nicks 729 and recv_pass == irc_pass 730 )