import socket import time import sys import re import string import os import base64 import traceback import logging from state import State from message import Message from server import VERSION from funcs import * from commands import BROADCAST from commands import DIRECT class Client(object): __linesep_regexp = re.compile(r"\r?\n") # The RFC limit for nicknames is 9 characters, but what the heck. __valid_nickname_regexp = re.compile( r"^[][\`_^{|}A-Za-z][][\`_^{|}A-Za-z0-9-]{0,50}$") __valid_channelname_regexp = re.compile( r"^[+!][^\x00\x07\x0a\x0d ,:]{0,50}$") def __init__(self, server, socket): self.server = server self.state = State.get_instance() self.socket = socket self.channels = {} # irc_lower(Channel name) --> Channel self.nickname = None self.user = None self.realname = None (self.host, self.port) = socket.getpeername() self.__timestamp = time.time() self.__readbuffer = "" self.__writebuffer = "" self.__sent_ping = False if self.server.password: self.__handle_command = self.__pass_handler else: self.__handle_command = self.__registration_handler def message_from_station(self, msg): targetname = self.server.channel_name if msg.command == BROADCAST else self.nickname pest_prefix = msg.prefix if msg.prefix else msg.speaker formatted_message = ":%s PRIVMSG %s :%s" % (pest_prefix, targetname, msg.body) self.__writebuffer += formatted_message + "\r\n" def get_prefix(self): return "%s" % (self.nickname) prefix = property(get_prefix) def check_aliveness(self): now = time.time() if self.__timestamp + 180 < now: self.disconnect("ping timeout") return if not self.__sent_ping and self.__timestamp + 90 < now: if self.__handle_command == self.__command_handler: # Registered. self.message("PING :%s" % self.server.name) self.__sent_ping = True else: # Not registered. self.disconnect("ping timeout") def write_queue_size(self): return len(self.__writebuffer) def __parse_read_buffer(self): lines = self.__linesep_regexp.split(self.__readbuffer) self.__readbuffer = lines[-1] lines = lines[:-1] for line in lines: if not line: # Empty line. Ignore. continue x = line.split(" ", 1) command = x[0].upper() if len(x) == 1: arguments = [] else: if len(x[1]) > 0 and x[1][0] == ":": arguments = [x[1][1:]] else: y = string.split(x[1], " :", 1) arguments = string.split(y[0]) if len(y) == 2: arguments.append(y[1]) self.__handle_command(command, arguments) def __pass_handler(self, command, arguments): server = self.server if command == "PASS": if len(arguments) == 0: self.reply_461("PASS") else: if arguments[0].lower() == server.password: self.__handle_command = self.__registration_handler else: self.reply("464 :Password incorrect") elif command == "QUIT": self.disconnect("Client quit") return def __registration_handler(self, command, arguments): server = self.server if command == "NICK": if len(arguments) < 1: self.reply("431 :No nickname given") return nick = arguments[0] if server.get_client(nick): self.reply("433 * %s :Nickname is already in use" % nick) elif not self.__valid_nickname_regexp.match(nick): self.reply("432 * %s :Erroneous nickname" % nick) else: self.nickname = nick server.client_changed_nickname(self, None) elif command == "USER": if len(arguments) < 4: self.reply_461("USER") return self.user = arguments[0] self.realname = arguments[3] elif command == "QUIT": self.disconnect("Client quit") return if self.nickname and self.user: self.reply("001 %s :Hi, welcome to Pest" % self.nickname) self.reply("002 %s :Your host is %s, running version blatta-%s" % (self.nickname, server.name, VERSION)) self.reply("003 %s :This server was created sometime" % self.nickname) self.reply("004 %s :%s blatta-%s o o" % (self.nickname, server.name, VERSION)) self.send_motd() self.__handle_command = self.__command_handler def __command_handler(self, command, arguments): def away_handler(): pass def ison_handler(): if len(arguments) < 1: self.reply_461("ISON") return nicks = arguments online = [n for n in nicks if server.get_client(n)] self.reply("303 %s :%s" % (self.nickname, " ".join(online))) def join_handler(): if len(arguments) < 1: self.reply_461("JOIN") return if arguments[0] == "0": for (channelname, channel) in self.channels.items(): self.message_channel(channel, "PART", channelname, True) server.remove_member_from_channel(self, channelname) self.channels = {} return channelnames = arguments[0].split(",") for channelname in channelnames: if irc_lower(channelname) in self.channels: continue if not valid_channel_re.match(channelname): self.reply_403(channelname) continue channel = server.get_channel(channelname) channel.add_member(self) self.channels[irc_lower(channelname)] = channel self.message_channel(channel, "JOIN", channelname, True) if channel.topic: self.reply("332 %s %s :%s" % (self.nickname, channel.name, channel.topic)) else: self.reply("331 %s %s :No topic is set" % (self.nickname, channel.name)) self.reply("353 %s = %s :%s" % (self.nickname, channelname, " ".join(sorted(x for x in self.state.get_peer_handles())))) self.reply("366 %s %s :End of NAMES list" % (self.nickname, channelname)) def list_handler(): if len(arguments) < 1: channels = server.channels.values() else: channels = [] for channelname in arguments[0].split(","): if server.has_channel(channelname): channels.append(server.get_channel(channelname)) channels.sort(key=lambda x: x.name) for channel in channels: self.reply("322 %s %s %d :%s" % (self.nickname, channel.name, len(channel.members), channel.topic)) self.reply("323 %s :End of LIST" % self.nickname) def lusers_handler(): pass def mode_handler(): if len(arguments) < 1: self.reply_461("MODE") return targetname = arguments[0] if server.has_channel(targetname): channel = server.get_channel(targetname) if len(arguments) < 2: if channel.key: modes = "+k" if irc_lower(channel.name) in self.channels: modes += " %s" % channel.key else: modes = "+" self.reply("324 %s %s %s" % (self.nickname, targetname, modes)) return flag = arguments[1] if flag == "+k": if len(arguments) < 3: self.reply_461("MODE") return key = arguments[2] if irc_lower(channel.name) in self.channels: channel.key = key self.message_channel( channel, "MODE", "%s +k %s" % (channel.name, key), True) else: self.reply("442 %s :You're not on that channel" % targetname) elif flag == "-k": if irc_lower(channel.name) in self.channels: channel.key = None self.message_channel( channel, "MODE", "%s -k" % channel.name, True) else: self.reply("442 %s :You're not on that channel" % targetname) else: self.reply("472 %s %s :Unknown MODE flag" % (self.nickname, flag)) elif targetname == self.nickname: if len(arguments) == 1: self.reply("221 %s +" % self.nickname) else: self.reply("501 %s :Unknown MODE flag" % self.nickname) else: self.reply_403(targetname) def motd_handler(): self.send_motd() def nick_handler(): if len(arguments) < 1: self.reply("431 :No nickname given") return newnick = arguments[0] client = server.get_client(newnick) if newnick == self.nickname: pass elif client and client is not self: self.reply("433 %s %s :Nickname is already in use" % (self.nickname, newnick)) elif not self.__valid_nickname_regexp.match(newnick): self.reply("432 %s %s :Erroneous Nickname" % (self.nickname, newnick)) else: oldnickname = self.nickname self.nickname = newnick server.client_changed_nickname(self, oldnickname) self.message_related( ":%s!%s@%s NICK %s" % (oldnickname, self.user, self.host, self.nickname), True) def notice_and_privmsg_handler(): if len(arguments) == 0: self.reply("411 %s :No recipient given (%s)" % (self.nickname, command)) return if len(arguments) == 1: self.reply("412 %s :No text to send" % self.nickname) return targetname = arguments[0] message = arguments[1] if server.has_channel(targetname): channel = server.get_channel(targetname) self.message_channel( channel, command, "%s :%s" % (channel.name, message)) # send the channel message to peers as well self.server.station.infosec.message( Message( { "speaker": self.nickname, "command": BROADCAST, "bounces": 0, "body": message })) else: self.server.station.infosec.message(Message({ "speaker": self.nickname, "handle": targetname, "body": message, "bounces": 0, "command": DIRECT })) def part_handler(): if len(arguments) < 1: self.reply_461("PART") return if len(arguments) > 1: partmsg = arguments[1] else: partmsg = self.nickname for channelname in arguments[0].split(","): if not valid_channel_re.match(channelname): self.reply_403(channelname) elif not irc_lower(channelname) in self.channels: self.reply("442 %s %s :You're not on that channel" % (self.nickname, channelname)) else: channel = self.channels[irc_lower(channelname)] self.message_channel( channel, "PART", "%s :%s" % (channelname, partmsg), True) del self.channels[irc_lower(channelname)] server.remove_member_from_channel(self, channelname) def ping_handler(): if len(arguments) < 1: self.reply("409 %s :No origin specified" % self.nickname) return self.reply("PONG %s :%s" % (server.name, arguments[0])) def pong_handler(): pass def quit_handler(): if len(arguments) < 1: quitmsg = self.nickname else: quitmsg = arguments[0] self.disconnect(quitmsg) def topic_handler(): if len(arguments) < 1: self.reply_461("TOPIC") return channelname = arguments[0] channel = self.channels.get(irc_lower(channelname)) if channel: if len(arguments) > 1: newtopic = arguments[1] channel.topic = newtopic self.message_channel( channel, "TOPIC", "%s :%s" % (channelname, newtopic), True) else: if channel.topic: self.reply("332 %s %s :%s" % (self.nickname, channel.name, channel.topic)) else: self.reply("331 %s %s :No topic is set" % (self.nickname, channel.name)) else: self.reply("442 %s :You're not on that channel" % channelname) def wallops_handler(): if len(arguments) < 1: self.reply_461(command) message = arguments[0] for client in server.clients.values(): client.message(":%s NOTICE %s :Global notice: %s" % (self.prefix, client.nickname, message)) def who_handler(): if len(arguments) < 1: return targetname = arguments[0] if server.has_channel(targetname): channel = server.get_channel(targetname) for member in channel.members: self.reply("352 %s %s %s %s %s %s H :0 %s" % (self.nickname, targetname, member.user, member.host, server.name, member.nickname, member.realname)) self.reply("315 %s %s :End of WHO list" % (self.nickname, targetname)) def whois_handler(): if len(arguments) < 1: return username = arguments[0] user = server.get_client(username) if user: self.reply("311 %s %s %s %s * :%s" % (self.nickname, user.nickname, user.user, user.host, user.realname)) self.reply("312 %s %s %s :%s" % (self.nickname, user.nickname, server.name, server.name)) self.reply("319 %s %s :%s" % (self.nickname, user.nickname, " ".join(user.channels))) self.reply("318 %s %s :End of WHOIS list" % (self.nickname, user.nickname)) else: self.reply("401 %s %s :No such nick" % (self.nickname, username)) def wot_handler(): if len(arguments) < 1: # Display the current WOT peers = self.state.get_peers() if len(peers) > 0: for peer in peers: if peer.address and peer.port: address = "%s:%s" % (peer.address, peer.port) else: address = "
" self.pest_reply("%s %s" % (string.join(peer.handles, ","), address)) else: self.pest_reply("WOT is empty") elif len(arguments) == 1: # Display all WOT data concerning the peer identified by HANDLE, # including all known keys, starting with the most recently used, for that peer. handle = arguments[0] peer = self.state.get_peer_by_handle(handle) if peer: self.pest_reply("keys:") for key in peer.keys: self.pest_reply("'%s'" % key) else: self.pest_reply("unknown peer: %s" % handle) else: pass def peer_handler(): if len(arguments) == 1: try: self.state.add_peer(arguments[0]) self.pest_reply("added new peer %s" % arguments[0]) self.message(":%s JOIN %s" % (arguments[0], self.server.channel_name)) except Exception, ex: self.pest_reply("error attempting to add peer %s" % arguments[0]) stack = traceback.format_exc() logging.debug(ex) logging.debug(stack) else: self.pest_reply("Usage: PEER