patch f6adfec3b3a67fe89d7f7528ea918372f8070ace Author: E. Bosch Date: Wed Jan 27 20:51:33 CET 2021 * irc: Add registration handling of new connections Improve replies, error responses, motd, nick validation, etc. diff -rN -u old-irgramd/include.py new-irgramd/include.py --- old-irgramd/include.py 1970-01-01 01:00:00.000000000 +0100 +++ new-irgramd/include.py 2024-10-23 04:30:50.559520538 +0200 @@ -0,0 +1,6 @@ + +# Constants + +VERSION = '0.1' +NICK_MAX_LENGTH = 20 +CHAN_MAX_LENGHT = 50 diff -rN -u old-irgramd/irc.py new-irgramd/irc.py --- old-irgramd/irc.py 2024-10-23 04:30:50.559520538 +0200 +++ new-irgramd/irc.py 2024-10-23 04:30:50.559520538 +0200 @@ -3,14 +3,22 @@ import logging import re import socket +import string +import time import tornado.httpclient import tornado.ioloop # Local modules -from utils import chunks +from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH from irc_replies import irc_codes +from utils import chunks + +# Constants + +VALID_IRC_NICK_FIRST_CHARS = string.ascii_letters + '[]\`_^{|}' +VALID_IRC_NICK_CHARS = VALID_IRC_NICK_FIRST_CHARS + string.digits + '-' # IRC Regular Expressions @@ -81,6 +89,7 @@ ) self.iid_to_tid = {} self.irc_channels = collections.defaultdict(set) + self.start_time = time.strftime('%a %d %b %Y %H:%M:%S %z') def get_irc_user_mask(self, nick): return '{}!{}@{}'.format(nick, nick, self.hostname) @@ -90,37 +99,51 @@ command = command + '\r\n' user.stream.write(command.encode()) + # IRC handlers + + async def handle_irc_pass(self, user, password): + self.logger.debug('Handling PASS: %s %s', password) + + if user.registered: + await self.reply(user, 'ERR_ALREADYREGISTRED') + else: + user.recv_pass = password + async def handle_irc_nick(self, user, nick): self.logger.debug('Handling NICK: %s', nick) - if user.irc_nick in self.iid_to_tid: - tid = self.iid_to_tid[user.irc_nick] - self.tg.tid_to_iid[tid] = nick - self.iid_to_tid[nick] = tid - - user.irc_nick = nick + if not self.valid_nick(nick): + await self.reply(user, 'ERR_ERRONEUSNICKNAME') + elif nick in [x.irc_nick for x in self.users if x is not user]: + await self.reply(user, 'ERR_NICKNAMEINUSE') + elif user.password == user.recv_pass: + user.irc_nick = nick + + if user.irc_nick in self.iid_to_tid: + tid = self.iid_to_tid[user.irc_nick] + self.tg.tid_to_iid[tid] = nick + self.iid_to_tid[nick] = tid + + if not user.registered and user.irc_username: + user.registered = True + await self.send_greeting(user) + else: + await self.reply(user, 'ERR_PASSWDMISMATCH') async def handle_irc_user(self, user, username, realname): self.logger.debug('Handling USER: %s, %s', username, realname) user.irc_username = username user.irc_realname = realname - - await self.send_irc_command(user, ':{} 001 {} :{}'.format( - self.hostname, user.irc_nick, 'Welcome to irgramd' - )) - await self.send_irc_command(user, ':{} 376 {} :{}'.format( - self.hostname, user.irc_nick, 'End of MOTD command' - )) + if user.irc_nick: + user.registered = True + await self.send_greeting(user) async def handle_irc_join(self, user, channel): self.logger.debug('Handling JOIN: %s', channel) await self.join_irc_channel(user, channel, True) - async def handle_irc_pass(self, user, app_id, app_hash): - self.logger.debug('Handling PASS: %s %s', app_id, app_hash) - async def handle_irc_ping(self, user, payload): self.logger.debug('Handling PING: %s', payload) await self.send_irc_command(user, ':{} PONG {} :{}'.format( @@ -144,6 +167,40 @@ self.hostname, num, user.irc_nick, tail )) + async def reply_param(self, user, num, rest): + await self.send_irc_command(user, ':{} {} {} {}'.format( + self.hostname, num, user.irc_nick, rest + )) + + async def send_greeting(self, user): + num, rest = irc_codes['RPL_WELCOME'] + await self.reply_param(user, num, rest.format(user.irc_nick)) + num, rest = irc_codes['RPL_YOURHOST'] + await self.reply_param(user, num, rest.format(self.hostname, VERSION)) + num, rest = irc_codes['RPL_CREATED'] + await self.reply_param(user, num, rest.format(self.start_time)) + num, rest = irc_codes['RPL_MYINFO'] + await self.reply_param(user, num, rest.format(self.hostname, VERSION)) + num, rest = irc_codes['RPL_ISUPPORT'] + await self.reply_param(user, num, rest.format(str(CHAN_MAX_LENGHT), str(NICK_MAX_LENGTH))) + await self.send_motd(user) + + async def send_motd(self, user): + num, rest = irc_codes['RPL_MOTDSTART'] + await self.reply_param(user, num, rest.format(self.hostname)) + num, rest = irc_codes['RPL_MOTD'] + await self.reply_param(user, num, rest.format('Welcome to the irgramd server')) + await self.reply_param(user, num, rest.format('')) + await self.reply_param(user, num, rest.format('This is not a normal IRC server, it\'s a gateway that')) + await self.reply_param(user, num, rest.format('allows connecting from an IRC client (the program that')) + await self.reply_param(user, num, rest.format('you are [probably] using right now) to the Telegram instant')) + await self.reply_param(user, num, rest.format('messaging network as a regular user account (not bot)')) + await self.reply_param(user, num, rest.format('')) + await self.reply_param(user, num, rest.format('irgramd is an open source project that you can find on')) + await self.reply_param(user, num, rest.format('git repository: https://github.com/prsai/irgramd')) + await self.reply_param(user, num, rest.format('darcs repository: https://src.presi.org/darcs/irgramd')) + await self.reply(user, 'RPL_ENDOFMOTD') + async def join_irc_channel(self, user, channel, full_join=False): self.irc_channels[channel].add(user.irc_nick) @@ -177,6 +234,14 @@ self.get_irc_user_mask(user.irc_nick), channel )) + def valid_nick(self, nick): + if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS: + for x in nick[1:]: + if x not in VALID_IRC_NICK_CHARS: + return 0 + return 1 + else: return 0 + class IRCUser(object): def __init__(self, stream, address): self.stream = stream @@ -185,4 +250,5 @@ self.irc_username = None self.irc_realname = None self.registered = True + self.password = '' self.recv_pass = '' diff -rN -u old-irgramd/irc_replies.py new-irgramd/irc_replies.py --- old-irgramd/irc_replies.py 2024-10-23 04:30:50.559520538 +0200 +++ new-irgramd/irc_replies.py 2024-10-23 04:30:50.559520538 +0200 @@ -1,13 +1,13 @@ irc_codes = \ { - 'RPL_WELCOME': ('001', 'Welcome to the irgramd gateway, {}'), - 'RPL_YOURHOST': ('002', 'Your host is {}, running version irgramd-{}'), - 'RPL_CREATED': ('003', 'This server was created {}'), + 'RPL_WELCOME': ('001', ':Welcome to the irgramd gateway, {}'), + 'RPL_YOURHOST': ('002', ':Your host is {}, running version irgramd-{}'), + 'RPL_CREATED': ('003', ':This server was created {}'), 'RPL_MYINFO': ('004', '{} irgramd-{} o nt'), - 'RPL_ISUPPORT': ('005', 'CASEMAPPING=ascii CHANLIMIT=#&+: CHANTYPES=&#+ CHANMODES=,,,nt CHANNELLEN={} NICKLEN={} SAFELIST'), - 'RPL_MOTDSTART': ('375', '- {} Message of the day - '), - 'RPL_MOTD': ('372', '{}'), + 'RPL_ISUPPORT': ('005', 'CASEMAPPING=ascii CHANLIMIT=#&+: CHANTYPES=&#+ CHANMODES=,,,nt CHANNELLEN={} NICKLEN={} SAFELIST :are supported by this server'), + 'RPL_MOTDSTART': ('375', ':- {} Message of the day - '), + 'RPL_MOTD': ('372', ':- {}'), 'RPL_ENDOFMOTD': (376, 'End of MOTD command'), 'ERR_NOSUCHNICK': ('401', 'No such nick'), 'ERR_NOSUCHSERVER': ('402', 'No such server'), diff -rN -u old-irgramd/telegram.py new-irgramd/telegram.py --- old-irgramd/telegram.py 2024-10-23 04:30:50.559520538 +0200 +++ new-irgramd/telegram.py 2024-10-23 04:30:50.563520531 +0200 @@ -3,6 +3,10 @@ import os import telethon +# Local modules + +from include import CHAN_MAX_LENGHT, NICK_MAX_LENGTH + # Configuration # GET API_ID and API_HASH from https://my.telegram.org/apps @@ -11,7 +15,6 @@ TELEGRAM_API_ID = TELEGRAM_API_HASH = '' -NICK_MAX_LENGTH = 20 # Telegram