- add config file location command line option
- add script and config for running local testnet
- fix timestamp range bug
- force endianness for packets
- explicitly send certain commands instead of all commands
- clean up logging somewhat
- don't use misleading .vpatch extension for patches
- add patch script to Makefile for generating a patch from the latest
commit
alcuin-9996.diff alcuin-9996.tar.gz
| 1 | |
| 2 | diff --git a/Makefile b/Makefile |
| 3 | index a548f62..7ab6ef1 100644 |
| 4 | |
| 5 | |
| 6 | |
| 7 | rm -rf alcuin-$(VERSION) |
| 8 | |
| 9 | clean: |
| 10 | - rm -rf genesis.vdiff genesis.vdiff.escaped alcuin-$(VERSION) |
| 11 | + rm -rf genesis.diff alcuin-$(VERSION).diff.escaped alcuin-$(VERSION) |
| 12 | rm *.tar.gz |
| 13 | + rm *.diff.escaped |
| 14 | find . -name "*.swp" -delete |
| 15 | find . -name "*.pyc" -delete |
| 16 | |
| 17 | genesis: |
| 18 | - git show --pretty="format:" -1 94c3ce55693f > genesis.vdiff |
| 19 | + git show --pretty="format:" -1 94c3ce55693f > genesis.diff |
| 20 | |
| 21 | -escaped-genesis: |
| 22 | - git show --pretty="format:" -1 HEAD | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g; s/((/\(\(/g; s/))/\)\)/g; s/\[([0-9])\[/\[$1\[/g; s/]]/\]\]/g' > genesis.vdiff.escaped |
| 23 | +patch: |
| 24 | + git show --pretty="format:" -1 HEAD > alcuin-$(VERSION).diff |
| 25 | + |
| 26 | +escaped-patch: |
| 27 | + git show --pretty="format:" -1 HEAD | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g; s/((/\(\(/g; s/))/\)\)/g; s/\[([0-9])\[/\[$1\[/g; s/]]/\]\]/g' > alcuin-$(VERSION).diff.escaped |
| 28 | diff --git a/alcuin b/alcuin |
| 29 | index 44efc5d..a46c007 100755 |
| 30 | |
| 31 | |
| 32 | |
| 33 | version=VERSION, |
| 34 | description="alcuin is a small and limited IRC server emulator for gossip networks.") |
| 35 | op.add_option( |
| 36 | + "-c", "--config-file-path", |
| 37 | + metavar="X", |
| 38 | + help="load the configfile from X") |
| 39 | + op.add_option( |
| 40 | "-d", "--daemon", |
| 41 | action="store_true", |
| 42 | help="fork and become a daemon") |
| 43 | |
| 44 | options.udp_port = 7778 |
| 45 | else: |
| 46 | options.udp_port = int(options.udp_port) |
| 47 | + if options.config_file_path is None: |
| 48 | + options.config_file_path = "config.py" |
| 49 | if options.chroot: |
| 50 | if os.getuid() != 0: |
| 51 | op.error("Must be root to use --chroot") |
| 52 | diff --git a/lib/client.py b/lib/client.py |
| 53 | index 0ce0d4c..f3dc6ab 100644 |
| 54 | |
| 55 | |
| 56 | |
| 57 | import re |
| 58 | import string |
| 59 | from lib.server import VERSION |
| 60 | -from lib.infosec import Infosec |
| 61 | from funcs import * |
| 62 | |
| 63 | class Client(object): |
| 64 | |
| 65 | self.__readbuffer = "" |
| 66 | self.__writebuffer = "" |
| 67 | self.__sent_ping = False |
| 68 | - self.infosec = Infosec(self.server) |
| 69 | if self.server.password: |
| 70 | self.__handle_command = self.__pass_handler |
| 71 | else: |
| 72 | |
| 73 | client = server.get_client(targetname) |
| 74 | |
| 75 | if client: |
| 76 | - client.message(":%s %s %s :%s" |
| 77 | - % (self.prefix, command, targetname, message)) |
| 78 | + formatted_message = ":%s %s %s :%s" % (self.prefix, command, targetname, message) |
| 79 | + client.message(formatted_message) |
| 80 | + self.server.peer_broadcast(formatted_message) |
| 81 | elif server.has_channel(targetname): |
| 82 | channel = server.get_channel(targetname) |
| 83 | self.message_channel( |
| 84 | channel, command, "%s :%s" % (channel.name, message)) |
| 85 | self.channel_log(channel, message) |
| 86 | else: |
| 87 | + # this isn't reliably true so let's not send the error |
| 88 | + return |
| 89 | self.reply("401 %s %s :No such nick/channel" |
| 90 | % (self.nickname, targetname)) |
| 91 | |
| 92 | |
| 93 | except KeyError: |
| 94 | self.reply("421 %s %s :Unknown command" % (self.nickname, command)) |
| 95 | |
| 96 | - def udp_data_received(self, address, data): |
| 97 | - if data: |
| 98 | - for peer in self.server.peers: |
| 99 | - self.server.print_debug("trying peer: %s" % peer.name) |
| 100 | - message, timestamp = self.infosec.unpack(peer, data) |
| 101 | - if(message != None): |
| 102 | - self.message(message) |
| 103 | - self.server.rebroadcast(self, peer, message, timestamp) |
| 104 | - return |
| 105 | - self.server.print_debug("Unknown peer address: %s" % address) |
| 106 | - |
| 107 | def socket_readable_notification(self): |
| 108 | try: |
| 109 | data = self.socket.recv(2 ** 10) |
| 110 | |
| 111 | self.__parse_read_buffer() |
| 112 | self.__timestamp = time.time() |
| 113 | self.__sent_ping = False |
| 114 | - for peer in self.server.peers: |
| 115 | - peer.send(self, data) |
| 116 | else: |
| 117 | self.disconnect(quitmsg) |
| 118 | |
| 119 | def socket_writable_notification(self): |
| 120 | try: |
| 121 | - self.server.print_debug("socket_writable_notification: %s" % self.__writebuffer) |
| 122 | sent = self.socket.send(self.__writebuffer) |
| 123 | self.server.print_debug( |
| 124 | "[%s:%d] <- %r" % ( |
| 125 | |
| 126 | for client in channel.members: |
| 127 | if client != self or include_self: |
| 128 | client.message(line) |
| 129 | + # send the channel message to peers as well |
| 130 | + self.server.peer_broadcast(line) |
| 131 | |
| 132 | def channel_log(self, channel, message, meta=False): |
| 133 | if not self.server.logdir: |
| 134 | diff --git a/lib/infosec.py b/lib/infosec.py |
| 135 | index 00aa6c1..a431c2f 100644 |
| 136 | |
| 137 | |
| 138 | |
| 139 | import struct |
| 140 | import sys |
| 141 | |
| 142 | -PACKET_SIZE = 584 |
| 143 | +PACKET_SIZE = 580 |
| 144 | MAX_MESSAGE_SIZE = 512 |
| 145 | MAX_SECRET_SIZE = 24 |
| 146 | +TS_ACCEPTABLE_SKEW = 60 * 15 |
| 147 | |
| 148 | class Infosec(object): |
| 149 | def __init__(self, server=None): |
| 150 | |
| 151 | |
| 152 | # we want to ignore this ts if it is sent back to us |
| 153 | self.server.recent.insert(int_ts) |
| 154 | + self.server.print_debug("added %d to recent" % int_ts) |
| 155 | |
| 156 | # build the packet and return it |
| 157 | - packet = struct.pack("L64s512s", int_ts, digest_bytes, ciphertext_bytes) |
| 158 | + packet = struct.pack("!L64s512s", int_ts, digest_bytes, ciphertext_bytes) |
| 159 | return packet |
| 160 | |
| 161 | def unpack(self, peer, packet): |
| 162 | - int_ts, digest, ciphertext_bytes = struct.unpack("L64s512s", packet) |
| 163 | + try: |
| 164 | + int_ts, digest, ciphertext_bytes = struct.unpack("!L64s512s", packet) |
| 165 | + except: |
| 166 | + self.server.print_error("Discarding malformed packet?") |
| 167 | + return None, None |
| 168 | |
| 169 | # Check the timestamp and digest |
| 170 | - current_ts = int(time.time()) |
| 171 | digest_check = hashlib.sha512(peer.remote_secret + ciphertext_bytes).digest() |
| 172 | - if((current_ts - int_ts) > 60 * 15): |
| 173 | - self.server.print_debug("rejected old message") |
| 174 | + |
| 175 | + if(int_ts not in self._ts_range()): |
| 176 | + self.server.print_debug("rejected message with timestamp out of range") |
| 177 | return None, None |
| 178 | elif(self.server.recent.has(int_ts)): |
| 179 | - self.server.print_debug("rejected known message") |
| 180 | + self.server.print_debug("rejected known message: %d" % int_ts) |
| 181 | return None, None |
| 182 | elif(digest_check != digest): |
| 183 | self.server.print_debug("name: %s" % peer.name) |
| 184 | |
| 185 | |
| 186 | def _pad(self, text, size): |
| 187 | return text.ljust(size) |
| 188 | + |
| 189 | + def _ts_range(self): |
| 190 | + current_ts = int(time.time()) |
| 191 | + return range(current_ts - TS_ACCEPTABLE_SKEW, current_ts + TS_ACCEPTABLE_SKEW) |
| 192 | diff --git a/lib/peer.py b/lib/peer.py |
| 193 | index 34b8c60..e108328 100644 |
| 194 | |
| 195 | |
| 196 | |
| 197 | -import socket |
| 198 | -from infosec import Infosec |
| 199 | +import socket |
| 200 | +from infosec import Infosec |
| 201 | +import sys |
| 202 | |
| 203 | class Peer(object): |
| 204 | def __init__(self, server, peer_entry): |
| 205 | |
| 206 | self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) |
| 207 | self.infosec = Infosec(server) |
| 208 | |
| 209 | - def send(self, client, msg, timestamp=None): |
| 210 | - if(timestamp == None): |
| 211 | - full_message = str.encode(":%s %s" % (client.nickname, msg)) |
| 212 | - else: |
| 213 | + def send(self, msg, timestamp=None): |
| 214 | + try: |
| 215 | full_message = str.encode(msg) |
| 216 | - self.server.print_debug("sending formatted_msg: %s" % full_message) |
| 217 | - self.socket.sendto(self.infosec.pack(self, full_message, timestamp), (self.address, self.port)) |
| 218 | + self.server.print_debug("sending message: %s" % full_message) |
| 219 | + self.socket.sendto(self.infosec.pack(self, full_message, timestamp), (self.address, self.port)) |
| 220 | + except Exception as ex: |
| 221 | + print("Exception while attempting to encode message: %s" % ex) |
| 222 | diff --git a/lib/server.py b/lib/server.py |
| 223 | index ad595f7..9e8cbe5 100644 |
| 224 | |
| 225 | |
| 226 | |
| 227 | -VERSION = "9997" |
| 228 | +VERSION = "9996" |
| 229 | |
| 230 | import os |
| 231 | import select |
| 232 | |
| 233 | from lib.client import Client |
| 234 | from lib.channel import Channel |
| 235 | from lib.infosec import PACKET_SIZE |
| 236 | +from lib.infosec import Infosec |
| 237 | from lib.peer import Peer |
| 238 | from lib.ringbuffer import Ringbuffer |
| 239 | from funcs import * |
| 240 | -import config as cfg |
| 241 | +import imp |
| 242 | |
| 243 | class Server(object): |
| 244 | def __init__(self, options): |
| 245 | |
| 246 | self.chroot = options.chroot |
| 247 | self.setuid = options.setuid |
| 248 | self.statedir = options.statedir |
| 249 | + self.infosec = Infosec(self) |
| 250 | + self.config_file_path = options.config_file_path |
| 251 | |
| 252 | if options.listen: |
| 253 | self.address = socket.gethostbyname(options.listen) |
| 254 | |
| 255 | self.clients = {} # Socket --> Client instance..peers = "" |
| 256 | self.nicknames = {} # irc_lower(Nickname) --> Client instance. |
| 257 | self.peers = [] |
| 258 | + if self.config_file_path: |
| 259 | + cfg = imp.load_source('config', self.config_file_path) |
| 260 | + else: |
| 261 | + cfg = imp.load_source('config', "./config.py") |
| 262 | + |
| 263 | for peer_entry in cfg.peers: |
| 264 | self.peers.append(Peer(self, peer_entry)) |
| 265 | self.recent = Ringbuffer(100) |
| 266 | |
| 267 | if self.statedir: |
| 268 | create_directory(self.statedir) |
| 269 | |
| 270 | - def rebroadcast(self, target_client, source_peer, message, timestamp): |
| 271 | - for peer in self.peers: |
| 272 | - if(peer != source_peer): |
| 273 | - peer.send(target_client, message, timestamp) |
| 274 | - |
| 275 | def daemonize(self): |
| 276 | try: |
| 277 | pid = os.fork() |
| 278 | |
| 279 | def remove_channel(self, channel): |
| 280 | del self.channels[irc_lower(channel.name)] |
| 281 | |
| 282 | + def handle_udp_data(self, data): |
| 283 | + for peer in self.peers: |
| 284 | + message, timestamp = self.infosec.unpack(peer, data) |
| 285 | + if(message != None): |
| 286 | + self.print_debug("valid message from peer: %s" % peer.name) |
| 287 | + # send the message to all clients |
| 288 | + for c in self.clients: |
| 289 | + self.clients[c].message(message) |
| 290 | + # send the message to all other peers |
| 291 | + self.rebroadcast(peer, message, timestamp) |
| 292 | + else: |
| 293 | + self.print_debug("Unknown peer address: %s" % peer.address) |
| 294 | + |
| 295 | + def peer_broadcast(self, message): |
| 296 | + for peer in self.peers: |
| 297 | + peer.send(message) |
| 298 | + |
| 299 | + def rebroadcast(self, source_peer, message, timestamp): |
| 300 | + for peer in self.peers: |
| 301 | + if(peer != source_peer): |
| 302 | + peer.send(message, timestamp) |
| 303 | + |
| 304 | + |
| 305 | def start(self): |
| 306 | # Setup UDP first |
| 307 | udp_server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) |
| 308 | |
| 309 | for x in inputready: |
| 310 | if x == udp_server_socket: |
| 311 | bytes_address_pair = udp_server_socket.recvfrom(PACKET_SIZE) |
| 312 | - message = bytes_address_pair[0] |
| 313 | - address = bytes_address_pair[1][0] |
| 314 | - for c in self.clients: |
| 315 | - self.clients[c].udp_data_received(address, message) |
| 316 | + data = bytes_address_pair[0] |
| 317 | + self.handle_udp_data(data) |
| 318 | for x in iwtd: |
| 319 | if x in self.clients: |
| 320 | self.clients[x].socket_readable_notification() |
| 321 | diff --git a/start_test_net.sh b/start_test_net.sh |
| 322 | new file mode 100755 |
| 323 | index 0000000..9930708 |
| 324 | |
| 325 | |
| 326 | |
| 327 | +#!/bin/bash |
| 328 | + |
| 329 | +# start 3 servers on different ports |
| 330 | +./alcuin --debug --port 6668 --udp-port 7778 --config-file test_net_configs/a.py > logs/a & |
| 331 | +./alcuin --debug --port 6669 --udp-port 7779 --config-file test_net_configs/b.py > logs/b & |
| 332 | +./alcuin --debug --port 6670 --udp-port 7780 --config-file test_net_configs/c.py > logs/c & |
| 333 | diff --git a/test_net_configs/a.py b/test_net_configs/a.py |
| 334 | new file mode 100644 |
| 335 | index 0000000..143bbd1 |
| 336 | |
| 337 | |
| 338 | |
| 339 | +peers = [ |
| 340 | + { |
| 341 | + "name":"b", |
| 342 | + "local_secret":"bik7TwuenAj,", |
| 343 | + "remote_secret":"5olfobNanov~", |
| 344 | + "address":"localhost", |
| 345 | + "port":7779 |
| 346 | + }, |
| 347 | +# { |
| 348 | +# "name":"c", |
| 349 | +# "local_secret":"bik7TwuenAj,", |
| 350 | +# "remote_secret":"Ceat]GrucEm4", |
| 351 | +# "address":"localhost", |
| 352 | +# "port":7780 |
| 353 | +# } |
| 354 | +] |
| 355 | diff --git a/test_net_configs/b.py b/test_net_configs/b.py |
| 356 | new file mode 100644 |
| 357 | index 0000000..5666cd8 |
| 358 | |
| 359 | |
| 360 | |
| 361 | +peers = [ |
| 362 | + { |
| 363 | + "name":"a", |
| 364 | + "local_secret":"5olfobNanov~", |
| 365 | + "remote_secret":"bik7TwuenAj,", |
| 366 | + "address":"localhost", |
| 367 | + "port":7778 |
| 368 | + }, |
| 369 | + { |
| 370 | + "name":"c", |
| 371 | + "local_secret":"5olfobNanov~", |
| 372 | + "remote_secret":"Ceat]GrucEm4", |
| 373 | + "address":"localhost", |
| 374 | + "port":7780 |
| 375 | + } |
| 376 | +] |
| 377 | diff --git a/test_net_configs/c.py b/test_net_configs/c.py |
| 378 | new file mode 100644 |
| 379 | index 0000000..5887bf0 |
| 380 | |
| 381 | |
| 382 | |
| 383 | +peers = [ |
| 384 | +# { |
| 385 | +# "name":"a", |
| 386 | +# "local_secret":"Ceat]GrucEm4", |
| 387 | +# "remote_secret":"bik7TwuenAj,", |
| 388 | +# "address":"localhost", |
| 389 | +# "port":7778 |
| 390 | +# }, |
| 391 | + { |
| 392 | + "name":"b", |
| 393 | + "local_secret":"Ceat]GrucEm4", |
| 394 | + "remote_secret":"5olfobNanov~", |
| 395 | + "address":"localhost", |
| 396 | + "port":7779 |
| 397 | + } |
| 398 | +] |
| 399 |