'High-level IRC protocol / server connection management.'
# built-ins
from abc import ABC, abstractmethod
+from base64 import b64encode
from dataclasses import dataclass
from getpass import getuser
from threading import Thread
self.clear()
self.challenge('LS', '302')
return []
- if self._challenge_met('END'):
+ if self._challenged('END'):
return [f'ignoring post-END CAP message not NEW, DEL: {params}']
match params[0]:
case 'LS' | 'LIST':
self._dict[cap_name].enabled = params[0] == 'ACK'
if self._challenge_met('LIST'):
self.challenge('END')
- self._challenge_set('END', done=True)
return (['server capabilities (enabled: "+"):']
+ [cap.str_for_log(cap_name)
for cap_name, cap in self._dict.items()])
self._send(IrcMessage(verb='CAP', params=params))
self._challenge_set(challenge_key)
+ @property
+ def could_sasl_plain(self) -> bool:
+ 'Whether opportunity for some SASL AUTHENTICATE PLAIN attempt.'
+ return (self._challenged('END')
+ and 'sasl' in self._dict
+ and 'PLAIN' in self._dict['sasl'].data.split(','))
+
def _challenge_met(self, step: str) -> bool:
return self._challenges.get(step, False)
hostname: str
nickname: str
realname: str
+ password: str
class Client(ABC, ClientQueueMixin):
case 'CAP':
for to_log in self._caps.process_msg(msg.params[1:]):
self.log.add(to_log)
+ if self._caps.could_sasl_plain and self.conn_setup.password:
+ # NB: spec recommends to AUTHENTICATE during, rather than
+ # after, CAPS negotation; opting for simplicity now instead
+ self.send(IrcMessage('AUTHENTICATE', ('PLAIN',)))
+ case 'AUTHENTICATE':
+ if msg.params == ('+',):
+ auth = b64encode((self.conn_setup.nickname + '\0' +
+ self.conn_setup.nickname + '\0' +
+ self.conn_setup.password
+ ).encode('utf-8')).decode('utf-8')
+ self.send(IrcMessage('AUTHENTICATE', (auth,)))
+ case '904':
+ self.log.alert('SASL authentication failed')
@dataclass
client_wins[0].prompt.prefix_copy_to(win.prompt)
return win
- def cmd__connect(self, hostname: str, nickname: str, realname: str
+ def cmd__connect(self, hostname: str, nickname_and_pw: str, realname: str
) -> None:
'Create Client and pass it via NewClientEvent.'
+ split = nickname_and_pw.split(':', maxsplit=1)
+ nickname = split[0]
+ pw = split[1] if len(split) > 1 else ''
self._put(NewClientEvent(
_ClientKnowingTui(
q_out=self.q_out,
- conn_setup=IrcConnSetup(hostname=hostname, nickname=nickname,
- realname=realname))))
+ conn_setup=IrcConnSetup(hostname, nickname, realname, pw))))
@dataclass