diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 2a768c7..7ad55fe 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -8,7 +8,6 @@ from pylinkirc.protocols.ircs2s_common import * from pylinkirc.classes import * FALLBACK_REALNAME = 'PyLink Relay Mirror Client' -COMMON_PREFIXMODES = [('h', 'halfop'), ('a', 'admin'), ('q', 'owner'), ('y', 'owner')] IRCV3_CAPABILITIES = {'multi-prefix', 'sasl'} class ClientbotWrapperProtocol(IRCCommonProtocol): @@ -25,7 +24,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): # This is just a fallback. Actual casemapping is fetched by handle_005() self.casemapping = 'ascii' - self.caps = {} + self._caps = {} self.ircv3_caps = set() self.ircv3_caps_available = {} @@ -42,9 +41,12 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): # are essentially all fatal errors for connections. self.handle_463 = self.handle_464 = self.handle_465 = self.handle_error + self._use_builtin_005_handling = True + def post_connect(self): """Initializes a connection to a server.""" # (Re)initialize counter-based pseudo UID generators + super().post_connect() self.uidgen = utils.PUIDGenerator('PUID') self.sidgen = utils.PUIDGenerator('ClientbotInternalSID') @@ -58,7 +60,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): # Clear states from last connect self.who_received.clear() self.kick_queue.clear() - self.caps.clear() + self._caps.clear() self.ircv3_caps.clear() self.ircv3_caps_available.clear() @@ -576,40 +578,6 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): # enumerate our uplink self.uplink = source - def handle_005(self, source, command, args): - """ - Handles 005 / RPL_ISUPPORT. - """ - self.caps.update(self.parse_isupport(args[1:-1])) - log.debug('(%s) handle_005: self.caps is %s', self.name, self.caps) - - if 'CHANMODES' in self.caps: - self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] = \ - self.caps['CHANMODES'].split(',') - log.debug('(%s) handle_005: cmodes: %s', self.name, self.cmodes) - - if 'USERMODES' in self.caps: - self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] = \ - self.caps['USERMODES'].split(',') - log.debug('(%s) handle_005: umodes: %s', self.name, self.umodes) - - self.casemapping = self.caps.get('CASEMAPPING', self.casemapping) - log.debug('(%s) handle_005: casemapping set to %s', self.name, self.casemapping) - - if 'PREFIX' in self.caps: - self.prefixmodes = prefixmodes = self.parse_isupport_prefixes(self.caps['PREFIX']) - log.debug('(%s) handle_005: prefix modes set to %s', self.name, self.prefixmodes) - - # Autodetect common prefix mode names. - for char, modename in COMMON_PREFIXMODES: - # Don't overwrite existing named mode definitions. - if char in self.prefixmodes and modename not in self.cmodes: - self.cmodes[modename] = char - log.debug('(%s) handle_005: autodetecting mode %s (%s) as %s', self.name, - char, self.prefixmodes[char], modename) - - self.connected.set() - def handle_376(self, source, command, args): """ Handles end of MOTD numerics, used to start things like autoperform. @@ -623,6 +591,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): if not self.has_eob: self.has_eob = True return {'parse_as': 'ENDBURST'} + self.connected.set() + handle_422 = handle_376 def handle_353(self, source, command, args): diff --git a/protocols/ircs2s_common.py b/protocols/ircs2s_common.py index 063c92b..55dede0 100644 --- a/protocols/ircs2s_common.py +++ b/protocols/ircs2s_common.py @@ -11,6 +11,18 @@ from pylinkirc.log import log from pylinkirc import utils class IRCCommonProtocol(IRCNetwork): + + COMMON_PREFIXMODES = [('h', 'halfop'), ('a', 'admin'), ('q', 'owner'), ('y', 'owner')] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._caps = {} + self._use_builtin_005_handling = False # Disabled by default for greater security + + def post_connect(self): + self._caps.clear() + def validate_server_conf(self): """Validates that the server block given contains the required keys.""" for k in self.conf_keys: @@ -163,6 +175,54 @@ class IRCCommonProtocol(IRCNetwork): """Handles ERROR messages - these mean that our uplink has disconnected us!""" raise ProtocolError('Received an ERROR, disconnecting!') + def handle_005(self, source, command, args): + """ + Handles 005 / RPL_ISUPPORT. This is used by at least Clientbot and ngIRCd (for server negotiation). + """ + # ngIRCd: + # <- :ngircd.midnight.local 005 pylink-devel.int NETWORK=ngircd-test :is my network name + # <- :ngircd.midnight.local 005 pylink-devel.int RFC2812 IRCD=ngIRCd CHARSET=UTF-8 CASEMAPPING=ascii PREFIX=(qaohv)~&@%+ CHANTYPES=#&+ CHANMODES=beI,k,l,imMnOPQRstVz CHANLIMIT=#&+:10 :are supported on this server + # <- :ngircd.midnight.local 005 pylink-devel.int CHANNELLEN=50 NICKLEN=21 TOPICLEN=490 AWAYLEN=127 KICKLEN=400 MODES=5 MAXLIST=beI:50 EXCEPTS=e INVEX=I PENALTY :are supported on this server + + # Regular clientbot, connecting to InspIRCd: + # <- :millennium.overdrivenetworks.com 005 ice AWAYLEN=200 CALLERID=g CASEMAPPING=rfc1459 CHANMODES=IXbegw,k,FJLfjl,ACKMNOPQRSTUcimnprstz CHANNELLEN=64 CHANTYPES=# CHARSET=ascii ELIST=MU ESILENCE EXCEPTS=e EXTBAN=,ACNOQRSTUcmprsuz FNC INVEX=I :are supported by this server + # <- :millennium.overdrivenetworks.com 005 ice KICKLEN=255 MAP MAXBANS=60 MAXCHANNELS=30 MAXPARA=32 MAXTARGETS=20 MODES=20 NAMESX NETWORK=OVERdrive-IRC NICKLEN=21 OVERRIDE PREFIX=(Yqaohv)*~&@%+ SILENCE=32 :are supported by this server + # <- :millennium.overdrivenetworks.com 005 ice SSL=[::]:6697 STARTTLS STATUSMSG=*~&@%+ TOPICLEN=307 UHNAMES USERIP VBANLIST WALLCHOPS WALLVOICES WATCH=32 :are supported by this server + + if not self._use_builtin_005_handling: + log.warning("(%s) Got spurious 005 message from %s: %r", self.name, source, args) + return + + self._caps.update(self.parse_isupport(args[1:-1])) + log.debug('(%s) handle_005: self._caps is %s', self.name, self._caps) + + if 'CHANMODES' in self._caps: + self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] = \ + self._caps['CHANMODES'].split(',') + log.debug('(%s) handle_005: cmodes: %s', self.name, self.cmodes) + + if 'USERMODES' in self._caps: + self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] = \ + self._caps['USERMODES'].split(',') + log.debug('(%s) handle_005: umodes: %s', self.name, self.umodes) + + self.casemapping = self._caps.get('CASEMAPPING', self.casemapping) + log.debug('(%s) handle_005: casemapping set to %s', self.name, self.casemapping) + + if 'PREFIX' in self._caps: + self.prefixmodes = prefixmodes = self.parse_isupport_prefixes(self._caps['PREFIX']) + log.debug('(%s) handle_005: prefix modes set to %s', self.name, self.prefixmodes) + + # Autodetect common prefix mode names. + for char, modename in self.COMMON_PREFIXMODES: + # Don't overwrite existing named mode definitions. + if char in self.prefixmodes and modename not in self.cmodes: + self.cmodes[modename] = char + log.debug('(%s) handle_005: autodetecting mode %s (%s) as %s', self.name, + char, self.prefixmodes[char], modename) + + self.connected.set() + def _send_with_prefix(self, source, msg, **kwargs): """Sends a RFC 459-style raw command from the given sender.""" self.send(':%s %s' % (self._expandPUID(source), msg), **kwargs) diff --git a/protocols/ngircd.py b/protocols/ngircd.py index 4af0dc9..ba90fb1 100644 --- a/protocols/ngircd.py +++ b/protocols/ngircd.py @@ -22,21 +22,25 @@ class NgIRCdProtocol(IRCS2SProtocol): super().__init__(irc) self.conf_keys -= {'sid', 'sidrange'} - self.casemapping = 'rfc1459' + self.casemapping = 'ascii' # This is the default; it's actually set on server negotiation # Track whether we've received end-of-burst from the uplink. self.has_eob = False self.uidgen = utils.PUIDGenerator("PUID") + self._caps = {} + self._use_builtin_005_handling = True ### Commands def post_connect(self): - self.send('PASS %s 0210-IRC+ PyLink|%s:LMoX' % (self.serverdata['sendpass'], __version__)) + self.send('PASS %s 0210-IRC+ PyLink|%s:HLMoX' % (self.serverdata['sendpass'], __version__)) self.send("SERVER %s 1 :%s" % (self.serverdata['hostname'], self.serverdata.get('serverdesc') or conf.conf['pylink']['serverdesc'])); self.sid = self.serverdata['hostname'] + self._caps.clear() + def spawn_client(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator', manipulatable=False):