import socket import time import re import os import base64 import traceback import logging import datetime from message import Message, PEST_VERSION from broadcast import Broadcast from direct import Direct from station import VERSION from funcs import * from commands import BROADCAST, 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 = None 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%s" % ( pest_prefix, targetname, msg.warning if msg.warning else "", 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 command, arguments = self.__parse_command_arguments(line) self.__handle_command(command, arguments) def __parse_command_arguments(self, line): 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]) return 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 self.state.set_knob("nick", 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 PestNet" % self.nickname) self.reply("002 %s :Your host is %s, running Blatta %d and Pest 0x%X" % (self.nickname, server.name, VERSION, PEST_VERSION)) self.reply("003 %s :This server was created %s" % (self.nickname, datetime.datetime.now())) 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) 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(): pass def lusers_handler(): pass def mode_handler(): pass 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) self.state.set_knob('nick', self.nickname) 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] # check for pest commands before handling this as a message if message[0] is "%": pest_command, pest_arguments = self.__parse_command_arguments(message[1:]) self.__handle_command(pest_command, pest_arguments) return 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 Broadcast( { "speaker": self.nickname, "body": message, "long_buffer": self.server.station.long_buffer }, self.state).send() else: Direct({ "speaker": self.nickname, "handle": targetname, "body": message, "long_buffer": self.server.station.long_buffer }, self.state).send() 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(): pass def wallops_handler(): pass def who_handler(): pass def whois_handler(): pass 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