async def line_read(self, line: Line): print(f"{self.name} < {line.format()}") if line.command == "001": await self.send(build("JOIN", [",".join(CHANS)])) elif (line.command == "JOIN" and not self.is_me(line.hostmask.nickname)): user = self.users[self.casefold(line.hostmask.nickname)] fingerprint = f"{line.hostmask.hostname}#{user.realname}" for pattern, mask in PATTERNS: match = re.search(pattern, fingerprint) if match: ip = match.group("ip") mask_f = mask.format(IP=ip) reason = await _match(ip) if reason is not None: await self._act(ACT_SOFT, line, mask_f, ip, reason) if not ACC or not user.account: await self._act(ACT_HARD, line, mask_f, ip, reason) elif (line.command == "INVITE" and self.is_me(line.params[0])): userhost = f"{line.hostmask.username}@{line.hostmask.hostname}" for admin_mask in ADMINS: if admin_mask.match(userhost): await self.send(build("JOIN", [line.params[1]])) break
async def _oper_up(self, oper_name: str, oper_file: str, oper_pass: str): try: challenge = Challenge(keyfile=oper_file, password=oper_pass) except Exception: traceback.print_exc() else: await self.send(build("CHALLENGE", [oper_name])) challenge_text = Response(RPL_RSACHALLENGE2, [SELF, ANY]) challenge_stop = Response(RPL_ENDOFRSACHALLENGE2, [SELF]) #:niven.freenode.net 740 sandcat :foobarbazmeow #:niven.freenode.net 741 sandcat :End of CHALLENGE while True: challenge_line = await self.wait_for({ challenge_text, challenge_stop }) if challenge_line.command == RPL_RSACHALLENGE2: challenge.push(challenge_line.params[1]) else: retort = challenge.finalise() await self.send(build("CHALLENGE", [f"+{retort}"])) break
async def on_mode(self, line): if self.nickname in line.source: return if line.params in [["#chaos", "+q", self.nickname], ["#chaos", "+qo", self.nickname, self.nickname]]: await self.send(build("MODE", self.tomode)) return unmo = [] for i in self.channels['#chaos'].users: #set(line.params[2:]): nick = i.lower() if line.params[0] in self.channels and nick in self.channels[ line.params[0]].users: us = await checkUser(self, nick) if us: unmo.append(us) if len(unmo) > 0: if 'a' in line.params[ 1]: # meh, only take away protected if protected was set self.tomode = [ "#chaos", "+q-c**t" + "q" * len(unmo) + "o" * len(unmo) + "a" * len(unmo), "xfnw" ] + unmo + unmo + unmo else: self.tomode = [ "#chaos", "+q-c**t" + "q" * len(unmo) + "o" * len(unmo), "xfnw" ] + unmo + unmo await self.send(build("MODE", self.tomode))
async def line_read(self, line: Line): if line.command == RPL_WELCOME: oper_name, oper_file, oper_pass = self._config.oper await self._oper_up(oper_name, oper_file, oper_pass) elif (line.command == "PRIVMSG" and self.is_me(line.params[0])): nickname = line.hostmask.nickname command, *args = line.params[1].lower().split() if command == "grantme": opername = await self._oper_name(nickname) if opername is not None: if not args: await self.send(build("NOTICE", [nickname, "give me an argument then"])) elif not args[0] in self._config.privsets: await self.send(build("NOTICE", [nickname, f"dunno what '{args[0]}' means"])) else: privset = args[0] await self.send(build("GRANT", [nickname, privset])) await self.send(build("NOTICE", [nickname, f"good luck with {privset} mate"])) else: await self.send(build("NOTICE", [nickname, "who are you though"]))
async def line_read(self, line: Line): global CONFIG print(f"{self.name} < {line.format()}") if line.command == "001": chans = list(CONFIG.channels.keys()) await self.send(build("JOIN", [",".join(chans)])) elif (line.command == "JOIN" and not self.is_me(line.hostmask.nickname)): nick = self.casefold(line.hostmask.nickname) chan = self.casefold(line.params[0]) if not nick in self._whox: self._whox[nick] = [] self._whox[nick].append(chan) await self.send(build("WHO", [nick, "%int,111"])) elif (line.command == RPL_WHOSPCRPL and line.params[1] == "111"): nick = self.casefold(line.params[3]) print("whox", nick) if nick in self.users: user = self.users[nick] chan = self.channels[self._whox[nick][0]] host = line.params[2] if host == "255.255.255.255": host = user.hostname await self._scan(user, chan, host) elif line.command == RPL_ENDOFWHO: nick = self.casefold(line.params[1]) if nick in self._whox: print("popping", nick, self._whox[nick].pop(0)) if not self._whox[nick]: print("removing", nick) del self._whox[nick] elif (line.command == "JOIN" and self.is_me(line.hostmask.nickname)): await self.send(build("MODE", [line.params[0], "+bq"])) elif (line.command == "INVITE" and self.is_me(line.params[0])): userhost = f"{line.hostmask.username}@{line.hostmask.hostname}" for admin_mask in CONFIG.admins: if admin_mask.match(userhost): await self.send(build("JOIN", [line.params[1]])) break elif (line.command == "PRIVMSG" and self.is_me(line.params[0]) and line.params[1] == "rehash"): userhost = f"{line.hostmask.username}@{line.hostmask.hostname}" for admin_mask in CONFIG.admins: if admin_mask.match(userhost): CONFIG = load_config(CONFIG_PATH) print("rehashed") break
async def handshake(self): try: await self.on_ls(self.server.available_caps) except HandshakeCancel: return else: await self.server.send(build("CAP", ["END"]))
async def _read_lines(self): while True: async with self._read_lguard: pass if not self._process_queue: async with self._read_lwork: read_aw = self._read_line(PING_TIMEOUT) dones, notdones = await asyncio.wait( [read_aw, self._wait_for.wait()], return_when=asyncio.FIRST_COMPLETED) self._wait_for.clear() for done in dones: if isinstance(done.result(), Line): self._ping_sent = False line = done.result() emit = self.parse_tokens(line) self._process_queue.append((line, emit)) elif done.result() is None: if not self._ping_sent: await self.send(build("PING", ["hello"])) self._ping_sent = True else: await self.disconnect() raise ServerDisconnectedException() for notdone in notdones: notdone.cancel() else: line, emit = self._process_queue.popleft() await self._on_read(line, emit)
async def _next_who(self): if self._pending_who: chan = self._pending_who[0] if self.isupport.whox: await self.send(self.prepare_whox(chan)) else: await self.send(build("WHO", [chan]))
async def _batch_joins(self, channels: List[str], batch_n: int = 10): #TODO: do as many JOINs in one line as we can fit #TODO: channel keys for i in range(0, len(channels), batch_n): batch = channels[i:i + batch_n] await self.send(build("JOIN", [",".join(batch)]))
async def handshake(self): nickname = self.params.nickname username = self.params.username or nickname realname = self.params.realname or nickname alt_nicks = self.params.alt_nicknames if not alt_nicks: alt_nicks = [nickname + "_" * i for i in range(1, 4)] self._alt_nicks = alt_nicks # these must remain non-awaited; reading hasn't started yet if not self.params.password is None: self.send(build("PASS", [self.params.password])) self.send(build("CAP", ["LS", "302"])) self.send(build("NICK", [nickname])) self.send(build("USER", [username, "0", "*", realname]))
async def _ban_list( self, chan: str, depth=0) -> Optional[List[Tuple[(List[str], str, int)]]]: await self.send(build("MODE", [chan, "+b"])) masks: List[Tuple[List[str], str, int]] = [] while True: line = await self.wait_for( Responses([RPL_BANLIST, RPL_ENDOFBANLIST, ERR_NOSUCHCHANNEL], [SELF, Folded(chan)])) if line.command == ERR_NOSUCHCHANNEL: return None elif line.command == RPL_ENDOFBANLIST: break else: mask = line.params[2] set_by = line.params[3] set_at = int(line.params[4]) masks.append(([mask], set_by, set_at)) if depth == 0: for (mask, ), _, _ in list(masks): if mask.startswith("$j:"): nextchan = mask.split(":", 1)[1].split("$", 1)[0] nextchan_masks = await self._ban_list(nextchan, depth + 1) if nextchan_masks is not None: for nextmask, set_by, set_at in nextchan_masks: masks.append((nextmask + [mask], set_by, set_at)) return masks
async def on_ls(self, tokens: Dict[str, str]): await self._sts(tokens) caps = list(self.server.desired_caps)+CAPS if (not self.server.params.sasl is None and not CAP_SASL in caps): caps.append(CAP_SASL) matched = (c.available(tokens) for c in caps) cap_names = [name for name in matched if not name is None] if cap_names: await self.server.send(build("CAP", ["REQ", " ".join(cap_names)])) while cap_names: line = await self.server.wait_for({ Response("CAP", [ANY, "ACK"]), Response("CAP", [ANY, "NAK"]) }) current_caps = line.params[2].split(" ") for cap in current_caps: if cap in cap_names: cap_names.remove(cap) if CAP_RESUME.available(current_caps): await self.resume_token() if (self.server.cap_agreed(CAP_SASL) and not self.server.params.sasl is None): await self.server.sasl_auth(self.server.params.sasl)
def send_whois(self, target: str, remote: bool = False) -> Awaitable[Optional[Whois]]: args = [target] if remote: args.append(target) fut = self.send(build("WHOIS", args)) async def _assure() -> Optional[Whois]: folded = self.casefold(target) params = [ANY, Folded(folded)] obj = Whois() while True: line = await self.wait_for( Responses([ ERR_NOSUCHNICK, ERR_NOSUCHSERVER, RPL_WHOISUSER, RPL_WHOISSERVER, RPL_WHOISOPERATOR, RPL_WHOISIDLE, RPL_WHOISCHANNELS, RPL_WHOISHOST, RPL_WHOISACCOUNT, RPL_WHOISSECURE, RPL_ENDOFWHOIS ], params), fut) if line.command in [ERR_NOSUCHNICK, ERR_NOSUCHSERVER]: return None elif line.command == RPL_WHOISUSER: nick, user, host, _, real = line.params[1:] obj.nickname = nick obj.username = user obj.hostname = host obj.realname = real elif line.command == RPL_WHOISIDLE: idle, signon, _ = line.params[2:] obj.idle = int(idle) obj.signon = int(signon) elif line.command == RPL_WHOISACCOUNT: obj.account = line.params[2] elif line.command == RPL_WHOISCHANNELS: channels = list(filter(bool, line.params[2].split(" "))) if obj.channels is None: obj.channels = [] for i, channel in enumerate(channels): symbols = "" while channel[0] in self.isupport.prefix.prefixes: symbols += channel[0] channel = channel[1:] channel_user = ChannelUser( Name(obj.nickname, folded), Name(channel, self.casefold(channel))) for symbol in symbols: mode = self.isupport.prefix.from_prefix(symbol) if mode is not None: channel_user.modes.append(mode) obj.channels.append(channel_user) elif line.command == RPL_ENDOFWHOIS: return obj return MaybeAwait(_assure)
def send_joins(self, names: List[str], keys: List[str] = []) -> Awaitable[List[Channel]]: folded_names = [self.casefold(name) for name in names] if not keys: fut = self.send(build("JOIN", [",".join(names)])) else: fut = self.send(build("JOIN", [",".join(names)] + keys)) async def _assure(): channels: List[Channel] = [] while folded_names: line = await self.wait_for( { Response(RPL_CHANNELMODEIS, [ANY, ANY]), Responses(JOIN_ERR_FIRST, [ANY, ANY]), Response(ERR_USERONCHANNEL, [ANY, SELF, ANY]), Response(ERR_LINKCHANNEL, [ANY, ANY, ANY]) }, fut) chan: Optional[str] = None if line.command == RPL_CHANNELMODEIS: chan = line.params[1] elif line.command in JOIN_ERR_FIRST: chan = line.params[1] elif line.command == ERR_USERONCHANNEL: chan = line.params[2] elif line.command == ERR_LINKCHANNEL: #XXX i dont like this chan = line.params[2] await self.wait_for( Response(RPL_CHANNELMODEIS, [ANY, Folded(chan)])) channels.append(self.channels[self.casefold(chan)]) continue if chan is not None: folded = self.casefold(chan) if folded in folded_names: folded_names.remove(folded) channels.append(self.channels[folded]) return channels return MaybeAwait(_assure)
def ircverify(username, verif_code): # define the variables d = irctokens.StatefulDecoder() e = irctokens.StatefulEncoder() s = socket.socket() #connecting to the server s.connect(("127.0.0.1", 6667)) #defining the send function with proper formatting def _send(line): print(f"> {line.format()}") e.push(line) while e.pending(): e.pop(s.send(e.pending())) # registering the connection to the server _send(irctokens.build("USER", [username, "0", "*", username])) _send(irctokens.build("NICK", [username])) # go through the cases while True: lines = d.push(s.recv(1024)) if lines == None: #if nothing is received from server return "server error" break for line in lines: print(f"< {line.format()}") if line.command == "433": # if nickname already in use return "433" elif line.command == "005": # when 005 is received pass the nickserv register command command _send( irctokens.build( "PRIVMSG", ["NickServ", f"VERIFY {username} {verif_code}"])) if line.command == "NOTICE" and line.params == [ username, "Account created" ]: # if Services respond with appropriate notice NOTICE _send(irctokens.build("QUIT")) return "success"
async def _send_auth_text(self, text: str): n = AUTH_BYTE_MAX chunks = [text[i:i+n] for i in range(0, len(text), n)] if len(chunks[-1]) == 400: chunks.append("+") for chunk in chunks: await self.server.send(build("AUTHENTICATE", [chunk]))
async def line_read(self, line: Line): print(f"{self.name} < {line.format()}") if line.command == "001": await self.send(build("JOIN", [",".join(CHANS)])) elif (line.command == "JOIN" and not self.is_me(line.hostmask.nickname)): nick = line.hostmask.nickname await self.send(build("WHO", [nick, "%int,111"])) who_line = await self.wait_for( Response(RPL_WHOSPCRPL, [ANY, "111", ANY, Folded(nick)])) host = who_line.params[2] if (host == "255.255.255.255" and line.hostmask.hostname is not None): host = line.hostmask.hostname user = self.users[self.casefold(line.hostmask.nickname)] fingerprint = f"{host}#{user.realname}" for pattern, mask_templ in PATTERNS: match = re.search(pattern, fingerprint) if match: chan = self.channels[self.casefold(line.params[0])] ip = match.group("ip") mask = mask_templ.format(IP=ip) list_modes = (chan.list_modes.get("b", []) + chan.list_modes.get("q", [])) if not mask in list_modes: reason = await _match(ip) if reason is not None: await self._act(False, line, mask, ip, reason) if not ACC or not user.account: await self._act(True, line, mask, ip, reason) else: print("already caught") elif (line.command == "JOIN" and self.is_me(line.hostmask.nickname)): await self.send(build("MODE", [line.params[0], "+bq"])) elif (line.command == "INVITE" and self.is_me(line.params[0])): userhost = f"{line.hostmask.username}@{line.hostmask.hostname}" for admin_mask in ADMINS: if admin_mask.match(userhost): await self.send(build("JOIN", [line.params[1]])) break
def send_part(self, name: str): fut = self.send(build("PART", [name])) async def _assure(): line = await self.wait_for( Response("PART", [Folded(name)], source=MASK_SELF), fut) return return MaybeAwait(_assure)
async def line_read(self, line: Line): print(f"{self.name} < {line.format()}") if line.command == "001": self.lc = "#" * (self.name == "freenode") + logchan print(f"connected to {self.name}") await self.send(build("JOIN", [self.lc])) if line.command == "PRIVMSG": if line.tags and 'batch' in line.tags and line.tags['batch'] == '1': return if line.params[1] == '!reload': importlib.reload(filts) await self.linelog('reloaded') #sifakis if line.params[1][ 0: 9] == "Sifakis: " and line.tags and 'account' in line.tags and line.tags[ 'account'] == 'lickthecheese': await self.linelog(str(await aexec(self, line.params[1][9:]))) if line.command == "INVITE": await self.send(build("JOIN", [line.params[1]])) asyncio.create_task(filts.line_read(self, line))
async def external(self) -> SASLResult: await self.server.send(build("AUTHENTICATE", ["EXTERNAL"])) line = await self.server.wait_for({ AUTHENTICATE_ANY, NUMERICS_INITIAL }) if line.command == "907": # we've done SASL already. cleanly abort return SASLResult.ALREADY elif line.command == "908": available = line.params[1].split(",") raise SASLUnknownMechanismError( "Server does not support SASL EXTERNAL " f"(it supports {available}") elif line.command == "AUTHENTICATE" and line.params[0] == "+": await self.server.send(build("AUTHENTICATE", ["+"])) line = await self.server.wait_for(NUMERICS_LAST) if line.command == "903": return SASLResult.SUCCESS return SASLResult.FAILURE
async def line_read(self, line: Line): if line.command == "001": chan_bans = await self._ban_list(CHAN) if chan_bans is None: sys.stderr.write(f"{CHAN} not found\n") sys.exit(1) accounts: Dict[str, List[str]] = {} for mask_tree, set_by, set_at in chan_bans: mask = mask_tree[0] if mask.startswith("$a:"): if not mask in accounts: accounts[mask] = [] accounts[mask].append((mask_tree[1:] or [CHAN])[0]) states: List[Tuple[str, bool]] = [] for mask in accounts.keys(): account = mask.split(":", 1)[1].split("$", 1)[0] account = self.casefold(account) await self.send(build("NS", ["INFO", account])) line = await self.wait_for({RESP_REG, RESP_UNREG}) if line.params[1].startswith("Information on "): if not NONEXISTENT_ONLY: states.append((mask, True)) await self.wait_for(RESP_END) else: states.append((mask, False)) states_where: Dict[str, Any] = {} for mask, registered in states: state: Dict[Any, Any] = {} sources = accounts[mask] if len(sources) == 1: state["source"] = sources[0] else: state["source"] = sources if not NONEXISTENT_ONLY: state["registered"] = registered states_where[mask] = state with open(FILE, "w") as outfile: outfile.write(yaml.dump(states_where, sort_keys=False)) outfile.write("\n") print(f"! written to {FILE}") sys.exit()
def send_message(self, target: str, message: str) -> Awaitable[Optional[str]]: fut = self.send(build("PRIVMSG", [target, message])) async def _assure(): line = await self.wait_for( Response("PRIVMSG", [Folded(target), ANY], source=MASK_SELF), fut) if line.command == "PRIVMSG": return line.params[1] else: return None return MaybeAwait(_assure)
async def line_read(self, line): # line tags command params source if line.command == "PING" or line.command == "001": self.jointh = [] self.tomode = ["#chaos", "-c**t"] self.attack = ['anton', 'julian'] if line.command == "482": await self.send(build("cs", ["owner", "#chaos"])) if line.command == "JOIN": await on_join(self, line) if line.command == "MODE": await on_mode(self, line) if line.command == "KICK": await on_kick(self, line)
async def _cs_op(self, channel: Channel) -> bool: await self.send(build("PRIVMSG", ["ChanServ", f"OP {channel.name}"])) try: await self.wait_for(Response( "MODE", [Folded(channel.name), "+o", SELF], source=CHANSERV, ), timeout=2) except asyncio.TimeoutError: return False else: return True
async def _cmodes(self, chan: str) -> Optional[str]: chan_fold = self.casefold(chan) if chan_fold in self.channels: channel = self.channels[chan_fold] return "".join(channel.modes.keys()) await self.send(build("MODE", [chan])) line = await self.wait_for(Responses( [RPL_CHANNELMODEIS, ERR_NOSUCHCHANNEL], [SELF, Folded(chan)] )) if line.command == RPL_CHANNELMODEIS: return line.params[2].replace("+", "") return None
async def _ban_list(self, chan: str, modes: str, depth=0 ) -> Optional[List[Tuple[Type, List[str], str, int]]]: await self.send(build("MODE", [chan, f"+{modes}"])) ends = len(modes) masks: List[Tuple[Type, List[str], str, int]] = [] while True: line = await self.wait_for(Responses([ RPL_BANLIST, RPL_QUIETLIST, RPL_ENDOFBANLIST, RPL_ENDOFQUIETLIST, ERR_NOSUCHCHANNEL ], [SELF, Folded(chan)])) if line.command == ERR_NOSUCHCHANNEL: return None elif line.command in [RPL_ENDOFBANLIST, RPL_ENDOFQUIETLIST]: ends -= 1 if ends == 0: break else: offset = 0 type = Type.BAN if line.command == RPL_QUIETLIST: offset += 1 type = Type.QUIET mask = line.params[offset+2] set_by = line.params[offset+3] set_at = int(line.params[offset+4]) masks.append((type, [mask], set_by, set_at)) if depth == 0: for type, (mask,), _, _ in list(masks): if mask.startswith("$j:"): nextchan = mask.split(":", 1)[1].split("$", 1)[0] nextchan_masks = await self._ban_list( nextchan, "b", depth + 1 ) if nextchan_masks is not None: for _, nextmask, set_by, set_at in nextchan_masks: masks.append( (type, nextmask+[mask], set_by, set_at) ) return masks
async def _act(self, user: User, chan: Channel, mask: str, ip: str, reason: str): act_sets = CONFIG.act_defaults act_sets_c = CONFIG.channels.get(chan.name_lower, None) if act_sets_c is not None: act_sets = act_sets_c act_cmds = [CONFIG.act_sets[a] for a in act_sets] acts = list(itertools.chain(*act_cmds)) # put False (non-op) acts first acts.sort(key=lambda x: x[0]) data = { "CHAN": chan.name, "NICK": user.nickname, "USER": user.username, "HOST": user.hostname, "MASK": mask, "IP": ip, "REASON": reason } remove_op = False last = len(acts) - 1 for i, (need_op, action_s) in enumerate(acts): if need_op: if not "o" in chan.users[self.nickname_lower].modes: got_op = await self._cs_op(chan) if not got_op: break else: remove_op = True action = tokenise(action_s.format(**data)) if i == last and remove_op: target = self.casefold(action.params[0]) if (action.command == "MODE" and chan.name_lower == target and len(action.params[2:]) < self.isupport.modes): action.params[1] += "-o" action.params.append(self.nickname) else: await self.send(action) action = build("MODE", [chan.name, "-o", self.nickname]) await self.send(action)
def send_nick(self, new_nick: str) -> Awaitable[bool]: fut = self.send(build("NICK", [new_nick])) async def _assure() -> bool: line = await self.wait_for( { Response("NICK", [Folded(new_nick)], source=MASK_SELF), Responses([ ERR_BANNICKCHANGE, ERR_NICKTOOFAST, ERR_CANTCHANGENICK ], [ANY]), Responses([ ERR_NICKNAMEINUSE, ERR_ERRONEUSNICKNAME, ERR_UNAVAILRESOURCE ], [ANY, Folded(new_nick)]) }, fut) return line.command == "NICK" return MaybeAwait(_assure)
async def resume_token(self): line = await self.server.wait_for(Response("RESUME", ["TOKEN", ANY])) token = line.params[1] address, port = self.server.server_address() resume_policy = ResumePolicy(address, token) previous_policy = self.server.params.resume self.server.params.resume = resume_policy await self.server.resume_policy(resume_policy) if previous_policy is not None and not self.server.registered: await self.server.send(build("RESUME", [previous_policy.token])) line = await self.server.wait_for({ Response("RESUME", ["SUCCESS"]), Response("FAIL", ["RESUME"]) }) if line.command == "RESUME": raise HandshakeCancel()
async def _assure_op(self, channel: Channel) -> bool: channel_self = channel.users[self.nickname_lower] if not "o" in channel_self.modes: await self.send( build("PRIVMSG", ["ChanServ", f"OP {channel.name}"])) try: await self.wait_for(Response( "MODE", [Folded(channel.name), "+o", SELF], source=CHANSERV, ), timeout=5) except asyncio.TimeoutError: return False else: return True else: return False