class Client(Protocol): '''ssl client implementation.''' def __init__(self, uid, port): super(Client, self).__init__('client') self.uid = uid self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = SSLIOStream(self.sock, ssl_options={'ca_certs':SERVER_CRT_PATH, 'cert_reqs':ssl.CERT_REQUIRED}) self.conn.connect(('127.0.0.1', self.port), self.on_connect) def on_connect(self): self.conn.set_close_callback(self.on_close) self.pubkey, self.privkey = None, None self.init_keys() self.write(OP_PUBKEY, self.pubkey) # this is hardcoded currently in absence of proper tests if self.uid == 2: self.send_message(1, 'hello world') logger.debug('Client %s: Sending message "Hello World" to user with uid 1' % self.uid) self.read_line() def read_line(self): self.conn.read_until(CRLF, self.handle_line) def on_close(self): self.conn = None self.sock = None def init_keys(self): if os.path.isfile(CLIENT_PUB_PATH % self.uid) and os.path.isfile(CLIENT_PRIV_PATH % self.uid): with open(CLIENT_PUB_PATH % self.uid, 'rb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'rb') as privfile: self.pubkey = pubfile.read().strip() self.privkey = privfile.read().strip() logger.debug('read existing pub/priv key for uid %s' % self.uid) else: self.pubkey, self.privkey = self.generate_keys() with open(CLIENT_PUB_PATH % self.uid, 'wb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'wb') as privfile: pubfile.write(self.pubkey) privfile.write(self.privkey) logger.debug('written pub/priv key for uid %s' % self.uid) def write(self, *messages): for message in messages: self.conn.write('%s%s' % (message, CRLF)) def send_message(self, uid, message): with open(CLIENT_PUB_PATH % uid, 'rb') as pubfile: pubkey = pubfile.read() # encrypt message using receiver public key enc = self.encrypt_message(message, pubkey) # sign encrypted message for digital verification sig = self.generate_signature(enc[0]) message = (OP_MESSAGE, pubkey, enc[0], sig[0],) self.write(*message) @staticmethod def generate_keys(): random_generator = Random.new().read priv = RSA.generate(1024, random_generator) pub = priv.publickey() return (pub.exportKey().strip(), priv.exportKey().strip()) def generate_signature(self, message): '''sign messaging using our priv key''' k = RSA.importKey(self.privkey) h = MD5.new(message).digest() return k.sign(h, '') @staticmethod def verify_signature(pubkey, signature, message): '''verify signature using signing user public key''' k = RSA.importKey(pubkey) h = MD5.new(message).digest() return k.verify(h, signature) @staticmethod def encrypt_message(message, pubkey): '''encrypt message using receiving user public key''' k = RSA.importKey(pubkey) return k.encrypt(message, 32) def decrypt_message(self, enc): '''decrypt message using our priv key''' k = RSA.importKey(self.privkey) return k.decrypt(enc)
class IrcConnection(object): def __init__(self, bot, server_name, address, port, nick, user, realname, owners, channels=None, password=None, ssl=None): self.bot = bot self.server_name = server_name self.owners = owners self.address = address self.port = port self.nick = nick self.user = user self.password = password self.ssl = ssl self.realname = realname self.initial_channels = set(channels) if channels else set() self.logger = getLogger(__name__) self._protocol_events = dict() self.channels = dict() self.users = dict() self.init_protocol_events() def init_protocol_events(self): self._protocol_events = { 'PRIVMSG' : self.on_privmsg, 'PING' : self.on_ping, 'JOIN' : self.on_join, '401' : self.on_nochan, '001' : self.on_welcome, 'KICK' : self.on_kick, 'PART' : self.on_part, '353' : self.on_names, 'NICK' : self.on_nick } def connect(self, reconnecting=False): self.logger.debug('CONNECTING...') _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) if self.ssl is None: self._stream = IOStream(_sock) elif self.ssl is True: self._stream = SSLIOStream(_sock) else: self._stream = SSLIOStream(_sock, ssl_options=self.ssl) self._stream.connect((self.address, self.port), self._register) def _register(self): self.logger.debug('CONNECTED') if self.password: # TODO need to check for error responses self.authenticate() self.set_nick(self.nick) self.write_raw('USER %s 0 * :%s' % (self.user, self.realname)) self._next() def add_channel(self, channel): self.channels[channel] = [] def remove_channel(self, channel): del self.channels[channel] def add_user(self, channel, user): self.channels[channel].append(user) self.users[user.nick] = user def remove_user(self, channel, user): self.channels[channels].remove(user) del self.users[user.nick] def user_change_nick(self, old_nick, new_nick): user = self.users.pop(old_nick) user.nick = new_nick self.users[new_nick] = user def authenticate(self): self.logger.debug("Authenticating (%s)", self.password) self.write_raw('PASS %s' % self.password) def set_nick(self, nick): if not nick: raise IrcError('Cannot set empty nick') self.logger.debug('SETTING NICK {nick: %s}' % nick) self.write_raw('NICK %s' % nick) def join_channel(self, *channels): if not all([c for c in channels]): raise IrcError('Empty channel') self.logger.debug('JOINING CHANNEL(S): {channels: %s}' % repr(channels)) chan_def = ','.join(channels) self.write_raw('JOIN %s' % chan_def) def part_channel(self, *channels): if not all([c for c in channels]): raise IrcError('Empty channel') self.logger.debug('PARTING CHANNEL: {channels: %s}' % repr(channels)) chan_def = ','.join(channels) self.write_raw('PART :%s' % chan_def) def private_message(self, destination, message): if not message: raise IrcError('Cannot send empty message') if not destination: raise IrcError('Cannot send to empty destination') self.logger.debug('SENDING PRIVMSG: {destination: %s, message: %s}' % (destination, message)) self.write_raw('PRIVMSG %s :%s' % (destination, message)) def reply(self, event, message): if event.destination and event.destination != self.nick: destination = event.destination else: destination = event.nick self.private_message(destination, message) def reply_with_nick(self, event, message): if event.destination: message = '%s: %s' % (event.nick, message) self.reply(event, message) def kick(self, channel, user, comment=None): if not channel: raise IrcError('Cannot kick from empty channel') if not user: raise IrcError('Cannot kick empty player') self.logger.debug('KICKING {channel: %s, user: %s}' % (channel, user)) kick_str = 'KICK %s %s' % (channel, user) if comment: kick_str += ' :%s' % comment self.write_raw(kick_str) def write_raw(self, line): line.replace(EOL, '') self.logger.debug('WRITE RAW: {line: %s}' % line) self._stream.write(line + EOL) def read_raw(self, line): self.logger.debug('READ RAW: {line: %s}' % line.replace(EOL, '')) event = IrcEvent(self.nick, line) self.handle(event) self.bot.process_hooks(self, event) self.bot.process_plugins(self, event) self._next() def handle(self, event): if event.type in self._protocol_events: self._protocol_events[event.type](event) def _next(self): self._stream.read_until(EOL, self.read_raw) def on_welcome(self, event): self.logger.debug('RECIEVED RPL_WELCOME') if self.initial_channels: self.join_channel(*self.initial_channels) def on_ping(self, event): # One ping only, please self.logger.debug('RECIEVED PING') self.write_raw("PONG %s\r\n" % event.text) def on_privmsg(self, event): # :[email protected] PRIVMSG #xx :hi self.logger.debug('RECIEVED PRIVMSG {destination: %s,' ' message: %s}' % (event.destination, event.text)) pass def on_nick(self, event): old_nick = event.nick new_nick = event.text self.logger.debug('RECIEVED NICK {old_nick: %s, new_nick: %s}' % (old_nick, new_nick)) if event.nick == self.nick: self.nick = new_nick else: self.user_change_nick(old_nick, new_nick) def on_join(self, event): channel = event.destination nick = event.nick self.logger.debug('RECIEVED JOIN {channel: %s, nick: %s}' % (channel, nick)) if self.nick == nick: self.add_channel(channel) else: self.add_user(channel, IrcUser(nick)) def on_names(self, event): nick_chan, nicks_raw = event.parameters_raw.split(':') nicks = nicks_raw.split() if '@' in nick_chan: channel = nick_chan.split('@')[-1].strip() elif '=' in nick_chan: channel = nick_chan.split('=')[-1].strip() else: raise Exception self.logger.debug('RECIEVED NAMES {channel: %s, nick_count: %d}' % ( channel, len(nicks))) for nr in nicks: if nr[0] == '@': nick = nr[1:] elif nr[0] == '+': nick = nr[1:] else: nick = nr user = IrcUser(nick) self.add_user(channel, user) def on_nochan(self, event): channel = event.parameters[0] self.logger.debug('RECIEVED ERR_NOSUCHCHANNEL {channel: %s}' % channel) # :senor.crunchybueno.com 401 nodnc #xx :No such nick/channel self.remove_channel(channel) def on_part(self, event): nick = event.nick channel = event.destination self.logger.debug('RECIEVED PART {channel: %s, nick: %s}' % (channel, nick)) if event.nick == self.nick: self.logger.debug('IOBot parted from %s' % channel) self.remove_channel(channel) def on_kick(self, event): nick = event.parameters[0] channel = event.destination self.logger.debug('RECIEVED KICK {channel: %s, nick: %s}' % (channel, nick)) if event.parameters[0] == self.nick: self.logger.warning('IOBot was KICKed from %s' % channel) self.remove_channel(channel)
class Client(Protocol): '''ssl client implementation.''' def __init__(self, uid, port): super(Client, self).__init__('client') self.uid = uid self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = SSLIOStream(self.sock, ssl_options={ 'ca_certs': SERVER_CRT_PATH, 'cert_reqs': ssl.CERT_REQUIRED }) self.conn.connect(('127.0.0.1', self.port), self.on_connect) def on_connect(self): self.conn.set_close_callback(self.on_close) self.pubkey, self.privkey = None, None self.init_keys() self.write(OP_PUBKEY, self.pubkey) # this is hardcoded currently in absence of proper tests if self.uid == 2: self.send_message(1, 'hello world') logger.debug( 'Client %s: Sending message "Hello World" to user with uid 1' % self.uid) self.read_line() def read_line(self): self.conn.read_until(CRLF, self.handle_line) def on_close(self): self.conn = None self.sock = None def init_keys(self): if os.path.isfile(CLIENT_PUB_PATH % self.uid) and os.path.isfile( CLIENT_PRIV_PATH % self.uid): with open(CLIENT_PUB_PATH % self.uid, 'rb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'rb') as privfile: self.pubkey = pubfile.read().strip() self.privkey = privfile.read().strip() logger.debug('read existing pub/priv key for uid %s' % self.uid) else: self.pubkey, self.privkey = self.generate_keys() with open(CLIENT_PUB_PATH % self.uid, 'wb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'wb') as privfile: pubfile.write(self.pubkey) privfile.write(self.privkey) logger.debug('written pub/priv key for uid %s' % self.uid) def write(self, *messages): for message in messages: self.conn.write('%s%s' % (message, CRLF)) def send_message(self, uid, message): with open(CLIENT_PUB_PATH % uid, 'rb') as pubfile: pubkey = pubfile.read() # encrypt message using receiver public key enc = self.encrypt_message(message, pubkey) # sign encrypted message for digital verification sig = self.generate_signature(enc[0]) message = ( OP_MESSAGE, pubkey, enc[0], sig[0], ) self.write(*message) @staticmethod def generate_keys(): random_generator = Random.new().read priv = RSA.generate(1024, random_generator) pub = priv.publickey() return (pub.exportKey().strip(), priv.exportKey().strip()) def generate_signature(self, message): '''sign messaging using our priv key''' k = RSA.importKey(self.privkey) h = MD5.new(message).digest() return k.sign(h, '') @staticmethod def verify_signature(pubkey, signature, message): '''verify signature using signing user public key''' k = RSA.importKey(pubkey) h = MD5.new(message).digest() return k.verify(h, signature) @staticmethod def encrypt_message(message, pubkey): '''encrypt message using receiving user public key''' k = RSA.importKey(pubkey) return k.encrypt(message, 32) def decrypt_message(self, enc): '''decrypt message using our priv key''' k = RSA.importKey(self.privkey) return k.decrypt(enc)