1 |
diff --git a/Makefile b/Makefile |
2 |
new file mode 100644 |
3 |
index 0000000..1db3ff4 |
4 |
--- /dev/null |
5 |
+++ b/Makefile |
6 |
@@ -0,0 +1,23 @@ |
7 |
+VERSION := $(shell sed -ne 's/^VERSION = "\(.*\)"/\1/p' lib/server.py) |
8 |
+ |
9 |
+DISTFILES = alcuin config.py.example README.txt lib |
10 |
+ |
11 |
+all: |
12 |
+ echo "Nothing to do." |
13 |
+ |
14 |
+dist: |
15 |
+ mkdir alcuin-$(VERSION) |
16 |
+ cp -r $(DISTFILES) alcuin-$(VERSION) |
17 |
+ tar cvzf alcuin-$(VERSION).tar.gz alcuin-$(VERSION) |
18 |
+ rm -rf alcuin-$(VERSION) |
19 |
+ |
20 |
+clean: |
21 |
+ rm -rf genesis.vdiff genesis.vdiff.escaped alcuin-$(VERSION) |
22 |
+ find . -name "*.swp" -delete |
23 |
+ find . -name "*.pyc" -delete |
24 |
+ |
25 |
+genesis: |
26 |
+ git show --pretty="format:" -1 HEAD > genesis.vdiff |
27 |
+ |
28 |
+escaped-genesis: |
29 |
+ 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 |
30 |
diff --git a/README.txt b/README.txt |
31 |
new file mode 100644 |
32 |
index 0000000..2d5ff88 |
33 |
--- /dev/null |
34 |
+++ b/README.txt |
35 |
@@ -0,0 +1,25 @@ |
36 |
+Alcuin implements an IRC server with a udp client built in |
37 |
+such that it can connect to other alcuin servers via a |
38 |
+gossip network. It is a work in progress and much is |
39 |
+yet to be completed including but not limited to: |
40 |
+ |
41 |
+- gossip style message forwarding |
42 |
+- message deduplication |
43 |
+- symmetric encryption of messages |
44 |
+- support for broadcasting several important IRC commands |
45 |
+ over the gossip net. |
46 |
+- mitigation of hash length extension attacks |
47 |
+ |
48 |
+GETTING STARTED |
49 |
+ |
50 |
+1. Copy config.py.example to config.py (nothing in the config file is |
51 |
+used yet, but alcuin will crash if it doesn't exist). |
52 |
+2. Launch alcuin with something like the following command: |
53 |
+./alcuin --verbose --port=6668 --peers=206.189.163.145 |
54 |
+ |
55 |
+NOTES FOR DIFF/PATCH N00B5 |
56 |
+ |
57 |
+To apply the genesis patch (or any patch) to the current directory |
58 |
+and recreate the directory structure of the original: |
59 |
+ |
60 |
+patch -p1 -ruN < <wherever>/genesis.vdiff |
61 |
diff --git a/alcuin b/alcuin |
62 |
new file mode 100755 |
63 |
index 0000000..bbf9ed4 |
64 |
--- /dev/null |
65 |
+++ b/alcuin |
66 |
@@ -0,0 +1,146 @@ |
67 |
+#! /usr/bin/env python |
68 |
+ |
69 |
+import os |
70 |
+import re |
71 |
+import select |
72 |
+import socket |
73 |
+import string |
74 |
+import sys |
75 |
+import tempfile |
76 |
+import time |
77 |
+from lib.server import VERSION |
78 |
+from lib.server import Server |
79 |
+from lib.peer import Peer |
80 |
+from datetime import datetime |
81 |
+from optparse import OptionParser |
82 |
+import config as cfg |
83 |
+ |
84 |
+ |
85 |
+def main(argv): |
86 |
+ op = OptionParser( |
87 |
+ version=VERSION, |
88 |
+ description="alcuin is a small and limited IRC server emulator for gossip networks.") |
89 |
+ op.add_option( |
90 |
+ "-d", "--daemon", |
91 |
+ action="store_true", |
92 |
+ help="fork and become a daemon") |
93 |
+ op.add_option( |
94 |
+ "--debug", |
95 |
+ action="store_true", |
96 |
+ help="print debug messages to stdout") |
97 |
+ op.add_option( |
98 |
+ "--listen", |
99 |
+ metavar="X", |
100 |
+ help="listen on specific IP address X") |
101 |
+ op.add_option( |
102 |
+ "--logdir", |
103 |
+ metavar="X", |
104 |
+ help="store channel log in directory X") |
105 |
+ op.add_option( |
106 |
+ "--motd", |
107 |
+ metavar="X", |
108 |
+ help="display file X as message of the day") |
109 |
+ op.add_option( |
110 |
+ "-s", "--ssl-pem-file", |
111 |
+ metavar="FILE", |
112 |
+ help="enable SSL and use FILE as the .pem certificate+key") |
113 |
+ op.add_option( |
114 |
+ "-p", "--password", |
115 |
+ metavar="X", |
116 |
+ help="require connection password X; default: no password") |
117 |
+ op.add_option( |
118 |
+ "--ports", |
119 |
+ metavar="X", |
120 |
+ help="listen to ports X (a list separated by comma or whitespace);" |
121 |
+ " default: 6667 or 6697 if SSL is enabled") |
122 |
+ op.add_option( |
123 |
+ "--udp-port", |
124 |
+ metavar="X", |
125 |
+ help="listen for UDP packets on X;" |
126 |
+ " default: 7778") |
127 |
+ op.add_option( |
128 |
+ "--peers", |
129 |
+ metavar="X", |
130 |
+ help="Broadcast to X (a list of IP addresses separated by comma or whitespace)") |
131 |
+ op.add_option( |
132 |
+ "--statedir", |
133 |
+ metavar="X", |
134 |
+ help="save persistent channel state (topic, key) in directory X") |
135 |
+ op.add_option( |
136 |
+ "--verbose", |
137 |
+ action="store_true", |
138 |
+ help="be verbose (print some progress messages to stdout)") |
139 |
+ if os.name == "posix": |
140 |
+ op.add_option( |
141 |
+ "--chroot", |
142 |
+ metavar="X", |
143 |
+ help="change filesystem root to directory X after startup" |
144 |
+ " (requires root)") |
145 |
+ op.add_option( |
146 |
+ "--setuid", |
147 |
+ metavar="U[:G]", |
148 |
+ help="change process user (and optionally group) after startup" |
149 |
+ " (requires root)") |
150 |
+ |
151 |
+ (options, args) = op.parse_args(argv[1:]) |
152 |
+ if options.debug: |
153 |
+ options.verbose = True |
154 |
+ if options.ports is None: |
155 |
+ if options.ssl_pem_file is None: |
156 |
+ options.ports = "6667" |
157 |
+ else: |
158 |
+ options.ports = "6697" |
159 |
+ if options.peers is None: |
160 |
+ options.peers = "" |
161 |
+ if options.udp_port is None: |
162 |
+ options.udp_port = 7778 |
163 |
+ else: |
164 |
+ options.udp_port = int(options.udp_port) |
165 |
+ if options.chroot: |
166 |
+ if os.getuid() != 0: |
167 |
+ op.error("Must be root to use --chroot") |
168 |
+ if options.setuid: |
169 |
+ from pwd import getpwnam |
170 |
+ from grp import getgrnam |
171 |
+ if os.getuid() != 0: |
172 |
+ op.error("Must be root to use --setuid") |
173 |
+ matches = options.setuid.split(":") |
174 |
+ if len(matches) == 2: |
175 |
+ options.setuid = (getpwnam(matches[0]).pw_uid, |
176 |
+ getgrnam(matches[1]).gr_gid) |
177 |
+ elif len(matches) == 1: |
178 |
+ options.setuid = (getpwnam(matches[0]).pw_uid, |
179 |
+ getpwnam(matches[0]).pw_gid) |
180 |
+ else: |
181 |
+ op.error("Specify a user, or user and group separated by a colon," |
182 |
+ " e.g. --setuid daemon, --setuid nobody:nobody") |
183 |
+ if (os.getuid() == 0 or os.getgid() == 0) and not options.setuid: |
184 |
+ op.error("Running this service as root is not recommended. Use the" |
185 |
+ " --setuid option to switch to an unprivileged account after" |
186 |
+ " startup. If you really intend to run as root, use" |
187 |
+ " \"--setuid root\".") |
188 |
+ |
189 |
+ ports = [] |
190 |
+ for port in re.split(r"[,\s]+", options.ports): |
191 |
+ try: |
192 |
+ ports.append(int(port)) |
193 |
+ except ValueError: |
194 |
+ op.error("bad port: %r" % port) |
195 |
+ options.ports = ports |
196 |
+ peers = [] |
197 |
+ for peer in re.split(r"[,\s]+", options.peers): |
198 |
+ try: |
199 |
+ peers.append(Peer(peer)) |
200 |
+ except ValueError: |
201 |
+ op.error("bad peer ip: %r" % peer) |
202 |
+ options.peers = peers |
203 |
+ server = Server(options) |
204 |
+ if options.daemon: |
205 |
+ server.daemonize() |
206 |
+ try: |
207 |
+ server.start() |
208 |
+ except KeyboardInterrupt: |
209 |
+ server.print_error("Interrupted.") |
210 |
+ |
211 |
+ |
212 |
+main(sys.argv) |
213 |
diff --git a/config.py.example b/config.py.example |
214 |
new file mode 100644 |
215 |
index 0000000..f9adc62 |
216 |
--- /dev/null |
217 |
+++ b/config.py.example |
218 |
@@ -0,0 +1,4 @@ |
219 |
+secret = "SEEKRIT" |
220 |
+peer_secrets = { |
221 |
+ "10.0.0.1":"K33P-0U7!" |
222 |
+} |
223 |
diff --git a/lib/__init__.py b/lib/__init__.py |
224 |
new file mode 100644 |
225 |
index 0000000..d2e75fb |
226 |
--- /dev/null |
227 |
+++ b/lib/__init__.py |
228 |
@@ -0,0 +1 @@ |
229 |
+# This file can't be empty otherwise diff won't see it. |
230 |
diff --git a/lib/channel.py b/lib/channel.py |
231 |
new file mode 100644 |
232 |
index 0000000..5086804 |
233 |
--- /dev/null |
234 |
+++ b/lib/channel.py |
235 |
@@ -0,0 +1,60 @@ |
236 |
+class Channel(object): |
237 |
+ def __init__(self, server, name): |
238 |
+ self.server = server |
239 |
+ self.name = name |
240 |
+ self.members = set() |
241 |
+ self._topic = "" |
242 |
+ self._key = None |
243 |
+ if self.server.statedir: |
244 |
+ self._state_path = "%s/%s" % ( |
245 |
+ self.server.statedir, |
246 |
+ name.replace("_", "__").replace("/", "_")) |
247 |
+ self._read_state() |
248 |
+ else: |
249 |
+ self._state_path = None |
250 |
+ |
251 |
+ def add_member(self, client): |
252 |
+ self.members.add(client) |
253 |
+ |
254 |
+ def get_topic(self): |
255 |
+ return self._topic |
256 |
+ |
257 |
+ def set_topic(self, value): |
258 |
+ self._topic = value |
259 |
+ self._write_state() |
260 |
+ |
261 |
+ topic = property(get_topic, set_topic) |
262 |
+ |
263 |
+ def get_key(self): |
264 |
+ return self._key |
265 |
+ |
266 |
+ def set_key(self, value): |
267 |
+ self._key = value |
268 |
+ self._write_state() |
269 |
+ |
270 |
+ key = property(get_key, set_key) |
271 |
+ |
272 |
+ def remove_client(self, client): |
273 |
+ self.members.discard(client) |
274 |
+ if not self.members: |
275 |
+ self.server.remove_channel(self) |
276 |
+ |
277 |
+ def _read_state(self): |
278 |
+ if not (self._state_path and os.path.exists(self._state_path)): |
279 |
+ return |
280 |
+ data = {} |
281 |
+ exec(open(self._state_path), {}, data) |
282 |
+ self._topic = data.get("topic", "") |
283 |
+ self._key = data.get("key") |
284 |
+ |
285 |
+ def _write_state(self): |
286 |
+ if not self._state_path: |
287 |
+ return |
288 |
+ (fd, path) = tempfile.mkstemp(dir=os.path.dirname(self._state_path)) |
289 |
+ fp = os.fdopen(fd, "w") |
290 |
+ fp.write("topic = %r\n" % self.topic) |
291 |
+ fp.write("key = %r\n" % self.key) |
292 |
+ fp.close() |
293 |
+ os.rename(path, self._state_path) |
294 |
+ |
295 |
+ |
296 |
diff --git a/lib/client.py b/lib/client.py |
297 |
new file mode 100644 |
298 |
index 0000000..cfc5331 |
299 |
--- /dev/null |
300 |
+++ b/lib/client.py |
301 |
@@ -0,0 +1,548 @@ |
302 |
+import time |
303 |
+import sys |
304 |
+import re |
305 |
+import string |
306 |
+from lib.server import VERSION |
307 |
+from lib.infosec import Infosec |
308 |
+from funcs import * |
309 |
+ |
310 |
+class Client(object): |
311 |
+ __linesep_regexp = re.compile(r"\r?\n") |
312 |
+ # The RFC limit for nicknames is 9 characters, but what the heck. |
313 |
+ __valid_nickname_regexp = re.compile( |
314 |
+ r"^[][\`_^{|}A-Za-z][][\`_^{|}A-Za-z0-9-]{0,50}$") |
315 |
+ __valid_channelname_regexp = re.compile( |
316 |
+ r"^[&#+!][^\x00\x07\x0a\x0d ,:]{0,50}$") |
317 |
+ |
318 |
+ def __init__(self, server, socket): |
319 |
+ self.server = server |
320 |
+ self.socket = socket |
321 |
+ self.channels = {} # irc_lower(Channel name) --> Channel |
322 |
+ self.nickname = None |
323 |
+ self.user = None |
324 |
+ self.realname = None |
325 |
+ (self.host, self.port) = socket.getpeername() |
326 |
+ self.__timestamp = time.time() |
327 |
+ self.__readbuffer = "" |
328 |
+ self.__writebuffer = "" |
329 |
+ self.__sent_ping = False |
330 |
+ self.infosec = Infosec() |
331 |
+ if self.server.password: |
332 |
+ self.__handle_command = self.__pass_handler |
333 |
+ else: |
334 |
+ self.__handle_command = self.__registration_handler |
335 |
+ |
336 |
+ def get_prefix(self): |
337 |
+ return "%s!%s@%s" % (self.nickname, self.user, self.host) |
338 |
+ prefix = property(get_prefix) |
339 |
+ |
340 |
+ def check_aliveness(self): |
341 |
+ now = time.time() |
342 |
+ if self.__timestamp + 180 < now: |
343 |
+ self.disconnect("ping timeout") |
344 |
+ return |
345 |
+ if not self.__sent_ping and self.__timestamp + 90 < now: |
346 |
+ if self.__handle_command == self.__command_handler: |
347 |
+ # Registered. |
348 |
+ self.message("PING :%s" % self.server.name) |
349 |
+ self.__sent_ping = True |
350 |
+ else: |
351 |
+ # Not registered. |
352 |
+ self.disconnect("ping timeout") |
353 |
+ |
354 |
+ def write_queue_size(self): |
355 |
+ return len(self.__writebuffer) |
356 |
+ |
357 |
+ def __parse_read_buffer(self): |
358 |
+ lines = self.__linesep_regexp.split(self.__readbuffer) |
359 |
+ self.__readbuffer = lines[-1] |
360 |
+ lines = lines[:-1] |
361 |
+ for line in lines: |
362 |
+ if not line: |
363 |
+ # Empty line. Ignore. |
364 |
+ continue |
365 |
+ x = line.split(" ", 1) |
366 |
+ command = x[0].upper() |
367 |
+ if len(x) == 1: |
368 |
+ arguments = [] |
369 |
+ else: |
370 |
+ if len(x[1]) > 0 and x[1][0] == ":": |
371 |
+ arguments = [x[1][1:]] |
372 |
+ else: |
373 |
+ y = string.split(x[1], " :", 1) |
374 |
+ arguments = string.split(y[0]) |
375 |
+ if len(y) == 2: |
376 |
+ arguments.append(y[1]) |
377 |
+ self.__handle_command(command, arguments) |
378 |
+ |
379 |
+ def __pass_handler(self, command, arguments): |
380 |
+ server = self.server |
381 |
+ if command == "PASS": |
382 |
+ if len(arguments) == 0: |
383 |
+ self.reply_461("PASS") |
384 |
+ else: |
385 |
+ if arguments[0].lower() == server.password: |
386 |
+ self.__handle_command = self.__registration_handler |
387 |
+ else: |
388 |
+ self.reply("464 :Password incorrect") |
389 |
+ elif command == "QUIT": |
390 |
+ self.disconnect("Client quit") |
391 |
+ return |
392 |
+ |
393 |
+ def __registration_handler(self, command, arguments): |
394 |
+ server = self.server |
395 |
+ if command == "NICK": |
396 |
+ if len(arguments) < 1: |
397 |
+ self.reply("431 :No nickname given") |
398 |
+ return |
399 |
+ nick = arguments[0] |
400 |
+ if server.get_client(nick): |
401 |
+ self.reply("433 * %s :Nickname is already in use" % nick) |
402 |
+ elif not self.__valid_nickname_regexp.match(nick): |
403 |
+ self.reply("432 * %s :Erroneous nickname" % nick) |
404 |
+ else: |
405 |
+ self.nickname = nick |
406 |
+ server.client_changed_nickname(self, None) |
407 |
+ elif command == "USER": |
408 |
+ if len(arguments) < 4: |
409 |
+ self.reply_461("USER") |
410 |
+ return |
411 |
+ self.user = arguments[0] |
412 |
+ self.realname = arguments[3] |
413 |
+ elif command == "QUIT": |
414 |
+ self.disconnect("Client quit") |
415 |
+ return |
416 |
+ if self.nickname and self.user: |
417 |
+ self.reply("001 %s :Hi, welcome to IRC" % self.nickname) |
418 |
+ self.reply("002 %s :Your host is %s, running version miniircd-%s" |
419 |
+ % (self.nickname, server.name, VERSION)) |
420 |
+ self.reply("003 %s :This server was created sometime" |
421 |
+ % self.nickname) |
422 |
+ self.reply("004 %s :%s miniircd-%s o o" |
423 |
+ % (self.nickname, server.name, VERSION)) |
424 |
+ self.send_lusers() |
425 |
+ self.send_motd() |
426 |
+ self.__handle_command = self.__command_handler |
427 |
+ |
428 |
+ def __command_handler(self, command, arguments): |
429 |
+ def away_handler(): |
430 |
+ pass |
431 |
+ |
432 |
+ def ison_handler(): |
433 |
+ if len(arguments) < 1: |
434 |
+ self.reply_461("ISON") |
435 |
+ return |
436 |
+ nicks = arguments |
437 |
+ online = [n for n in nicks if server.get_client(n)] |
438 |
+ self.reply("303 %s :%s" % (self.nickname, " ".join(online))) |
439 |
+ |
440 |
+ def join_handler(): |
441 |
+ if len(arguments) < 1: |
442 |
+ self.reply_461("JOIN") |
443 |
+ return |
444 |
+ if arguments[0] == "0": |
445 |
+ for (channelname, channel) in self.channels.items(): |
446 |
+ self.message_channel(channel, "PART", channelname, True) |
447 |
+ self.channel_log(channel, "left", meta=True) |
448 |
+ server.remove_member_from_channel(self, channelname) |
449 |
+ self.channels = {} |
450 |
+ return |
451 |
+ channelnames = arguments[0].split(",") |
452 |
+ if len(arguments) > 1: |
453 |
+ keys = arguments[1].split(",") |
454 |
+ else: |
455 |
+ keys = [] |
456 |
+ keys.extend((len(channelnames) - len(keys)) * [None]) |
457 |
+ for (i, channelname) in enumerate(channelnames): |
458 |
+ if irc_lower(channelname) in self.channels: |
459 |
+ continue |
460 |
+ if not valid_channel_re.match(channelname): |
461 |
+ self.reply_403(channelname) |
462 |
+ continue |
463 |
+ channel = server.get_channel(channelname) |
464 |
+ if channel.key is not None and channel.key != keys[i]: |
465 |
+ self.reply( |
466 |
+ "475 %s %s :Cannot join channel (+k) - bad key" |
467 |
+ % (self.nickname, channelname)) |
468 |
+ continue |
469 |
+ channel.add_member(self) |
470 |
+ self.channels[irc_lower(channelname)] = channel |
471 |
+ self.message_channel(channel, "JOIN", channelname, True) |
472 |
+ self.channel_log(channel, "joined", meta=True) |
473 |
+ if channel.topic: |
474 |
+ self.reply("332 %s %s :%s" |
475 |
+ % (self.nickname, channel.name, channel.topic)) |
476 |
+ else: |
477 |
+ self.reply("331 %s %s :No topic is set" |
478 |
+ % (self.nickname, channel.name)) |
479 |
+ self.reply("353 %s = %s :%s" |
480 |
+ % (self.nickname, |
481 |
+ channelname, |
482 |
+ " ".join(sorted(x.nickname |
483 |
+ for x in channel.members)))) |
484 |
+ self.reply("366 %s %s :End of NAMES list" |
485 |
+ % (self.nickname, channelname)) |
486 |
+ |
487 |
+ def list_handler(): |
488 |
+ if len(arguments) < 1: |
489 |
+ channels = server.channels.values() |
490 |
+ else: |
491 |
+ channels = [] |
492 |
+ for channelname in arguments[0].split(","): |
493 |
+ if server.has_channel(channelname): |
494 |
+ channels.append(server.get_channel(channelname)) |
495 |
+ channels.sort(key=lambda x: x.name) |
496 |
+ for channel in channels: |
497 |
+ self.reply("322 %s %s %d :%s" |
498 |
+ % (self.nickname, channel.name, |
499 |
+ len(channel.members), channel.topic)) |
500 |
+ self.reply("323 %s :End of LIST" % self.nickname) |
501 |
+ |
502 |
+ def lusers_handler(): |
503 |
+ self.send_lusers() |
504 |
+ |
505 |
+ def mode_handler(): |
506 |
+ if len(arguments) < 1: |
507 |
+ self.reply_461("MODE") |
508 |
+ return |
509 |
+ targetname = arguments[0] |
510 |
+ if server.has_channel(targetname): |
511 |
+ channel = server.get_channel(targetname) |
512 |
+ if len(arguments) < 2: |
513 |
+ if channel.key: |
514 |
+ modes = "+k" |
515 |
+ if irc_lower(channel.name) in self.channels: |
516 |
+ modes += " %s" % channel.key |
517 |
+ else: |
518 |
+ modes = "+" |
519 |
+ self.reply("324 %s %s %s" |
520 |
+ % (self.nickname, targetname, modes)) |
521 |
+ return |
522 |
+ flag = arguments[1] |
523 |
+ if flag == "+k": |
524 |
+ if len(arguments) < 3: |
525 |
+ self.reply_461("MODE") |
526 |
+ return |
527 |
+ key = arguments[2] |
528 |
+ if irc_lower(channel.name) in self.channels: |
529 |
+ channel.key = key |
530 |
+ self.message_channel( |
531 |
+ channel, "MODE", "%s +k %s" % (channel.name, key), |
532 |
+ True) |
533 |
+ self.channel_log( |
534 |
+ channel, "set channel key to %s" % key, meta=True) |
535 |
+ else: |
536 |
+ self.reply("442 %s :You're not on that channel" |
537 |
+ % targetname) |
538 |
+ elif flag == "-k": |
539 |
+ if irc_lower(channel.name) in self.channels: |
540 |
+ channel.key = None |
541 |
+ self.message_channel( |
542 |
+ channel, "MODE", "%s -k" % channel.name, |
543 |
+ True) |
544 |
+ self.channel_log( |
545 |
+ channel, "removed channel key", meta=True) |
546 |
+ else: |
547 |
+ self.reply("442 %s :You're not on that channel" |
548 |
+ % targetname) |
549 |
+ else: |
550 |
+ self.reply("472 %s %s :Unknown MODE flag" |
551 |
+ % (self.nickname, flag)) |
552 |
+ elif targetname == self.nickname: |
553 |
+ if len(arguments) == 1: |
554 |
+ self.reply("221 %s +" % self.nickname) |
555 |
+ else: |
556 |
+ self.reply("501 %s :Unknown MODE flag" % self.nickname) |
557 |
+ else: |
558 |
+ self.reply_403(targetname) |
559 |
+ |
560 |
+ def motd_handler(): |
561 |
+ self.send_motd() |
562 |
+ |
563 |
+ def nick_handler(): |
564 |
+ if len(arguments) < 1: |
565 |
+ self.reply("431 :No nickname given") |
566 |
+ return |
567 |
+ newnick = arguments[0] |
568 |
+ client = server.get_client(newnick) |
569 |
+ if newnick == self.nickname: |
570 |
+ pass |
571 |
+ elif client and client is not self: |
572 |
+ self.reply("433 %s %s :Nickname is already in use" |
573 |
+ % (self.nickname, newnick)) |
574 |
+ elif not self.__valid_nickname_regexp.match(newnick): |
575 |
+ self.reply("432 %s %s :Erroneous Nickname" |
576 |
+ % (self.nickname, newnick)) |
577 |
+ else: |
578 |
+ for x in self.channels.values(): |
579 |
+ self.channel_log( |
580 |
+ x, "changed nickname to %s" % newnick, meta=True) |
581 |
+ oldnickname = self.nickname |
582 |
+ self.nickname = newnick |
583 |
+ server.client_changed_nickname(self, oldnickname) |
584 |
+ self.message_related( |
585 |
+ ":%s!%s@%s NICK %s" |
586 |
+ % (oldnickname, self.user, self.host, self.nickname), |
587 |
+ True) |
588 |
+ |
589 |
+ def notice_and_privmsg_handler(): |
590 |
+ if len(arguments) == 0: |
591 |
+ self.reply("411 %s :No recipient given (%s)" |
592 |
+ % (self.nickname, command)) |
593 |
+ return |
594 |
+ if len(arguments) == 1: |
595 |
+ self.reply("412 %s :No text to send" % self.nickname) |
596 |
+ return |
597 |
+ targetname = arguments[0] |
598 |
+ message = arguments[1] |
599 |
+ client = server.get_client(targetname) |
600 |
+ |
601 |
+ if client: |
602 |
+ client.message(":%s %s %s :%s" |
603 |
+ % (self.prefix, command, targetname, message)) |
604 |
+ elif server.has_channel(targetname): |
605 |
+ channel = server.get_channel(targetname) |
606 |
+ self.message_channel( |
607 |
+ channel, command, "%s :%s" % (channel.name, message)) |
608 |
+ self.channel_log(channel, message) |
609 |
+ else: |
610 |
+ self.reply("401 %s %s :No such nick/channel" |
611 |
+ % (self.nickname, targetname)) |
612 |
+ |
613 |
+ def part_handler(): |
614 |
+ if len(arguments) < 1: |
615 |
+ self.reply_461("PART") |
616 |
+ return |
617 |
+ if len(arguments) > 1: |
618 |
+ partmsg = arguments[1] |
619 |
+ else: |
620 |
+ partmsg = self.nickname |
621 |
+ for channelname in arguments[0].split(","): |
622 |
+ if not valid_channel_re.match(channelname): |
623 |
+ self.reply_403(channelname) |
624 |
+ elif not irc_lower(channelname) in self.channels: |
625 |
+ self.reply("442 %s %s :You're not on that channel" |
626 |
+ % (self.nickname, channelname)) |
627 |
+ else: |
628 |
+ channel = self.channels[irc_lower(channelname)] |
629 |
+ self.message_channel( |
630 |
+ channel, "PART", "%s :%s" % (channelname, partmsg), |
631 |
+ True) |
632 |
+ self.channel_log(channel, "left (%s)" % partmsg, meta=True) |
633 |
+ del self.channels[irc_lower(channelname)] |
634 |
+ server.remove_member_from_channel(self, channelname) |
635 |
+ |
636 |
+ def ping_handler(): |
637 |
+ if len(arguments) < 1: |
638 |
+ self.reply("409 %s :No origin specified" % self.nickname) |
639 |
+ return |
640 |
+ self.reply("PONG %s :%s" % (server.name, arguments[0])) |
641 |
+ |
642 |
+ def pong_handler(): |
643 |
+ pass |
644 |
+ |
645 |
+ def quit_handler(): |
646 |
+ if len(arguments) < 1: |
647 |
+ quitmsg = self.nickname |
648 |
+ else: |
649 |
+ quitmsg = arguments[0] |
650 |
+ self.disconnect(quitmsg) |
651 |
+ |
652 |
+ def topic_handler(): |
653 |
+ if len(arguments) < 1: |
654 |
+ self.reply_461("TOPIC") |
655 |
+ return |
656 |
+ channelname = arguments[0] |
657 |
+ channel = self.channels.get(irc_lower(channelname)) |
658 |
+ if channel: |
659 |
+ if len(arguments) > 1: |
660 |
+ newtopic = arguments[1] |
661 |
+ channel.topic = newtopic |
662 |
+ self.message_channel( |
663 |
+ channel, "TOPIC", "%s :%s" % (channelname, newtopic), |
664 |
+ True) |
665 |
+ self.channel_log( |
666 |
+ channel, "set topic to %r" % newtopic, meta=True) |
667 |
+ else: |
668 |
+ if channel.topic: |
669 |
+ self.reply("332 %s %s :%s" |
670 |
+ % (self.nickname, channel.name, |
671 |
+ channel.topic)) |
672 |
+ else: |
673 |
+ self.reply("331 %s %s :No topic is set" |
674 |
+ % (self.nickname, channel.name)) |
675 |
+ else: |
676 |
+ self.reply("442 %s :You're not on that channel" % channelname) |
677 |
+ |
678 |
+ def wallops_handler(): |
679 |
+ if len(arguments) < 1: |
680 |
+ self.reply_461(command) |
681 |
+ message = arguments[0] |
682 |
+ for client in server.clients.values(): |
683 |
+ client.message(":%s NOTICE %s :Global notice: %s" |
684 |
+ % (self.prefix, client.nickname, message)) |
685 |
+ |
686 |
+ def who_handler(): |
687 |
+ if len(arguments) < 1: |
688 |
+ return |
689 |
+ targetname = arguments[0] |
690 |
+ if server.has_channel(targetname): |
691 |
+ channel = server.get_channel(targetname) |
692 |
+ for member in channel.members: |
693 |
+ self.reply("352 %s %s %s %s %s %s H :0 %s" |
694 |
+ % (self.nickname, targetname, member.user, |
695 |
+ member.host, server.name, member.nickname, |
696 |
+ member.realname)) |
697 |
+ self.reply("315 %s %s :End of WHO list" |
698 |
+ % (self.nickname, targetname)) |
699 |
+ |
700 |
+ def whois_handler(): |
701 |
+ if len(arguments) < 1: |
702 |
+ return |
703 |
+ username = arguments[0] |
704 |
+ user = server.get_client(username) |
705 |
+ if user: |
706 |
+ self.reply("311 %s %s %s %s * :%s" |
707 |
+ % (self.nickname, user.nickname, user.user, |
708 |
+ user.host, user.realname)) |
709 |
+ self.reply("312 %s %s %s :%s" |
710 |
+ % (self.nickname, user.nickname, server.name, |
711 |
+ server.name)) |
712 |
+ self.reply("319 %s %s :%s" |
713 |
+ % (self.nickname, user.nickname, |
714 |
+ " ".join(user.channels))) |
715 |
+ self.reply("318 %s %s :End of WHOIS list" |
716 |
+ % (self.nickname, user.nickname)) |
717 |
+ else: |
718 |
+ self.reply("401 %s %s :No such nick" |
719 |
+ % (self.nickname, username)) |
720 |
+ |
721 |
+ handler_table = { |
722 |
+ "AWAY": away_handler, |
723 |
+ "ISON": ison_handler, |
724 |
+ "JOIN": join_handler, |
725 |
+ "LIST": list_handler, |
726 |
+ "LUSERS": lusers_handler, |
727 |
+ "MODE": mode_handler, |
728 |
+ "MOTD": motd_handler, |
729 |
+ "NICK": nick_handler, |
730 |
+ "NOTICE": notice_and_privmsg_handler, |
731 |
+ "PART": part_handler, |
732 |
+ "PING": ping_handler, |
733 |
+ "PONG": pong_handler, |
734 |
+ "PRIVMSG": notice_and_privmsg_handler, |
735 |
+ "QUIT": quit_handler, |
736 |
+ "TOPIC": topic_handler, |
737 |
+ "WALLOPS": wallops_handler, |
738 |
+ "WHO": who_handler, |
739 |
+ "WHOIS": whois_handler, |
740 |
+ } |
741 |
+ server = self.server |
742 |
+ valid_channel_re = self.__valid_channelname_regexp |
743 |
+ try: |
744 |
+ handler_table[command]() |
745 |
+ except KeyError: |
746 |
+ self.reply("421 %s %s :Unknown command" % (self.nickname, command)) |
747 |
+ |
748 |
+ def udp_data_received(self, data): |
749 |
+ if data: |
750 |
+ message = self.infosec.unpack(data) |
751 |
+ if(message != None): |
752 |
+ self.message(message) |
753 |
+ |
754 |
+ def socket_readable_notification(self): |
755 |
+ try: |
756 |
+ data = self.socket.recv(2 ** 10) |
757 |
+ self.server.print_debug( |
758 |
+ "[%s:%d] -> %r" % (self.host, self.port, data)) |
759 |
+ quitmsg = "EOT" |
760 |
+ except socket.error as x: |
761 |
+ data = "" |
762 |
+ quitmsg = x |
763 |
+ if data: |
764 |
+ self.__readbuffer += data |
765 |
+ self.__parse_read_buffer() |
766 |
+ self.__timestamp = time.time() |
767 |
+ self.__sent_ping = False |
768 |
+ for peer in self.server.peers: |
769 |
+ peer.send(self, data) |
770 |
+ else: |
771 |
+ self.disconnect(quitmsg) |
772 |
+ |
773 |
+ def socket_writable_notification(self): |
774 |
+ try: |
775 |
+ print("socket_writable_notification: %s" % self.__writebuffer) |
776 |
+ sent = self.socket.send(self.__writebuffer) |
777 |
+ self.server.print_debug( |
778 |
+ "[%s:%d] <- %r" % ( |
779 |
+ self.host, self.port, self.__writebuffer[:sent])) |
780 |
+ self.__writebuffer = self.__writebuffer[sent:] |
781 |
+ except socket.error as x: |
782 |
+ self.disconnect(x) |
783 |
+ |
784 |
+ def disconnect(self, quitmsg): |
785 |
+ self.message("ERROR :%s" % quitmsg) |
786 |
+ self.server.print_info( |
787 |
+ "Disconnected connection from %s:%s (%s)." % ( |
788 |
+ self.host, self.port, quitmsg)) |
789 |
+ self.socket.close() |
790 |
+ self.server.remove_client(self, quitmsg) |
791 |
+ |
792 |
+ def message(self, msg): |
793 |
+ self.__writebuffer += msg + "\r\n" |
794 |
+ |
795 |
+ def reply(self, msg): |
796 |
+ self.message(":%s %s" % (self.server.name, msg)) |
797 |
+ |
798 |
+ def reply_403(self, channel): |
799 |
+ self.reply("403 %s %s :No such channel" % (self.nickname, channel)) |
800 |
+ |
801 |
+ def reply_461(self, command): |
802 |
+ nickname = self.nickname or "*" |
803 |
+ self.reply("461 %s %s :Not enough parameters" % (nickname, command)) |
804 |
+ |
805 |
+ def message_channel(self, channel, command, message, include_self=False): |
806 |
+ line = ":%s %s %s" % (self.prefix, command, message) |
807 |
+ for client in channel.members: |
808 |
+ if client != self or include_self: |
809 |
+ client.message(line) |
810 |
+ |
811 |
+ def channel_log(self, channel, message, meta=False): |
812 |
+ if not self.server.logdir: |
813 |
+ return |
814 |
+ if meta: |
815 |
+ format = "[%s] * %s %s\n" |
816 |
+ else: |
817 |
+ format = "[%s] <%s> %s\n" |
818 |
+ timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") |
819 |
+ logname = channel.name.replace("_", "__").replace("/", "_") |
820 |
+ fp = open("%s/%s.log" % (self.server.logdir, logname), "a") |
821 |
+ fp.write(format % (timestamp, self.nickname, message)) |
822 |
+ fp.close() |
823 |
+ |
824 |
+ def message_related(self, msg, include_self=False): |
825 |
+ clients = set() |
826 |
+ if include_self: |
827 |
+ clients.add(self) |
828 |
+ for channel in self.channels.values(): |
829 |
+ clients |= channel.members |
830 |
+ if not include_self: |
831 |
+ clients.discard(self) |
832 |
+ for client in clients: |
833 |
+ client.message(msg) |
834 |
+ |
835 |
+ def send_lusers(self): |
836 |
+ self.reply("251 %s :There are %d users and 0 services on 1 server" |
837 |
+ % (self.nickname, len(self.server.clients))) |
838 |
+ |
839 |
+ def send_motd(self): |
840 |
+ server = self.server |
841 |
+ motdlines = server.get_motd_lines() |
842 |
+ if motdlines: |
843 |
+ self.reply("375 %s :- %s Message of the day -" |
844 |
+ % (self.nickname, server.name)) |
845 |
+ for line in motdlines: |
846 |
+ self.reply("372 %s :- %s" % (self.nickname, line.rstrip())) |
847 |
+ self.reply("376 %s :End of /MOTD command" % self.nickname) |
848 |
+ else: |
849 |
+ self.reply("422 %s :MOTD File is missing" % self.nickname) |
850 |
diff --git a/lib/funcs.py b/lib/funcs.py |
851 |
new file mode 100644 |
852 |
index 0000000..4093964 |
853 |
--- /dev/null |
854 |
+++ b/lib/funcs.py |
855 |
@@ -0,0 +1,11 @@ |
856 |
+import sys |
857 |
+import string |
858 |
+ |
859 |
+_maketrans = str.maketrans if sys.version_info[0] == 3 else string.maketrans |
860 |
+_ircstring_translation = _maketrans( |
861 |
+ string.ascii_lowercase.upper() + "[]\\^", |
862 |
+ string.ascii_lowercase + "{}|~") |
863 |
+ |
864 |
+def irc_lower(s): |
865 |
+ return string.translate(s, _ircstring_translation) |
866 |
+ |
867 |
diff --git a/lib/infosec.py b/lib/infosec.py |
868 |
new file mode 100644 |
869 |
index 0000000..6e87ca6 |
870 |
--- /dev/null |
871 |
+++ b/lib/infosec.py |
872 |
@@ -0,0 +1,29 @@ |
873 |
+import hashlib |
874 |
+PACKET_SIZE = 1024 |
875 |
+MAX_MESSAGE_SIZE = 512 |
876 |
+ |
877 |
+class Infosec(object): |
878 |
+ #def __init__(self): |
879 |
+ # do nothing |
880 |
+ |
881 |
+ def pack(self, message): |
882 |
+ digest = hashlib.sha512(self._pad(message)).hexdigest() |
883 |
+ return digest + message |
884 |
+ |
885 |
+ def unpack(self, package): |
886 |
+ print("received package: %s" % package) |
887 |
+ received_digest = package[0:128] |
888 |
+ message = package[128:1023] |
889 |
+ digest = hashlib.sha512(self._pad(message)).hexdigest() |
890 |
+ print("received_digest: %s" % received_digest) |
891 |
+ print("digest: %s" % digest) |
892 |
+ print("message: %s") % message |
893 |
+ if(received_digest == digest): |
894 |
+ return message |
895 |
+ else: |
896 |
+ print("unable to validate package: %s" % package) |
897 |
+ return None |
898 |
+ |
899 |
+ def _pad(self, text): |
900 |
+ return str(text.ljust(MAX_MESSAGE_SIZE)).encode("ascii") |
901 |
+ |
902 |
diff --git a/lib/peer.py b/lib/peer.py |
903 |
new file mode 100644 |
904 |
index 0000000..4a64ed7 |
905 |
--- /dev/null |
906 |
+++ b/lib/peer.py |
907 |
@@ -0,0 +1,13 @@ |
908 |
+import socket |
909 |
+from infosec import Infosec |
910 |
+ |
911 |
+class Peer(object): |
912 |
+ def __init__(self, address): |
913 |
+ self.address = address |
914 |
+ self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) |
915 |
+ self.infosec = Infosec() |
916 |
+ |
917 |
+ def send(self, client, msg): |
918 |
+ full_message = str.encode(":%s %s" % (client.nickname, msg)) |
919 |
+ print("sending formatted_msg: %s" % full_message) |
920 |
+ self.socket.sendto(self.infosec.pack(full_message), (self.address, 7778)) |
921 |
diff --git a/lib/server.py b/lib/server.py |
922 |
new file mode 100644 |
923 |
index 0000000..38bf514 |
924 |
--- /dev/null |
925 |
+++ b/lib/server.py |
926 |
@@ -0,0 +1,208 @@ |
927 |
+VERSION = "9999" |
928 |
+ |
929 |
+import os |
930 |
+import select |
931 |
+import socket |
932 |
+import sys |
933 |
+import sys |
934 |
+import tempfile |
935 |
+import time |
936 |
+import string |
937 |
+from datetime import datetime |
938 |
+from lib.client import Client |
939 |
+from lib.channel import Channel |
940 |
+from lib.infosec import PACKET_SIZE |
941 |
+from lib.infosec import Infosec |
942 |
+from lib.peer import Peer |
943 |
+from funcs import * |
944 |
+ |
945 |
+class Server(object): |
946 |
+ def __init__(self, options): |
947 |
+ self.ports = options.ports |
948 |
+ self.peers = options.peers |
949 |
+ self.udp_port = options.udp_port |
950 |
+ self.password = options.password |
951 |
+ self.ssl_pem_file = options.ssl_pem_file |
952 |
+ self.motdfile = options.motd |
953 |
+ self.verbose = options.verbose |
954 |
+ self.debug = options.debug |
955 |
+ self.logdir = options.logdir |
956 |
+ self.chroot = options.chroot |
957 |
+ self.setuid = options.setuid |
958 |
+ self.statedir = options.statedir |
959 |
+ |
960 |
+ if options.listen: |
961 |
+ self.address = socket.gethostbyname(options.listen) |
962 |
+ else: |
963 |
+ self.address = "" |
964 |
+ server_name_limit = 63 # From the RFC. |
965 |
+ self.name = socket.getfqdn(self.address)[:server_name_limit] |
966 |
+ |
967 |
+ self.channels = {} # irc_lower(Channel name) --> Channel instance. |
968 |
+ self.clients = {} # Socket --> Client instance..peers = "" |
969 |
+ self.nicknames = {} # irc_lower(Nickname) --> Client instance. |
970 |
+ if self.logdir: |
971 |
+ create_directory(self.logdir) |
972 |
+ if self.statedir: |
973 |
+ create_directory(self.statedir) |
974 |
+ |
975 |
+ def daemonize(self): |
976 |
+ try: |
977 |
+ pid = os.fork() |
978 |
+ if pid > 0: |
979 |
+ sys.exit(0) |
980 |
+ except OSError: |
981 |
+ sys.exit(1) |
982 |
+ os.setsid() |
983 |
+ try: |
984 |
+ pid = os.fork() |
985 |
+ if pid > 0: |
986 |
+ self.print_info("PID: %d" % pid) |
987 |
+ sys.exit(0) |
988 |
+ except OSError: |
989 |
+ sys.exit(1) |
990 |
+ os.chdir("/") |
991 |
+ os.umask(0) |
992 |
+ dev_null = open("/dev/null", "r+") |
993 |
+ os.dup2(dev_null.fileno(), sys.stdout.fileno()) |
994 |
+ os.dup2(dev_null.fileno(), sys.stderr.fileno()) |
995 |
+ os.dup2(dev_null.fileno(), sys.stdin.fileno()) |
996 |
+ |
997 |
+ def get_client(self, nickname): |
998 |
+ return self.nicknames.get(irc_lower(nickname)) |
999 |
+ |
1000 |
+ def has_channel(self, name): |
1001 |
+ return irc_lower(name) in self.channels |
1002 |
+ |
1003 |
+ def get_channel(self, channelname): |
1004 |
+ if irc_lower(channelname) in self.channels: |
1005 |
+ channel = self.channels[irc_lower(channelname)] |
1006 |
+ else: |
1007 |
+ channel = Channel(self, channelname) |
1008 |
+ self.channels[irc_lower(channelname)] = channel |
1009 |
+ return channel |
1010 |
+ |
1011 |
+ def get_motd_lines(self): |
1012 |
+ if self.motdfile: |
1013 |
+ try: |
1014 |
+ return open(self.motdfile).readlines() |
1015 |
+ except IOError: |
1016 |
+ return ["Could not read MOTD file %r." % self.motdfile] |
1017 |
+ else: |
1018 |
+ return [] |
1019 |
+ |
1020 |
+ def print_info(self, msg): |
1021 |
+ if self.verbose: |
1022 |
+ print(msg) |
1023 |
+ sys.stdout.flush() |
1024 |
+ |
1025 |
+ def print_debug(self, msg): |
1026 |
+ if self.debug: |
1027 |
+ print(msg) |
1028 |
+ sys.stdout.flush() |
1029 |
+ |
1030 |
+ def print_error(self, msg): |
1031 |
+ sys.stderr.write("%s\n" % msg) |
1032 |
+ |
1033 |
+ def client_changed_nickname(self, client, oldnickname): |
1034 |
+ if oldnickname: |
1035 |
+ del self.nicknames[irc_lower(oldnickname)] |
1036 |
+ self.nicknames[irc_lower(client.nickname)] = client |
1037 |
+ |
1038 |
+ def remove_member_from_channel(self, client, channelname): |
1039 |
+ if irc_lower(channelname) in self.channels: |
1040 |
+ channel = self.channels[irc_lower(channelname)] |
1041 |
+ channel.remove_client(client) |
1042 |
+ |
1043 |
+ def remove_client(self, client, quitmsg): |
1044 |
+ client.message_related(":%s QUIT :%s" % (client.prefix, quitmsg)) |
1045 |
+ for x in client.channels.values(): |
1046 |
+ client.channel_log(x, "quit (%s)" % quitmsg, meta=True) |
1047 |
+ x.remove_client(client) |
1048 |
+ if client.nickname \ |
1049 |
+ and irc_lower(client.nickname) in self.nicknames: |
1050 |
+ del self.nicknames[irc_lower(client.nickname)] |
1051 |
+ del self.clients[client.socket] |
1052 |
+ |
1053 |
+ def remove_channel(self, channel): |
1054 |
+ del self.channels[irc_lower(channel.name)] |
1055 |
+ |
1056 |
+ def start(self): |
1057 |
+ # Setup UDP first |
1058 |
+ udp_server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) |
1059 |
+ udp_server_socket.bind((self.address, self.udp_port)) |
1060 |
+ |
1061 |
+ serversockets = [] |
1062 |
+ for port in self.ports: |
1063 |
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
1064 |
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
1065 |
+ try: |
1066 |
+ s.bind((self.address, port)) |
1067 |
+ except socket.error as e: |
1068 |
+ self.print_error("Could not bind port %s: %s." % (port, e)) |
1069 |
+ sys.exit(1) |
1070 |
+ s.listen(5) |
1071 |
+ serversockets.append(s) |
1072 |
+ del s |
1073 |
+ self.print_info("Listening on port %d." % port) |
1074 |
+ if self.chroot: |
1075 |
+ os.chdir(self.chroot) |
1076 |
+ os.chroot(self.chroot) |
1077 |
+ self.print_info("Changed root directory to %s" % self.chroot) |
1078 |
+ if self.setuid: |
1079 |
+ os.setgid(self.setuid[1]) |
1080 |
+ os.setuid(self.setuid[0]) |
1081 |
+ self.print_info("Setting uid:gid to %s:%s" |
1082 |
+ % (self.setuid[0], self.setuid[1])) |
1083 |
+ last_aliveness_check = time.time() |
1084 |
+ while True: |
1085 |
+ (inputready,outputready,exceptready) = select.select([udp_server_socket],[],[],0) |
1086 |
+ (iwtd, owtd, ewtd) = select.select( |
1087 |
+ serversockets + [x.socket for x in self.clients.values()], |
1088 |
+ [x.socket for x in self.clients.values() |
1089 |
+ if x.write_queue_size() > 0], |
1090 |
+ [], |
1091 |
+ 0) |
1092 |
+ for x in inputready: |
1093 |
+ if x == udp_server_socket: |
1094 |
+ bytes_address_pair = udp_server_socket.recvfrom(PACKET_SIZE) |
1095 |
+ message = bytes_address_pair[0] |
1096 |
+ address = bytes_address_pair[1] |
1097 |
+ print message |
1098 |
+ for c in self.clients: |
1099 |
+ self.clients[c].udp_data_received(message) |
1100 |
+ for x in iwtd: |
1101 |
+ if x in self.clients: |
1102 |
+ self.clients[x].socket_readable_notification() |
1103 |
+ else: |
1104 |
+ (conn, addr) = x.accept() |
1105 |
+ if self.ssl_pem_file: |
1106 |
+ import ssl |
1107 |
+ try: |
1108 |
+ conn = ssl.wrap_socket( |
1109 |
+ conn, |
1110 |
+ server_side=True, |
1111 |
+ certfile=self.ssl_pem_file, |
1112 |
+ keyfile=self.ssl_pem_file) |
1113 |
+ except ssl.SSLError as e: |
1114 |
+ self.print_error( |
1115 |
+ "SSL error for connection from %s:%s: %s" % ( |
1116 |
+ addr[0], addr[1], e)) |
1117 |
+ continue |
1118 |
+ self.clients[conn] = Client(self, conn) |
1119 |
+ self.print_info("Accepted connection from %s:%s." % ( |
1120 |
+ addr[0], addr[1])) |
1121 |
+ for x in owtd: |
1122 |
+ if x in self.clients: # client may have been disconnected |
1123 |
+ self.clients[x].socket_writable_notification() |
1124 |
+ now = time.time() |
1125 |
+ if last_aliveness_check + 10 < now: |
1126 |
+ for client in self.clients.values(): |
1127 |
+ client.check_aliveness() |
1128 |
+ last_aliveness_check = now |
1129 |
+ |
1130 |
+ |
1131 |
+def create_directory(path): |
1132 |
+ if not os.path.isdir(path): |
1133 |
+ os.makedirs(path) |
1134 |
+ |
1135 |
|