- 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 |