VERSION = "9989" import os import select import socket import sys import sys import tempfile import time import string import binascii import datetime from datetime import datetime from lib.client import Client from lib.state import State from lib.channel import Channel from lib.infosec import PACKET_SIZE from lib.infosec import MAX_BOUNCES from lib.infosec import STALE_PACKET from lib.infosec import DUPLICATE_PACKET from lib.infosec import MALFORMED_PACKET from lib.infosec import INVALID_SIGNATURE from lib.infosec import IGNORED from lib.infosec import Infosec from lib.peer import Peer from lib.message import Message from lib.ringbuffer import Ringbuffer from funcs import * from commands import BROADCAST from commands import DIRECT from commands import IGNORE import imp import pprint class Server(object): def __init__(self, options): self.irc_ports = options.irc_ports self.udp_port = options.udp_port self.channel_name = options.channel_name self.password = options.password self.motdfile = options.motd self.verbose = options.verbose self.debug = options.debug self.logdir = options.logdir self.chroot = options.chroot self.setuid = options.setuid self.statedir = options.statedir self.infosec = Infosec(self) self.config_file_path = options.config_file_path self.state = State(self, options.db_path) self.pp = pprint.PrettyPrinter(indent=4) if options.address_table_path != None: self.state.import_at_and_wot(options.address_table_path) if options.listen: self.address = socket.gethostbyname(options.listen) else: self.address = "" server_name_limit = 63 # From the RFC. self.name = socket.getfqdn(self.address)[:server_name_limit] self.channels = {} # irc_lower(Channel name) --> Channel instance. self.clients = {} # Socket --> Client instance..peers = "" self.nicknames = {} # irc_lower(Nickname) --> Client instance. self.recent = Ringbuffer(100) if self.logdir: create_directory(self.logdir) if self.statedir: create_directory(self.statedir) def daemonize(self): try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: sys.exit(1) os.setsid() try: pid = os.fork() if pid > 0: self.print_info("PID: %d" % pid) sys.exit(0) except OSError: sys.exit(1) os.chdir("/") os.umask(0) dev_null = open("/dev/null", "r+") os.dup2(dev_null.fileno(), sys.stdout.fileno()) os.dup2(dev_null.fileno(), sys.stderr.fileno()) os.dup2(dev_null.fileno(), sys.stdin.fileno()) def get_client(self, nickname): return self.nicknames.get(irc_lower(nickname)) def has_channel(self, name): return irc_lower(name) in self.channels def get_channel(self, channelname): if irc_lower(channelname) in self.channels: channel = self.channels[irc_lower(channelname)] else: channel = Channel(self, channelname) self.channels[irc_lower(channelname)] = channel return channel def get_motd_lines(self): if self.motdfile: try: return open(self.motdfile).readlines() except IOError: return ["Could not read MOTD file %r." % self.motdfile] else: return [] def print_info(self, msg): if self.verbose: print(msg) sys.stdout.flush() def print_debug(self, msg): if self.debug: print("%s %s" % (datetime.now(), msg)) sys.stdout.flush() def print_error(self, msg): sys.stderr.write("%s\n" % msg) def client_changed_nickname(self, client, oldnickname): if oldnickname: del self.nicknames[irc_lower(oldnickname)] self.nicknames[irc_lower(client.nickname)] = client def remove_member_from_channel(self, client, channelname): if irc_lower(channelname) in self.channels: channel = self.channels[irc_lower(channelname)] channel.remove_client(client) def remove_client(self, client, quitmsg): client.message_related(":%s QUIT :%s" % (client.prefix, quitmsg)) for x in client.channels.values(): client.channel_log(x, "quit (%s)" % quitmsg, meta=True) x.remove_client(client) if client.nickname \ and irc_lower(client.nickname) in self.nicknames: del self.nicknames[irc_lower(client.nickname)] del self.clients[client.socket] def remove_channel(self, channel): del self.channels[irc_lower(channel.name)] def handle_udp_data(self, bytes_address_pair): data = bytes_address_pair[0] address = bytes_address_pair[1] packet_info = (address[0], address[1], binascii.hexlify(data)[0:16]) self.print_debug("[%s:%d] -> %s" % packet_info) for peer in self.state.get_peers(): if peer.get_key() != None: message = self.infosec.unpack(peer, data) error_code = message.error_code if(error_code == None): self.print_debug("[%s] -> %s" % (peer.handles[0], message.body)) self.conditionally_update_address_table(peer, message, address) # send the message to all clients for c in self.clients: if (self.clients[c].is_addressed_to_me(message.body)): self.clients[c].message(message.body) # send the message to all other peers if it should be propagated if(message.command == BROADCAST) and message.bounces < MAX_BOUNCES: self.rebroadcast(peer, message) return elif error_code == STALE_PACKET: self.print_debug("[%s:%d] -> stale packet: %s" % packet_info) return elif error_code == DUPLICATE_PACKET: self.print_debug("[%s:%d] -> duplicate packet: %s" % packet_info) return elif error_code == MALFORMED_PACKET: self.print_debug("[%s:%d] -> malformed packet: %s" % packet_info) return elif error_code == IGNORED: self.conditionally_update_address_table(peer, message, address) self.print_debug("[%s:%d] -> ignoring packet: %s" % packet_info) return elif error_code == INVALID_SIGNATURE: pass self.print_debug("[%s:%d] -> martian packet: %s" % packet_info) # we only update the address table if the speaker is same as peer def conditionally_update_address_table(self, peer, message, address): try: idx = peer.handles.index(message.speaker) except: idx = None if idx != None: self.state.update_address_table({"handle": message.speaker, "address": address[0], "port": address[1] }) def peer_message(self, message): message.original = True if message.command == DIRECT: peer = self.state.get_peer_by_handle(message.handle) if peer and (peer.get_key() != None): peer.send(message) else: self.print_debug("Discarding message to unknown handle or handle with no key: %s" % message.handle) else: for peer in self.state.get_peers(): if peer.get_key() != None: peer.send(message) else: self.print_debug("Discarding message to handle with no key: %s" % message.handle) def rebroadcast(self, source_peer, message): message.original = False for peer in self.state.get_peers(): if(peer.peer_id != source_peer.peer_id): message.command = BROADCAST message.bounces = message.bounces + 1 peer.send(message) def sendrubbish(self): for peer in self.state.get_peers(): for socket in self.clients: self.peer_message(Message({ "speaker": self.clients[socket].nickname, "command": IGNORE, "bounces": 0, "body": self.infosec.gen_rubbish_body() }, self)) def start(self): # Setup UDP first self.udp_server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) self.udp_server_socket.bind((self.address, self.udp_port)) self.print_info("Listening for Pest packets on udp port %d." % self.udp_port) serversockets = [] for port in self.irc_ports: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind((self.address, port)) except socket.error as e: self.print_error("Could not bind port %s: %s." % (port, e)) sys.exit(1) s.listen(5) serversockets.append(s) del s self.print_info("Listening for IRC connections on port %d." % port) if self.chroot: os.chdir(self.chroot) os.chroot(self.chroot) self.print_info("Changed root directory to %s" % self.chroot) if self.setuid: os.setgid(self.setuid[1]) os.setuid(self.setuid[0]) self.print_info("Setting uid:gid to %s:%s" % (self.setuid[0], self.setuid[1])) last_aliveness_check = time.time() while True: (inputready,outputready,exceptready) = select.select([self.udp_server_socket],[],[],0) (iwtd, owtd, ewtd) = select.select( serversockets + [x.socket for x in self.clients.values()], [x.socket for x in self.clients.values() if x.write_queue_size() > 0], [], .2) for x in inputready: if x == self.udp_server_socket: bytes_address_pair = self.udp_server_socket.recvfrom(PACKET_SIZE) self.handle_udp_data(bytes_address_pair) for x in iwtd: if x in self.clients: self.clients[x].socket_readable_notification() else: (conn, addr) = x.accept() self.clients[conn] = Client(self, conn) self.print_info("Accepted connection from %s:%s." % ( addr[0], addr[1])) for x in owtd: if x in self.clients: # client may have been disconnected self.clients[x].socket_writable_notification() now = time.time() if last_aliveness_check + 10 < now: for client in self.clients.values(): client.check_aliveness() last_aliveness_check = now self.sendrubbish() # Kludge to keep ephemeral port open when NATed def create_directory(path): if not os.path.isdir(path): os.makedirs(path)