VERSION = "9992" import os import select import socket import sys import sys import tempfile import time import string 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 Infosec from lib.peer import Peer from lib.ringbuffer import Ringbuffer from funcs import * from commands import BROADCAST from commands import DIRECT import imp import pprint class Server(object): def __init__(self, options): self.ports = options.ports self.udp_port = options.udp_port self.password = options.password self.ssl_pem_file = options.ssl_pem_file 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(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] for peer in self.state.get_peers(): if peer.get_key() != None: message = self.infosec.unpack(peer, data) if(message != None): self.print_debug("valid message from peer: %s" % peer.handles[0]) # we only update the address table if the speaker is same as peer 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] }) # send the message to all clients for c in self.clients: # self.clients[c].udp_socket_readable_notification(message) 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 self.print_debug("Unknown peer: %s %d" % (address[0], 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 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)) serversockets = [] for port in self.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 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], [], 0) 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() if self.ssl_pem_file: import ssl try: conn = ssl.wrap_socket( conn, server_side=True, certfile=self.ssl_pem_file, keyfile=self.ssl_pem_file) except ssl.SSLError as e: self.print_error( "SSL error for connection from %s:%s: %s" % ( addr[0], addr[1], e)) continue 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 def create_directory(path): if not os.path.isdir(path): os.makedirs(path)