Archive for the ‘irc’ Category

New Alcuin Release and Rename to Blatta

Monday, November 1st, 2021

As you may know, "blatta" is Latin for "cockroach." This name seems more aligned with the Pest theme, so I'm going with that.

I'm attempting to release this as a signed genesis. I haven't been able to figure out how to press it myself yet using vk.pl, but it *should* work for those who know how to use their v implementation.

This version implements several Pest-specific commands: AT, WOT, KEY, UNKEY, PEER, UNPEER, and GENKEY. In addition, this version requires only one key to be shared between a pair of peers, and also uses base64 format to store and display keys.

This is the first version that doesn't require a station to be restarted to add new peers, so it may be a bit easier at this point to stand up a longer running test net.

genesis.vdiff genesis.vdiff.thimbronion.sig

Alcuin 9994

Monday, October 11th, 2021

From the updated README:

Alcuin implements a subset of the Pest spec, which can be found
here: http://www.loper-os.org/pub/pest/pest_draft.html                                                    

Although it is a work in progress and much is yet to be completed, Alcuin
*should* be able to interact with another Pest client
successfully.

Notably missing:                                                                                          

- Pest specific client commands
- Pest specific warning/informational output for incoming/outgoing messages
- GetData message support
- Key Offer message support
- Key Slice message support                                                                               

Under the hood there is a dynamic AT and WOT, but there is no way to update
them via the client.

For the moment the client must still be configured via the config.py file,
although this is now passed in via the --address-table-path command line
option.  For now, the config file is really a combination WOT/AT.

I am unable to provide a genesis using vdiff at this time because I can't get vdiff to exclude certain ephemeral files and directories.

alcuin-9994.tar.gz alcuin-9994.tar.gz.asc

Alcuin 9995: propagate field

Wednesday, August 25th, 2021
  • add propagate field
  • don't propagate PMs
  • filter commands sent to client via udp by nick/channel

alcuin-9995.diff alcuin-9995.tar.gz

1
2 diff --git a/Makefile b/Makefile
3 index 7ab6ef1..5b21c74 100644
4 --- a/Makefile
5 +++ b/Makefile
6 @@ -12,9 +12,8 @@ dist:
7 rm -rf alcuin-$(VERSION)
8
9 clean:
10 - rm -rf genesis.diff alcuin-$(VERSION).diff.escaped alcuin-$(VERSION)
11 + rm -rf alcuin-$(VERSION).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 diff --git a/lib/client.py b/lib/client.py
18 index f3dc6ab..25ec4ab 100644
19 --- a/lib/client.py
20 +++ b/lib/client.py
21 @@ -30,6 +30,16 @@ class Client(object):
22 else:
23 self.__handle_command = self.__registration_handler
24
25 + def is_addressed_to_me(self, message):
26 + command = self.__parse_udp_message(message)
27 + if command[0] == 'PRIVMSG':
28 + if command[1][0][0] == '#' or command[1][0] == self.nickname:
29 + return True
30 + else:
31 + return False
32 + else:
33 + return True
34 +
35 def get_prefix(self):
36 return "%s!%s@%s" % (self.nickname, self.user, self.host)
37 prefix = property(get_prefix)
38 @@ -51,6 +61,30 @@ class Client(object):
39 def write_queue_size(self):
40 return len(self.__writebuffer)
41
42 + def __parse_udp_message(self, message):
43 + data = " ".join(message.split()[1:]) + "\r\n"
44 + lines = self.__linesep_regexp.split(data)
45 + lines = lines[:-1]
46 + commands = []
47 + for line in lines:
48 + if not line:
49 + # Empty line. Ignore.
50 + continue
51 + x = line.split(" ", 1)
52 + command = x[0].upper()
53 + if len(x) == 1:
54 + arguments = []
55 + else:
56 + if len(x[1]) > 0 and x[1][0] == ":":
57 + arguments = [x[1][1:]]
58 + else:
59 + y = string.split(x[1], " :", 1)
60 + arguments = string.split(y[0])
61 + if len(y) == 2:
62 + arguments.append(y[1])
63 + commands.append([command, arguments])
64 + return commands[0]
65 +
66 def __parse_read_buffer(self):
67 lines = self.__linesep_regexp.split(self.__readbuffer)
68 self.__readbuffer = lines[-1]
69 @@ -294,21 +328,18 @@ class Client(object):
70 targetname = arguments[0]
71 message = arguments[1]
72 client = server.get_client(targetname)
73 + self.server.print_debug(self.server.nicknames)
74
75 - if client:
76 - formatted_message = ":%s %s %s :%s" % (self.prefix, command, targetname, message)
77 - client.message(formatted_message)
78 - self.server.peer_broadcast(formatted_message)
79 - elif server.has_channel(targetname):
80 + if server.has_channel(targetname):
81 channel = server.get_channel(targetname)
82 self.message_channel(
83 channel, command, "%s :%s" % (channel.name, message))
84 self.channel_log(channel, message)
85 else:
86 - # this isn't reliably true so let's not send the error
87 - return
88 - self.reply("401 %s %s :No such nick/channel"
89 - % (self.nickname, targetname))
90 + formatted_message = ":%s %s %s :%s" % (self.prefix, command, targetname, message)
91 + if(client):
92 + client.message(formatted_message)
93 + self.server.peer_broadcast(formatted_message, False)
94
95 def part_handler():
96 if len(arguments) < 1:
97 diff --git a/lib/infosec.py b/lib/infosec.py
98 index a431c2f..b0c9c86 100644
99 --- a/lib/infosec.py
100 +++ b/lib/infosec.py
101 @@ -7,7 +7,7 @@ import time
102 import struct
103 import sys
104
105 -PACKET_SIZE = 580
106 +PACKET_SIZE = 581
107 MAX_MESSAGE_SIZE = 512
108 MAX_SECRET_SIZE = 24
109 TS_ACCEPTABLE_SKEW = 60 * 15
110 @@ -16,7 +16,7 @@ class Infosec(object):
111 def __init__(self, server=None):
112 self.server = server
113
114 - def pack(self, peer, message, timestamp=None):
115 + def pack(self, peer, message, propagate, timestamp=None):
116 # if we are rebroadcasting we need to use the original timestamp
117 if(timestamp == None):
118 int_ts = int(time.time())
119 @@ -34,39 +34,40 @@ class Infosec(object):
120 self.server.print_debug("added %d to recent" % int_ts)
121
122 # build the packet and return it
123 - packet = struct.pack("!L64s512s", int_ts, digest_bytes, ciphertext_bytes)
124 + packet = struct.pack("!L64s?512s", int_ts, digest_bytes, propagate, ciphertext_bytes)
125 return packet
126
127 def unpack(self, peer, packet):
128 try:
129 - int_ts, digest, ciphertext_bytes = struct.unpack("!L64s512s", packet)
130 + int_ts, digest, propagate, ciphertext_bytes = struct.unpack("!L64s?512s", packet)
131 except:
132 self.server.print_error("Discarding malformed packet?")
133 - return None, None
134 + return None, None, None
135
136 # Check the timestamp and digest
137 digest_check = hashlib.sha512(peer.remote_secret + ciphertext_bytes).digest()
138
139 if(int_ts not in self._ts_range()):
140 self.server.print_debug("rejected message with timestamp out of range")
141 - return None, None
142 + return None, None, None
143 elif(self.server.recent.has(int_ts)):
144 self.server.print_debug("rejected known message: %d" % int_ts)
145 - return None, None
146 + return None, None, None
147 elif(digest_check != digest):
148 self.server.print_debug("name: %s" % peer.name)
149 self.server.print_debug("remote_secret: %s" % peer.remote_secret)
150 self.server.print_debug("ciphertext_bytes: %s" % binascii.hexlify(ciphertext_bytes))
151 self.server.print_debug("digest_check: %s" % binascii.hexlify(digest_check))
152 self.server.print_debug("rejected bad digest: %s" % binascii.hexlify(digest))
153 - return None, None
154 + return None, None, None
155 else:
156 # Return the cleartext
157 serpent = Serpent(self._pad(peer.remote_secret, MAX_SECRET_SIZE))
158 cleartext = serpent.decrypt(ciphertext_bytes).rstrip()
159 self.server.print_debug("received, validated, and decrypted udp packet: %s" % cleartext)
160 self.server.recent.insert(int_ts)
161 - return cleartext, int_ts
162 + # self.server.print_debug("%s %d %s") % cleartext, int_ts, propagate
163 + return cleartext, int_ts, propagate
164
165 def _pad(self, text, size):
166 return text.ljust(size)
167 diff --git a/lib/peer.py b/lib/peer.py
168 index e108328..5c25d25 100644
169 --- a/lib/peer.py
170 +++ b/lib/peer.py
171 @@ -13,10 +13,10 @@ class Peer(object):
172 self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
173 self.infosec = Infosec(server)
174
175 - def send(self, msg, timestamp=None):
176 + def send(self, msg, propagate, timestamp=None):
177 try:
178 full_message = str.encode(msg)
179 self.server.print_debug("sending message: %s" % full_message)
180 - self.socket.sendto(self.infosec.pack(self, full_message, timestamp), (self.address, self.port))
181 + self.socket.sendto(self.infosec.pack(self, full_message, propagate, timestamp), (self.address, self.port))
182 except Exception as ex:
183 print("Exception while attempting to encode message: %s" % ex)
184 diff --git a/lib/server.py b/lib/server.py
185 index 9e8cbe5..bdd44d5 100644
186 --- a/lib/server.py
187 +++ b/lib/server.py
188 @@ -1,4 +1,4 @@
189 -VERSION = "9996"
190 +VERSION = "9995"
191
192 import os
193 import select
194 @@ -141,20 +141,23 @@ class Server(object):
195
196 def handle_udp_data(self, data):
197 for peer in self.peers:
198 - message, timestamp = self.infosec.unpack(peer, data)
199 + message, timestamp, propagate = self.infosec.unpack(peer, data)
200 if(message != None):
201 self.print_debug("valid message from peer: %s" % peer.name)
202 # send the message to all clients
203 for c in self.clients:
204 - self.clients[c].message(message)
205 - # send the message to all other peers
206 - self.rebroadcast(peer, message, timestamp)
207 + # self.clients[c].udp_socket_readable_notification(message)
208 + if (self.clients[c].is_addressed_to_me(message)):
209 + self.clients[c].message(message)
210 + # send the message to all other peers if it should be propagated
211 + if(propagate == True):
212 + self.rebroadcast(peer, message, timestamp)
213 else:
214 self.print_debug("Unknown peer address: %s" % peer.address)
215
216 - def peer_broadcast(self, message):
217 + def peer_broadcast(self, message, propagate=True):
218 for peer in self.peers:
219 - peer.send(message)
220 + peer.send(message, propagate)
221
222 def rebroadcast(self, source_peer, message, timestamp):
223 for peer in self.peers:
224

Alcuin 9996: Testnet and Bugfixes

Sunday, August 22nd, 2021

- 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 --- a/Makefile
5 +++ b/Makefile
6 @@ -12,13 +12,17 @@ dist:
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/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/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/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/g' > alcuin-$(VERSION).diff.escaped
28 diff --git a/alcuin b/alcuin
29 index 44efc5d..a46c007 100755
30 --- a/alcuin
31 +++ b/alcuin
32 @@ -20,6 +20,10 @@ def main(argv):
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 @@ -89,6 +93,8 @@ def main(argv):
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 --- a/lib/client.py
55 +++ b/lib/client.py
56 @@ -3,7 +3,6 @@ import sys
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 @@ -26,7 +25,6 @@ class Client(object):
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 @@ -298,14 +296,17 @@ class Client(object):
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 @@ -444,17 +445,6 @@ class Client(object):
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 @@ -469,14 +459,11 @@ class Client(object):
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 @@ -511,6 +498,8 @@ class Client(object):
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 --- a/lib/infosec.py
137 +++ b/lib/infosec.py
138 @@ -7,9 +7,10 @@ import time
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 @@ -30,22 +31,27 @@ class Infosec(object):
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 @@ -64,3 +70,7 @@ class Infosec(object):
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 --- a/lib/peer.py
195 +++ b/lib/peer.py
196 @@ -1,5 +1,6 @@
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 @@ -12,10 +13,10 @@ class Peer(object):
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 --- a/lib/server.py
225 +++ b/lib/server.py
226 @@ -1,4 +1,4 @@
227 -VERSION = "9997"
228 +VERSION = "9996"
229
230 import os
231 import select
232 @@ -12,10 +12,11 @@ from datetime import datetime
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 @@ -30,6 +31,8 @@ class Server(object):
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 @@ -42,6 +45,11 @@ class Server(object):
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 @@ -50,11 +58,6 @@ class Server(object):
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 @@ -136,6 +139,29 @@ class Server(object):
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 @@ -175,10 +201,8 @@ class Server(object):
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 --- /dev/null
325 +++ b/start_test_net.sh
326 @@ -0,0 +1,6 @@
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 --- /dev/null
337 +++ b/test_net_configs/a.py
338 @@ -0,0 +1,16 @@
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 --- /dev/null
359 +++ b/test_net_configs/b.py
360 @@ -0,0 +1,16 @@
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 --- /dev/null
381 +++ b/test_net_configs/c.py
382 @@ -0,0 +1,16 @@
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

Alcuin 9997: Gossip Support

Tuesday, August 10th, 2021

Alcuin 9997 now uses a gossip protocol to send messages between network nodes. Additionally, the config file has been restructured to allow for using a separate local secret for each peer. Packet sizes have been significantly reduced through usage of the struct.pack/unpack functions to directly write bytes to the packet. Thanks to PeterL for a snippet I used to detect duplicate timestamps.

I don't foresee making any further major releases until asciilifeform publishes his standard.

alcuin-9997-gossip.diff | alcuin-9997.tar.gz

Encryption for alcuin

Sunday, August 1st, 2021

Alcuin 9998 is available as a patch to the genesis, or as a tarball.

This release includes the following changes:

  • Packet encryption using a pure Python implementation of Serpent1
  • Digest now includes the peer secret in order to allow easy dropping of garbage packets
  • Updated config file format - all peer information including port number is now specified in the config file. The --peers command line option has been removed.
1 diff --git a/README.txt b/README.txt
2 index 2d5ff88..5228ab4 100644
3 --- a/README.txt
4 +++ b/README.txt
5 @@ -5,17 +5,13 @@ yet to be completed including but not limited to:
6
7 - gossip style message forwarding
8 - message deduplication
9 -- symmetric encryption of messages
10 -- support for broadcasting several important IRC commands
11 - over the gossip net.
12 - mitigation of hash length extension attacks
13
14 GETTING STARTED
15
16 -1. Copy config.py.example to config.py (nothing in the config file is
17 -used yet, but alcuin will crash if it doesn't exist).
18 +1. Copy config.py.example to config.py and update it appropriately.
19 2. Launch alcuin with something like the following command:
20 -./alcuin --verbose --port=6668 --peers=206.189.163.145
21 +./alcuin --port=6668
22
23 NOTES FOR DIFF/PATCH N00B5
24
25 diff --git a/lib/server.py b/lib/server.py
26 index f2c6206..a609aaf 100644
27 --- a/lib/server.py
28 +++ b/lib/server.py
29 @@ -1,4 +1,4 @@
30 -VERSION = "9999"
31 +VERSION = "9998"
32
33 import os
34 import select
35
36 diff --git a/lib/infosec.py b/lib/infosec.py
37 index 0807633..255d201 100644
38 --- a/lib/infosec.py
39 +++ b/lib/infosec.py
40 @@ -16,7 +16,7 @@ class Infosec(object):
41 serpent = Serpent(self._pad(config.secret, MAX_SECRET_SIZE))
42 padded_message = self._pad(message, MAX_MESSAGE_SIZE)
43 ciphertext = binascii.hexlify(serpent.encrypt(padded_message.encode("ascii")))
44 - digest = hashlib.sha512(ciphertext).hexdigest()
45 + digest = hashlib.sha512(config.secret + ciphertext).hexdigest()
46 print("packing message: %s" % message)
47 print("pack digest: %s" % digest)
48 print("pack digest length: %d" % len(digest))
49 @@ -32,7 +32,7 @@ class Infosec(object):
50 print("received package: %s" % package)
51 received_digest = package[0:128]
52 ciphertext = package[128:1152]
53 - digest = hashlib.sha512(ciphertext).hexdigest()
54 + digest = hashlib.sha512(peer_secret + ciphertext).hexdigest()
55 print("unpack package length: %d" % len(package))
56 print("unpack sender digest: %s" % received_digest)
57 print("unpack sender digest length: %d" % len(received_digest))
58
59 diff --git a/alcuin b/alcuin
60 index bbf9ed4..44efc5d 100755
61 --- a/alcuin
62 +++ b/alcuin
63 @@ -13,7 +13,6 @@ from lib.server import Server
64 from lib.peer import Peer
65 from datetime import datetime
66 from optparse import OptionParser
67 -import config as cfg
68
69
70 def main(argv):
71 @@ -59,10 +58,6 @@ def main(argv):
72 help="listen for UDP packets on X;"
73 " default: 7778")
74 op.add_option(
75 - "--peers",
76 - metavar="X",
77 - help="Broadcast to X (a list of IP addresses separated by comma or whitespace)")
78 - op.add_option(
79 "--statedir",
80 metavar="X",
81 help="save persistent channel state (topic, key) in directory X")
82 @@ -90,8 +85,6 @@ def main(argv):
83 options.ports = "6667"
84 else:
85 options.ports = "6697"
86 - if options.peers is None:
87 - options.peers = ""
88 if options.udp_port is None:
89 options.udp_port = 7778
90 else:
91 @@ -127,13 +120,6 @@ def main(argv):
92 except ValueError:
93 op.error("bad port: %r" % port)
94 options.ports = ports
95 - peers = []
96 - for peer in re.split(r"[,\s]+", options.peers):
97 - try:
98 - peers.append(Peer(peer))
99 - except ValueError:
100 - op.error("bad peer ip: %r" % peer)
101 - options.peers = peers
102 server = Server(options)
103 if options.daemon:
104 server.daemonize()
105 diff --git a/config.py.example b/config.py.example
106 index f9adc62..611913e 100644
107 --- a/config.py.example
108 +++ b/config.py.example
109 @@ -1,4 +1,9 @@
110 secret = "SEEKRIT"
111 -peer_secrets = {
112 - "10.0.0.1":"K33P-0U7!"
113 -}
114 +peers = [
115 + {
116 + "name":"schellenberg",
117 + "secret":"K33P-0U7!",
118 + "address":"10.0.0.1",
119 + "port":7778
120 + }
121 +]
122 diff --git a/lib/client.py b/lib/client.py
123 index 97d8a7f..30eb5ed 100644
124 --- a/lib/client.py
125 +++ b/lib/client.py
126 @@ -446,9 +446,13 @@ class Client(object):
127
128 def udp_data_received(self, address, data):
129 if data:
130 - message = self.infosec.unpack(address, data)
131 - if(message != None):
132 - self.message(message)
133 + for peer in self.server.peers:
134 + if(address == peer.address):
135 + message = self.infosec.unpack(peer, data)
136 + if(message != None):
137 + self.message(message)
138 + return
139 + print("Unknown peer address: " % address)
140
141 def socket_readable_notification(self):
142 try:
143 diff --git a/lib/infosec.py b/lib/infosec.py
144 index f11b992..0807633 100644
145 --- a/lib/infosec.py
146 +++ b/lib/infosec.py
147 @@ -26,28 +26,25 @@ class Infosec(object):
148 print("pack package length: %d" % len(package))
149 return package
150
151 - def unpack(self, address, package):
152 - peer_secret = config.peer_secrets[address]
153 - if(None != peer_secret):
154 - serpent = Serpent(self._pad(peer_secret, MAX_SECRET_SIZE))
155 - print("received package: %s" % package)
156 - received_digest = package[0:128]
157 - ciphertext = package[128:1152]
158 - digest = hashlib.sha512(ciphertext).hexdigest()
159 - print("unpack package length: %d" % len(package))
160 - print("unpack sender digest: %s" % received_digest)
161 - print("unpack sender digest length: %d" % len(received_digest))
162 - print("unpack local digest: %s" % digest)
163 - print("unpack local digest length: %d" % len(digest))
164 - print("unpack ciphertext: %s") % ciphertext
165 - print("unpack ciphertext length: %d") % len(ciphertext)
166 - if(received_digest == digest):
167 - return serpent.decrypt(binascii.unhexlify(ciphertext))
168 - else:
169 - print("unable to validate package: %s" % package)
170 - return None
171 + def unpack(self, peer, package):
172 + peer_secret = peer.secret
173 + serpent = Serpent(self._pad(peer_secret, MAX_SECRET_SIZE))
174 + print("received package: %s" % package)
175 + received_digest = package[0:128]
176 + ciphertext = package[128:1152]
177 + digest = hashlib.sha512(ciphertext).hexdigest()
178 + print("unpack package length: %d" % len(package))
179 + print("unpack sender digest: %s" % received_digest)
180 + print("unpack sender digest length: %d" % len(received_digest))
181 + print("unpack local digest: %s" % digest)
182 + print("unpack local digest length: %d" % len(digest))
183 + print("unpack ciphertext: %s") % ciphertext
184 + print("unpack ciphertext length: %d") % len(ciphertext)
185 + if(received_digest == digest):
186 + return serpent.decrypt(binascii.unhexlify(ciphertext))
187 else:
188 - print("received message from unknown peer: %s" % address)
189 + print("unable to validate package: %s" % package)
190 + return None
191
192 def _pad(self, text, size):
193 return text.ljust(size)
194 diff --git a/lib/peer.py b/lib/peer.py
195 index 4a64ed7..fcb0f0c 100644
196 --- a/lib/peer.py
197 +++ b/lib/peer.py
198 @@ -1,13 +1,16 @@
199 import socket
200 from infosec import Infosec
201
202 -class Peer(object):
203 - def __init__(self, address):
204 - self.address = address
205 - self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
206 - self.infosec = Infosec()
207 +class Peer(object):
208 + def __init__(self, peer_entry):
209 + self.name = peer_entry["name"]
210 + self.address = peer_entry["address"]
211 + self.secret = peer_entry["secret"]
212 + self.port = peer_entry["port"]
213 + self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
214 + self.infosec = Infosec()
215
216 - def send(self, client, msg):
217 - full_message = str.encode(":%s %s" % (client.nickname, msg))
218 - print("sending formatted_msg: %s" % full_message)
219 - self.socket.sendto(self.infosec.pack(full_message), (self.address, 7778))
220 + def send(self, client, msg):
221 + full_message = str.encode(":%s %s" % (client.nickname, msg))
222 + print("sending formatted_msg: %s" % full_message)
223 + self.socket.sendto(self.infosec.pack(full_message), (self.address, self.port))
224 diff --git a/lib/server.py b/lib/server.py
225 index a248f1a..f2c6206 100644
226 --- a/lib/server.py
227 +++ b/lib/server.py
228 @@ -15,11 +15,11 @@ from lib.infosec import PACKET_SIZE
229 from lib.infosec import Infosec
230 from lib.peer import Peer
231 from funcs import *
232 +import config as cfg
233
234 class Server(object):
235 def __init__(self, options):
236 self.ports = options.ports
237 - self.peers = options.peers
238 self.udp_port = options.udp_port
239 self.password = options.password
240 self.ssl_pem_file = options.ssl_pem_file
241 @@ -41,6 +41,9 @@ class Server(object):
242 self.channels = {} # irc_lower(Channel name) --> Channel instance.
243 self.clients = {} # Socket --> Client instance..peers = ""
244 self.nicknames = {} # irc_lower(Nickname) --> Client instance.
245 + self.peers = []
246 + for peer_entry in cfg.peers:
247 + self.peers.append(Peer(peer_entry))
248 if self.logdir:
249 create_directory(self.logdir)
250 if self.statedir:
251
252 diff --git a/lib/client.py b/lib/client.py
253 index cfc5331..97d8a7f 100644
254 --- a/lib/client.py
255 +++ b/lib/client.py
256 @@ -444,9 +444,9 @@ class Client(object):
257 except KeyError:
258 self.reply("421 %s %s :Unknown command" % (self.nickname, command))
259
260 - def udp_data_received(self, data):
261 + def udp_data_received(self, address, data):
262 if data:
263 - message = self.infosec.unpack(data)
264 + message = self.infosec.unpack(address, data)
265 if(message != None):
266 self.message(message)
267
268 diff --git a/lib/infosec.py b/lib/infosec.py
269 index 6e87ca6..f11b992 100644
270 --- a/lib/infosec.py
271 +++ b/lib/infosec.py
272 @@ -1,29 +1,54 @@
273 import hashlib
274 -PACKET_SIZE = 1024
275 +import lib.serpent
276 +
277 +from lib.serpent import Serpent
278 +import config
279 +import binascii
280 +PACKET_SIZE = 1152
281 MAX_MESSAGE_SIZE = 512
282 +MAX_SECRET_SIZE = 24
283
284 class Infosec(object):
285 #def __init__(self):
286 # do nothing
287
288 def pack(self, message):
289 - digest = hashlib.sha512(self._pad(message)).hexdigest()
290 - return digest + message
291 + serpent = Serpent(self._pad(config.secret, MAX_SECRET_SIZE))
292 + padded_message = self._pad(message, MAX_MESSAGE_SIZE)
293 + ciphertext = binascii.hexlify(serpent.encrypt(padded_message.encode("ascii")))
294 + digest = hashlib.sha512(ciphertext).hexdigest()
295 + print("packing message: %s" % message)
296 + print("pack digest: %s" % digest)
297 + print("pack digest length: %d" % len(digest))
298 + print("pack ciphertext: %s" % ciphertext)
299 + print("pack ciphertext length: %d" % len(ciphertext))
300 + package = digest + ciphertext
301 + print("pack package length: %d" % len(package))
302 + return package
303
304 - def unpack(self, package):
305 - print("received package: %s" % package)
306 - received_digest = package[0:128]
307 - message = package[128:1023]
308 - digest = hashlib.sha512(self._pad(message)).hexdigest()
309 - print("received_digest: %s" % received_digest)
310 - print("digest: %s" % digest)
311 - print("message: %s") % message
312 - if(received_digest == digest):
313 - return message
314 + def unpack(self, address, package):
315 + peer_secret = config.peer_secrets[address]
316 + if(None != peer_secret):
317 + serpent = Serpent(self._pad(peer_secret, MAX_SECRET_SIZE))
318 + print("received package: %s" % package)
319 + received_digest = package[0:128]
320 + ciphertext = package[128:1152]
321 + digest = hashlib.sha512(ciphertext).hexdigest()
322 + print("unpack package length: %d" % len(package))
323 + print("unpack sender digest: %s" % received_digest)
324 + print("unpack sender digest length: %d" % len(received_digest))
325 + print("unpack local digest: %s" % digest)
326 + print("unpack local digest length: %d" % len(digest))
327 + print("unpack ciphertext: %s") % ciphertext
328 + print("unpack ciphertext length: %d") % len(ciphertext)
329 + if(received_digest == digest):
330 + return serpent.decrypt(binascii.unhexlify(ciphertext))
331 + else:
332 + print("unable to validate package: %s" % package)
333 + return None
334 else:
335 - print("unable to validate package: %s" % package)
336 - return None
337 + print("received message from unknown peer: %s" % address)
338
339 - def _pad(self, text):
340 - return str(text.ljust(MAX_MESSAGE_SIZE)).encode("ascii")
341 + def _pad(self, text, size):
342 + return text.ljust(size)
343
344 diff --git a/lib/serpent.py b/lib/serpent.py
345 new file mode 100644
346 index 0000000..c7c9f83
347 --- /dev/null
348 +++ b/lib/serpent.py
349 @@ -0,0 +1,2998 @@
350 +## serpent.py - pure Python implementation of the Serpent algorithm.
351 +## Bjorn Edstrom <be@bjrn.se> 13 december 2007.
352 +##
353 +## Copyrights
354 +## ==========
355 +##
356 +## This code is a derived from an implementation by Dr Brian Gladman
357 +## (gladman@seven77.demon.co.uk) which is subject to the following license.
358 +## This Python implementation is not subject to any other license.
359 +##
360 +##/* This is an independent implementation of the encryption algorithm:
361 +## *
362 +## * Serpent by Ross Anderson, Eli Biham and Lars Knudsen
363 +## *
364 +## * which is a candidate algorithm in the Advanced Encryption Standard
365 +## * programme of the US National Institute of Standards and Technology
366 +## *
367 +## * Copyright in this implementation is held by Dr B R Gladman but I
368 +## * hereby give permission for its free direct or derivative use subject
369 +## * to acknowledgment of its origin and compliance with any conditions
370 +## * that the originators of the algorithm place on its exploitation.
371 +## *
372 +## * Dr Brian Gladman (gladman@seven77.demon.co.uk) 14th January 1999
373 +## */
374 +##
375 +## The above copyright notice must not be removed.
376 +##
377 +## Information
378 +## ===========
379 +##
380 +## Anyone thinking of using this code should reconsider. It's slow.
381 +## Try python-mcrypt instead. In case a faster library is not installed
382 +## on the target system, this code can be used as a portable fallback.
383 +
384 +import binascii
385 +import base64
386 +
387 +block_size = 16
388 +key_size = 32
389 +
390 +class Serpent:
391 +
392 + def __init__(self, key=None):
393 + """Serpent."""
394 +
395 + if key:
396 + self.set_key(key)
397 +
398 +
399 + def set_key(self, key):
400 + """Init."""
401 +
402 + key_len = len(key)
403 + if key_len % 4:
404 + # XXX: add padding?
405 + raise KeyError, "key not a multiple of 4"
406 + if key_len > 32:
407 + # XXX: prune?
408 + raise KeyError, "key_len > 32"
409 +
410 + self.key_context = [0] * 140
411 +
412 + key_word32 = [0] * 32
413 + i = 0
414 + while key:
415 + key_word32[i] = struct.unpack("<L", key[0:4])[0]
416 + key = key[4:]
417 + i += 1
418 +
419 + set_key(self.key_context, key_word32, key_len)
420 + #print(map(hex,self.key_context))
421 +
422 +
423 + def decrypt(self, block):
424 + """Decrypt blocks."""
425 +
426 + if len(block) % 16:
427 + raise ValueError, "block size must be a multiple of 16"
428 +
429 + plaintext = ''
430 +
431 + while block:
432 + a, b, c, d = struct.unpack("<4L", block[:16])
433 + temp = [a, b, c, d]
434 + decrypt(self.key_context, temp)
435 + plaintext += struct.pack("<4L", *temp)
436 + block = block[16:]
437 +
438 + return plaintext
439 +
440 +
441 + def encrypt(self, block):
442 + """Encrypt blocks."""
443 +
444 + if len(block) % 16:
445 + raise ValueError, "block size must be a multiple of 16"
446 +
447 + ciphertext = ''
448 +
449 + while block:
450 + a, b, c, d = struct.unpack("<4L", block[0:16])
451 + temp = [a, b, c, d]
452 + encrypt(self.key_context, temp)
453 + ciphertext += struct.pack("<4L", *temp)
454 + block = block[16:]
455 +
456 + return ciphertext
457 +
458 +
459 + def get_name(self):
460 + """Return the name of the cipher."""
461 +
462 + return "Serpent"
463 +
464 +
465 + def get_block_size(self):
466 + """Get cipher block size in bytes."""
467 +
468 + return 16
469 +
470 +
471 + def get_key_size(self):
472 + """Get cipher key size in bytes."""
473 +
474 + return 32
475 +
476 +
477 +#
478 +# Private.
479 +#
480 +
481 +import struct
482 +import sys
483 +
484 +WORD_BIGENDIAN = 0
485 +if sys.byteorder == 'big':
486 + WORD_BIGENDIAN = 1
487 +
488 +def rotr32(x, n):
489 + return (x >> n) | ((x << (32 - n)) & 0xFFFFFFFF)
490 +
491 +def rotl32(x, n):
492 + return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))
493 +
494 +def byteswap32(x):
495 + return ((x & 0xff) << 24) | (((x >> 8) & 0xff) << 16) | \
496 + (((x >> 16) & 0xff) << 8) | ((x >> 24) & 0xff)
497 +
498 +def set_key(l_key, key, key_len):
499 + key_len *= 8
500 + if key_len > 256:
501 + return False
502 +
503 + i = 0
504 + lk = (key_len + 31) / 32
505 + while i < lk:
506 + l_key[i] = key[i]
507 + if WORD_BIGENDIAN:
508 + l_key[i] = byteswap32(key[i])
509 + i += 1
510 +
511 + if key_len < 256:
512 + while i < 8:
513 + l_key[i] = 0
514 + i += 1
515 + i = key_len / 32
516 + lk = 1 << (key_len % 32)
517 + l_key[i] = (l_key[i] & (lk - 1)) | lk
518 + for i in xrange(132):
519 + lk = l_key[i] ^ l_key[i + 3] ^ l_key[i + 5] ^ l_key[i + 7] ^ 0x9e3779b9 ^ i
520 + l_key[i + 8] = ((lk << 11) & 0xFFFFFFFF) | (lk >> 21)
521 +
522 + key = l_key
523 + # serpent_generate.py
524 + a = key[4 * 0 + 8]
525 + b = key[4 * 0 + 9]
526 + c = key[4 * 0 + 10]
527 + d = key[4 * 0 + 11]
528 + e = 0
529 + f = 0
530 + g = 0
531 + h = 0
532 + t1 = 0
533 + t2 = 0
534 + t3 = 0
535 + t4 = 0
536 + t5 = 0
537 + t6 = 0
538 + t7 = 0
539 + t8 = 0
540 + t9 = 0
541 + t10 = 0
542 + t11 = 0
543 + t12 = 0
544 + t13 = 0
545 + t14 = 0
546 + t15 = 0
547 + t16 = 0
548 + t1 = a ^ c;
549 + t2 = d ^ t1;
550 + t3 = a & t2;
551 + t4 = d ^ t3;
552 + t5 = b & t4;
553 + g = t2 ^ t5;
554 + t7 = a | g;
555 + t8 = b | d;
556 + t11 = a | d;
557 + t9 = t4 & t7;
558 + f = t8 ^ t9;
559 + t12 = b ^ t11;
560 + t13 = g ^ t9;
561 + t15 = t3 ^ t8;
562 + h = t12 ^ t13;
563 + t16 = c & t15;
564 + e = t12 ^ t16
565 + key[4 * 0 + 8] = e
566 + key[4 * 0 + 9] = f
567 + key[4 * 0 + 10] = g
568 + key[4 * 0 + 11] = h
569 + a = key[4 * 1 + 8]
570 + b = key[4 * 1 + 9]
571 + c = key[4 * 1 + 10]
572 + d = key[4 * 1 + 11]
573 + t1 = (~a) % 0x100000000;
574 + t2 = b ^ d;
575 + t3 = c & t1;
576 + t13 = d | t1;
577 + e = t2 ^ t3;
578 + t5 = c ^ t1;
579 + t6 = c ^ e;
580 + t7 = b & t6;
581 + t10 = e | t5;
582 + h = t5 ^ t7;
583 + t9 = d | t7;
584 + t11 = t9 & t10;
585 + t14 = t2 ^ h;
586 + g = a ^ t11;
587 + t15 = g ^ t13;
588 + f = t14 ^ t15
589 + key[4 * 1 + 8] = e
590 + key[4 * 1 + 9] = f
591 + key[4 * 1 + 10] = g
592 + key[4 * 1 + 11] = h
593 + a = key[4 * 2 + 8]
594 + b = key[4 * 2 + 9]
595 + c = key[4 * 2 + 10]
596 + d = key[4 * 2 + 11]
597 + t1 = (~a) % 0x100000000;
598 + t2 = b ^ t1;
599 + t3 = a | t2;
600 + t4 = d | t2;
601 + t5 = c ^ t3;
602 + g = d ^ t5;
603 + t7 = b ^ t4;
604 + t8 = t2 ^ g;
605 + t9 = t5 & t7;
606 + h = t8 ^ t9;
607 + t11 = t5 ^ t7;
608 + f = h ^ t11;
609 + t13 = t8 & t11;
610 + e = t5 ^ t13
611 + key[4 * 2 + 8] = e
612 + key[4 * 2 + 9] = f
613 + key[4 * 2 + 10] = g
614 + key[4 * 2 + 11] = h
615 + a = key[4 * 3 + 8]
616 + b = key[4 * 3 + 9]
617 + c = key[4 * 3 + 10]
618 + d = key[4 * 3 + 11]
619 + t1 = a ^ d;
620 + t2 = a & d;
621 + t3 = c ^ t1;
622 + t6 = b & t1;
623 + t4 = b ^ t3;
624 + t10 = (~t3) % 0x100000000;
625 + h = t2 ^ t4;
626 + t7 = a ^ t6;
627 + t14 = (~t7) % 0x100000000;
628 + t8 = c | t7;
629 + t11 = t3 ^ t7;
630 + g = t4 ^ t8;
631 + t12 = h & t11;
632 + f = t10 ^ t12;
633 + e = t12 ^ t14
634 + key[4 * 3 + 8] = e
635 + key[4 * 3 + 9] = f
636 + key[4 * 3 + 10] = g
637 + key[4 * 3 + 11] = h
638 + a = key[4 * 4 + 8]
639 + b = key[4 * 4 + 9]
640 + c = key[4 * 4 + 10]
641 + d = key[4 * 4 + 11]
642 + t1 = (~c) % 0x100000000;
643 + t2 = b ^ c;
644 + t3 = b | t1;
645 + t4 = d ^ t3;
646 + t5 = a & t4;
647 + t7 = a ^ d;
648 + h = t2 ^ t5;
649 + t8 = b ^ t5;
650 + t9 = t2 | t8;
651 + t11 = d & t3;
652 + f = t7 ^ t9;
653 + t12 = t5 ^ f;
654 + t15 = t1 | t4;
655 + t13 = h & t12;
656 + g = t11 ^ t13;
657 + t16 = t12 ^ g;
658 + e = t15 ^ t16
659 + key[4 * 4 + 8] = e
660 + key[4 * 4 + 9] = f
661 + key[4 * 4 + 10] = g
662 + key[4 * 4 + 11] = h
663 + a = key[4 * 5 + 8]
664 + b = key[4 * 5 + 9]
665 + c = key[4 * 5 + 10]
666 + d = key[4 * 5 + 11]
667 + t1 = (~a) % 0x100000000;
668 + t2 = a ^ d;
669 + t3 = b ^ t2;
670 + t4 = t1 | t2;
671 + t5 = c ^ t4;
672 + f = b ^ t5;
673 + t13 = (~t5) % 0x100000000;
674 + t7 = t2 | f;
675 + t8 = d ^ t7;
676 + t9 = t5 & t8;
677 + g = t3 ^ t9;
678 + t11 = t5 ^ t8;
679 + e = g ^ t11;
680 + t14 = t3 & t11;
681 + h = t13 ^ t14
682 + key[4 * 5 + 8] = e
683 + key[4 * 5 + 9] = f
684 + key[4 * 5 + 10] = g
685 + key[4 * 5 + 11] = h
686 + a = key[4 * 6 + 8]
687 + b = key[4 * 6 + 9]
688 + c = key[4 * 6 + 10]
689 + d = key[4 * 6 + 11]
690 + t1 = (~a) % 0x100000000;
691 + t2 = a ^ b;
692 + t3 = a ^ d;
693 + t4 = c ^ t1;
694 + t5 = t2 | t3;
695 + e = t4 ^ t5;
696 + t7 = d & e;
697 + t8 = t2 ^ e;
698 + t10 = t1 | e;
699 + f = t7 ^ t8;
700 + t11 = t2 | t7;
701 + t12 = t3 ^ t10;
702 + t14 = b ^ t7;
703 + g = t11 ^ t12;
704 + t15 = f & t12;
705 + h = t14 ^ t15
706 + key[4 * 6 + 8] = e
707 + key[4 * 6 + 9] = f
708 + key[4 * 6 + 10] = g
709 + key[4 * 6 + 11] = h
710 + a = key[4 * 7 + 8]
711 + b = key[4 * 7 + 9]
712 + c = key[4 * 7 + 10]
713 + d = key[4 * 7 + 11]
714 + t1 = a ^ d;
715 + t2 = d & t1;
716 + t3 = c ^ t2;
717 + t4 = b | t3;
718 + h = t1 ^ t4;
719 + t6 = (~b) % 0x100000000;
720 + t7 = t1 | t6;
721 + e = t3 ^ t7;
722 + t9 = a & e;
723 + t10 = t1 ^ t6;
724 + t11 = t4 & t10;
725 + g = t9 ^ t11;
726 + t13 = a ^ t3;
727 + t14 = t10 & g;
728 + f = t13 ^ t14
729 + key[4 * 7 + 8] = e
730 + key[4 * 7 + 9] = f
731 + key[4 * 7 + 10] = g
732 + key[4 * 7 + 11] = h
733 + a = key[4 * 8 + 8]
734 + b = key[4 * 8 + 9]
735 + c = key[4 * 8 + 10]
736 + d = key[4 * 8 + 11]
737 + t1 = a ^ c;
738 + t2 = d ^ t1;
739 + t3 = a & t2;
740 + t4 = d ^ t3;
741 + t5 = b & t4;
742 + g = t2 ^ t5;
743 + t7 = a | g;
744 + t8 = b | d;
745 + t11 = a | d;
746 + t9 = t4 & t7;
747 + f = t8 ^ t9;
748 + t12 = b ^ t11;
749 + t13 = g ^ t9;
750 + t15 = t3 ^ t8;
751 + h = t12 ^ t13;
752 + t16 = c & t15;
753 + e = t12 ^ t16
754 + key[4 * 8 + 8] = e
755 + key[4 * 8 + 9] = f
756 + key[4 * 8 + 10] = g
757 + key[4 * 8 + 11] = h
758 + a = key[4 * 9 + 8]
759 + b = key[4 * 9 + 9]
760 + c = key[4 * 9 + 10]
761 + d = key[4 * 9 + 11]
762 + t1 = (~a) % 0x100000000;
763 + t2 = b ^ d;
764 + t3 = c & t1;
765 + t13 = d | t1;
766 + e = t2 ^ t3;
767 + t5 = c ^ t1;
768 + t6 = c ^ e;
769 + t7 = b & t6;
770 + t10 = e | t5;
771 + h = t5 ^ t7;
772 + t9 = d | t7;
773 + t11 = t9 & t10;
774 + t14 = t2 ^ h;
775 + g = a ^ t11;
776 + t15 = g ^ t13;
777 + f = t14 ^ t15
778 + key[4 * 9 + 8] = e
779 + key[4 * 9 + 9] = f
780 + key[4 * 9 + 10] = g
781 + key[4 * 9 + 11] = h
782 + a = key[4 * 10 + 8]
783 + b = key[4 * 10 + 9]
784 + c = key[4 * 10 + 10]
785 + d = key[4 * 10 + 11]
786 + t1 = (~a) % 0x100000000;
787 + t2 = b ^ t1;
788 + t3 = a | t2;
789 + t4 = d | t2;
790 + t5 = c ^ t3;
791 + g = d ^ t5;
792 + t7 = b ^ t4;
793 + t8 = t2 ^ g;
794 + t9 = t5 & t7;
795 + h = t8 ^ t9;
796 + t11 = t5 ^ t7;
797 + f = h ^ t11;
798 + t13 = t8 & t11;
799 + e = t5 ^ t13
800 + key[4 * 10 + 8] = e
801 + key[4 * 10 + 9] = f
802 + key[4 * 10 + 10] = g
803 + key[4 * 10 + 11] = h
804 + a = key[4 * 11 + 8]
805 + b = key[4 * 11 + 9]
806 + c = key[4 * 11 + 10]
807 + d = key[4 * 11 + 11]
808 + t1 = a ^ d;
809 + t2 = a & d;
810 + t3 = c ^ t1;
811 + t6 = b & t1;
812 + t4 = b ^ t3;
813 + t10 = (~t3) % 0x100000000;
814 + h = t2 ^ t4;
815 + t7 = a ^ t6;
816 + t14 = (~t7) % 0x100000000;
817 + t8 = c | t7;
818 + t11 = t3 ^ t7;
819 + g = t4 ^ t8;
820 + t12 = h & t11;
821 + f = t10 ^ t12;
822 + e = t12 ^ t14
823 + key[4 * 11 + 8] = e
824 + key[4 * 11 + 9] = f
825 + key[4 * 11 + 10] = g
826 + key[4 * 11 + 11] = h
827 + a = key[4 * 12 + 8]
828 + b = key[4 * 12 + 9]
829 + c = key[4 * 12 + 10]
830 + d = key[4 * 12 + 11]
831 + t1 = (~c) % 0x100000000;
832 + t2 = b ^ c;
833 + t3 = b | t1;
834 + t4 = d ^ t3;
835 + t5 = a & t4;
836 + t7 = a ^ d;
837 + h = t2 ^ t5;
838 + t8 = b ^ t5;
839 + t9 = t2 | t8;
840 + t11 = d & t3;
841 + f = t7 ^ t9;
842 + t12 = t5 ^ f;
843 + t15 = t1 | t4;
844 + t13 = h & t12;
845 + g = t11 ^ t13;
846 + t16 = t12 ^ g;
847 + e = t15 ^ t16
848 + key[4 * 12 + 8] = e
849 + key[4 * 12 + 9] = f
850 + key[4 * 12 + 10] = g
851 + key[4 * 12 + 11] = h
852 + a = key[4 * 13 + 8]
853 + b = key[4 * 13 + 9]
854 + c = key[4 * 13 + 10]
855 + d = key[4 * 13 + 11]
856 + t1 = (~a) % 0x100000000;
857 + t2 = a ^ d;
858 + t3 = b ^ t2;
859 + t4 = t1 | t2;
860 + t5 = c ^ t4;
861 + f = b ^ t5;
862 + t13 = (~t5) % 0x100000000;
863 + t7 = t2 | f;
864 + t8 = d ^ t7;
865 + t9 = t5 & t8;
866 + g = t3 ^ t9;
867 + t11 = t5 ^ t8;
868 + e = g ^ t11;
869 + t14 = t3 & t11;
870 + h = t13 ^ t14
871 + key[4 * 13 + 8] = e
872 + key[4 * 13 + 9] = f
873 + key[4 * 13 + 10] = g
874 + key[4 * 13 + 11] = h
875 + a = key[4 * 14 + 8]
876 + b = key[4 * 14 + 9]
877 + c = key[4 * 14 + 10]
878 + d = key[4 * 14 + 11]
879 + t1 = (~a) % 0x100000000;
880 + t2 = a ^ b;
881 + t3 = a ^ d;
882 + t4 = c ^ t1;
883 + t5 = t2 | t3;
884 + e = t4 ^ t5;
885 + t7 = d & e;
886 + t8 = t2 ^ e;
887 + t10 = t1 | e;
888 + f = t7 ^ t8;
889 + t11 = t2 | t7;
890 + t12 = t3 ^ t10;
891 + t14 = b ^ t7;
892 + g = t11 ^ t12;
893 + t15 = f & t12;
894 + h = t14 ^ t15
895 + key[4 * 14 + 8] = e
896 + key[4 * 14 + 9] = f
897 + key[4 * 14 + 10] = g
898 + key[4 * 14 + 11] = h
899 + a = key[4 * 15 + 8]
900 + b = key[4 * 15 + 9]
901 + c = key[4 * 15 + 10]
902 + d = key[4 * 15 + 11]
903 + t1 = a ^ d;
904 + t2 = d & t1;
905 + t3 = c ^ t2;
906 + t4 = b | t3;
907 + h = t1 ^ t4;
908 + t6 = (~b) % 0x100000000;
909 + t7 = t1 | t6;
910 + e = t3 ^ t7;
911 + t9 = a & e;
912 + t10 = t1 ^ t6;
913 + t11 = t4 & t10;
914 + g = t9 ^ t11;
915 + t13 = a ^ t3;
916 + t14 = t10 & g;
917 + f = t13 ^ t14
918 + key[4 * 15 + 8] = e
919 + key[4 * 15 + 9] = f
920 + key[4 * 15 + 10] = g
921 + key[4 * 15 + 11] = h
922 + a = key[4 * 16 + 8]
923 + b = key[4 * 16 + 9]
924 + c = key[4 * 16 + 10]
925 + d = key[4 * 16 + 11]
926 + t1 = a ^ c;
927 + t2 = d ^ t1;
928 + t3 = a & t2;
929 + t4 = d ^ t3;
930 + t5 = b & t4;
931 + g = t2 ^ t5;
932 + t7 = a | g;
933 + t8 = b | d;
934 + t11 = a | d;
935 + t9 = t4 & t7;
936 + f = t8 ^ t9;
937 + t12 = b ^ t11;
938 + t13 = g ^ t9;
939 + t15 = t3 ^ t8;
940 + h = t12 ^ t13;
941 + t16 = c & t15;
942 + e = t12 ^ t16
943 + key[4 * 16 + 8] = e
944 + key[4 * 16 + 9] = f
945 + key[4 * 16 + 10] = g
946 + key[4 * 16 + 11] = h
947 + a = key[4 * 17 + 8]
948 + b = key[4 * 17 + 9]
949 + c = key[4 * 17 + 10]
950 + d = key[4 * 17 + 11]
951 + t1 = (~a) % 0x100000000;
952 + t2 = b ^ d;
953 + t3 = c & t1;
954 + t13 = d | t1;
955 + e = t2 ^ t3;
956 + t5 = c ^ t1;
957 + t6 = c ^ e;
958 + t7 = b & t6;
959 + t10 = e | t5;
960 + h = t5 ^ t7;
961 + t9 = d | t7;
962 + t11 = t9 & t10;
963 + t14 = t2 ^ h;
964 + g = a ^ t11;
965 + t15 = g ^ t13;
966 + f = t14 ^ t15
967 + key[4 * 17 + 8] = e
968 + key[4 * 17 + 9] = f
969 + key[4 * 17 + 10] = g
970 + key[4 * 17 + 11] = h
971 + a = key[4 * 18 + 8]
972 + b = key[4 * 18 + 9]
973 + c = key[4 * 18 + 10]
974 + d = key[4 * 18 + 11]
975 + t1 = (~a) % 0x100000000;
976 + t2 = b ^ t1;
977 + t3 = a | t2;
978 + t4 = d | t2;
979 + t5 = c ^ t3;
980 + g = d ^ t5;
981 + t7 = b ^ t4;
982 + t8 = t2 ^ g;
983 + t9 = t5 & t7;
984 + h = t8 ^ t9;
985 + t11 = t5 ^ t7;
986 + f = h ^ t11;
987 + t13 = t8 & t11;
988 + e = t5 ^ t13
989 + key[4 * 18 + 8] = e
990 + key[4 * 18 + 9] = f
991 + key[4 * 18 + 10] = g
992 + key[4 * 18 + 11] = h
993 + a = key[4 * 19 + 8]
994 + b = key[4 * 19 + 9]
995 + c = key[4 * 19 + 10]
996 + d = key[4 * 19 + 11]
997 + t1 = a ^ d;
998 + t2 = a & d;
999 + t3 = c ^ t1;
1000 + t6 = b & t1;
1001 + t4 = b ^ t3;
1002 + t10 = (~t3) % 0x100000000;
1003 + h = t2 ^ t4;
1004 + t7 = a ^ t6;
1005 + t14 = (~t7) % 0x100000000;
1006 + t8 = c | t7;
1007 + t11 = t3 ^ t7;
1008 + g = t4 ^ t8;
1009 + t12 = h & t11;
1010 + f = t10 ^ t12;
1011 + e = t12 ^ t14
1012 + key[4 * 19 + 8] = e
1013 + key[4 * 19 + 9] = f
1014 + key[4 * 19 + 10] = g
1015 + key[4 * 19 + 11] = h
1016 + a = key[4 * 20 + 8]
1017 + b = key[4 * 20 + 9]
1018 + c = key[4 * 20 + 10]
1019 + d = key[4 * 20 + 11]
1020 + t1 = (~c) % 0x100000000;
1021 + t2 = b ^ c;
1022 + t3 = b | t1;
1023 + t4 = d ^ t3;
1024 + t5 = a & t4;
1025 + t7 = a ^ d;
1026 + h = t2 ^ t5;
1027 + t8 = b ^ t5;
1028 + t9 = t2 | t8;
1029 + t11 = d & t3;
1030 + f = t7 ^ t9;
1031 + t12 = t5 ^ f;
1032 + t15 = t1 | t4;
1033 + t13 = h & t12;
1034 + g = t11 ^ t13;
1035 + t16 = t12 ^ g;
1036 + e = t15 ^ t16
1037 + key[4 * 20 + 8] = e
1038 + key[4 * 20 + 9] = f
1039 + key[4 * 20 + 10] = g
1040 + key[4 * 20 + 11] = h
1041 + a = key[4 * 21 + 8]
1042 + b = key[4 * 21 + 9]
1043 + c = key[4 * 21 + 10]
1044 + d = key[4 * 21 + 11]
1045 + t1 = (~a) % 0x100000000;
1046 + t2 = a ^ d;
1047 + t3 = b ^ t2;
1048 + t4 = t1 | t2;
1049 + t5 = c ^ t4;
1050 + f = b ^ t5;
1051 + t13 = (~t5) % 0x100000000;
1052 + t7 = t2 | f;
1053 + t8 = d ^ t7;
1054 + t9 = t5 & t8;
1055 + g = t3 ^ t9;
1056 + t11 = t5 ^ t8;
1057 + e = g ^ t11;
1058 + t14 = t3 & t11;
1059 + h = t13 ^ t14
1060 + key[4 * 21 + 8] = e
1061 + key[4 * 21 + 9] = f
1062 + key[4 * 21 + 10] = g
1063 + key[4 * 21 + 11] = h
1064 + a = key[4 * 22 + 8]
1065 + b = key[4 * 22 + 9]
1066 + c = key[4 * 22 + 10]
1067 + d = key[4 * 22 + 11]
1068 + t1 = (~a) % 0x100000000;
1069 + t2 = a ^ b;
1070 + t3 = a ^ d;
1071 + t4 = c ^ t1;
1072 + t5 = t2 | t3;
1073 + e = t4 ^ t5;
1074 + t7 = d & e;
1075 + t8 = t2 ^ e;
1076 + t10 = t1 | e;
1077 + f = t7 ^ t8;
1078 + t11 = t2 | t7;
1079 + t12 = t3 ^ t10;
1080 + t14 = b ^ t7;
1081 + g = t11 ^ t12;
1082 + t15 = f & t12;
1083 + h = t14 ^ t15
1084 + key[4 * 22 + 8] = e
1085 + key[4 * 22 + 9] = f
1086 + key[4 * 22 + 10] = g
1087 + key[4 * 22 + 11] = h
1088 + a = key[4 * 23 + 8]
1089 + b = key[4 * 23 + 9]
1090 + c = key[4 * 23 + 10]
1091 + d = key[4 * 23 + 11]
1092 + t1 = a ^ d;
1093 + t2 = d & t1;
1094 + t3 = c ^ t2;
1095 + t4 = b | t3;
1096 + h = t1 ^ t4;
1097 + t6 = (~b) % 0x100000000;
1098 + t7 = t1 | t6;
1099 + e = t3 ^ t7;
1100 + t9 = a & e;
1101 + t10 = t1 ^ t6;
1102 + t11 = t4 & t10;
1103 + g = t9 ^ t11;
1104 + t13 = a ^ t3;
1105 + t14 = t10 & g;
1106 + f = t13 ^ t14
1107 + key[4 * 23 + 8] = e
1108 + key[4 * 23 + 9] = f
1109 + key[4 * 23 + 10] = g
1110 + key[4 * 23 + 11] = h
1111 + a = key[4 * 24 + 8]
1112 + b = key[4 * 24 + 9]
1113 + c = key[4 * 24 + 10]
1114 + d = key[4 * 24 + 11]
1115 + t1 = a ^ c;
1116 + t2 = d ^ t1;
1117 + t3 = a & t2;
1118 + t4 = d ^ t3;
1119 + t5 = b & t4;
1120 + g = t2 ^ t5;
1121 + t7 = a | g;
1122 + t8 = b | d;
1123 + t11 = a | d;
1124 + t9 = t4 & t7;
1125 + f = t8 ^ t9;
1126 + t12 = b ^ t11;
1127 + t13 = g ^ t9;
1128 + t15 = t3 ^ t8;
1129 + h = t12 ^ t13;
1130 + t16 = c & t15;
1131 + e = t12 ^ t16
1132 + key[4 * 24 + 8] = e
1133 + key[4 * 24 + 9] = f
1134 + key[4 * 24 + 10] = g
1135 + key[4 * 24 + 11] = h
1136 + a = key[4 * 25 + 8]
1137 + b = key[4 * 25 + 9]
1138 + c = key[4 * 25 + 10]
1139 + d = key[4 * 25 + 11]
1140 + t1 = (~a) % 0x100000000;
1141 + t2 = b ^ d;
1142 + t3 = c & t1;
1143 + t13 = d | t1;
1144 + e = t2 ^ t3;
1145 + t5 = c ^ t1;
1146 + t6 = c ^ e;
1147 + t7 = b & t6;
1148 + t10 = e | t5;
1149 + h = t5 ^ t7;
1150 + t9 = d | t7;
1151 + t11 = t9 & t10;
1152 + t14 = t2 ^ h;
1153 + g = a ^ t11;
1154 + t15 = g ^ t13;
1155 + f = t14 ^ t15
1156 + key[4 * 25 + 8] = e
1157 + key[4 * 25 + 9] = f
1158 + key[4 * 25 + 10] = g
1159 + key[4 * 25 + 11] = h
1160 + a = key[4 * 26 + 8]
1161 + b = key[4 * 26 + 9]
1162 + c = key[4 * 26 + 10]
1163 + d = key[4 * 26 + 11]
1164 + t1 = (~a) % 0x100000000;
1165 + t2 = b ^ t1;
1166 + t3 = a | t2;
1167 + t4 = d | t2;
1168 + t5 = c ^ t3;
1169 + g = d ^ t5;
1170 + t7 = b ^ t4;
1171 + t8 = t2 ^ g;
1172 + t9 = t5 & t7;
1173 + h = t8 ^ t9;
1174 + t11 = t5 ^ t7;
1175 + f = h ^ t11;
1176 + t13 = t8 & t11;
1177 + e = t5 ^ t13
1178 + key[4 * 26 + 8] = e
1179 + key[4 * 26 + 9] = f
1180 + key[4 * 26 + 10] = g
1181 + key[4 * 26 + 11] = h
1182 + a = key[4 * 27 + 8]
1183 + b = key[4 * 27 + 9]
1184 + c = key[4 * 27 + 10]
1185 + d = key[4 * 27 + 11]
1186 + t1 = a ^ d;
1187 + t2 = a & d;
1188 + t3 = c ^ t1;
1189 + t6 = b & t1;
1190 + t4 = b ^ t3;
1191 + t10 = (~t3) % 0x100000000;
1192 + h = t2 ^ t4;
1193 + t7 = a ^ t6;
1194 + t14 = (~t7) % 0x100000000;
1195 + t8 = c | t7;
1196 + t11 = t3 ^ t7;
1197 + g = t4 ^ t8;
1198 + t12 = h & t11;
1199 + f = t10 ^ t12;
1200 + e = t12 ^ t14
1201 + key[4 * 27 + 8] = e
1202 + key[4 * 27 + 9] = f
1203 + key[4 * 27 + 10] = g
1204 + key[4 * 27 + 11] = h
1205 + a = key[4 * 28 + 8]
1206 + b = key[4 * 28 + 9]
1207 + c = key[4 * 28 + 10]
1208 + d = key[4 * 28 + 11]
1209 + t1 = (~c) % 0x100000000;
1210 + t2 = b ^ c;
1211 + t3 = b | t1;
1212 + t4 = d ^ t3;
1213 + t5 = a & t4;
1214 + t7 = a ^ d;
1215 + h = t2 ^ t5;
1216 + t8 = b ^ t5;
1217 + t9 = t2 | t8;
1218 + t11 = d & t3;
1219 + f = t7 ^ t9;
1220 + t12 = t5 ^ f;
1221 + t15 = t1 | t4;
1222 + t13 = h & t12;
1223 + g = t11 ^ t13;
1224 + t16 = t12 ^ g;
1225 + e = t15 ^ t16
1226 + key[4 * 28 + 8] = e
1227 + key[4 * 28 + 9] = f
1228 + key[4 * 28 + 10] = g
1229 + key[4 * 28 + 11] = h
1230 + a = key[4 * 29 + 8]
1231 + b = key[4 * 29 + 9]
1232 + c = key[4 * 29 + 10]
1233 + d = key[4 * 29 + 11]
1234 + t1 = (~a) % 0x100000000;
1235 + t2 = a ^ d;
1236 + t3 = b ^ t2;
1237 + t4 = t1 | t2;
1238 + t5 = c ^ t4;
1239 + f = b ^ t5;
1240 + t13 = (~t5) % 0x100000000;
1241 + t7 = t2 | f;
1242 + t8 = d ^ t7;
1243 + t9 = t5 & t8;
1244 + g = t3 ^ t9;
1245 + t11 = t5 ^ t8;
1246 + e = g ^ t11;
1247 + t14 = t3 & t11;
1248 + h = t13 ^ t14
1249 + key[4 * 29 + 8] = e
1250 + key[4 * 29 + 9] = f
1251 + key[4 * 29 + 10] = g
1252 + key[4 * 29 + 11] = h
1253 + a = key[4 * 30 + 8]
1254 + b = key[4 * 30 + 9]
1255 + c = key[4 * 30 + 10]
1256 + d = key[4 * 30 + 11]
1257 + t1 = (~a) % 0x100000000;
1258 + t2 = a ^ b;
1259 + t3 = a ^ d;
1260 + t4 = c ^ t1;
1261 + t5 = t2 | t3;
1262 + e = t4 ^ t5;
1263 + t7 = d & e;
1264 + t8 = t2 ^ e;
1265 + t10 = t1 | e;
1266 + f = t7 ^ t8;
1267 + t11 = t2 | t7;
1268 + t12 = t3 ^ t10;
1269 + t14 = b ^ t7;
1270 + g = t11 ^ t12;
1271 + t15 = f & t12;
1272 + h = t14 ^ t15
1273 + key[4 * 30 + 8] = e
1274 + key[4 * 30 + 9] = f
1275 + key[4 * 30 + 10] = g
1276 + key[4 * 30 + 11] = h
1277 + a = key[4 * 31 + 8]
1278 + b = key[4 * 31 + 9]
1279 + c = key[4 * 31 + 10]
1280 + d = key[4 * 31 + 11]
1281 + t1 = a ^ d;
1282 + t2 = d & t1;
1283 + t3 = c ^ t2;
1284 + t4 = b | t3;
1285 + h = t1 ^ t4;
1286 + t6 = (~b) % 0x100000000;
1287 + t7 = t1 | t6;
1288 + e = t3 ^ t7;
1289 + t9 = a & e;
1290 + t10 = t1 ^ t6;
1291 + t11 = t4 & t10;
1292 + g = t9 ^ t11;
1293 + t13 = a ^ t3;
1294 + t14 = t10 & g;
1295 + f = t13 ^ t14
1296 + key[4 * 31 + 8] = e
1297 + key[4 * 31 + 9] = f
1298 + key[4 * 31 + 10] = g
1299 + key[4 * 31 + 11] = h
1300 + a = key[4 * 32 + 8]
1301 + b = key[4 * 32 + 9]
1302 + c = key[4 * 32 + 10]
1303 + d = key[4 * 32 + 11]
1304 + t1 = a ^ c;
1305 + t2 = d ^ t1;
1306 + t3 = a & t2;
1307 + t4 = d ^ t3;
1308 + t5 = b & t4;
1309 + g = t2 ^ t5;
1310 + t7 = a | g;
1311 + t8 = b | d;
1312 + t11 = a | d;
1313 + t9 = t4 & t7;
1314 + f = t8 ^ t9;
1315 + t12 = b ^ t11;
1316 + t13 = g ^ t9;
1317 + t15 = t3 ^ t8;
1318 + h = t12 ^ t13;
1319 + t16 = c & t15;
1320 + e = t12 ^ t16
1321 + key[4 * 32 + 8] = e
1322 + key[4 * 32 + 9] = f
1323 + key[4 * 32 + 10] = g
1324 + key[4 * 32 + 11] = h
1325 +
1326 +def encrypt(key, in_blk):
1327 + # serpent_generate.py
1328 + a = in_blk[0]
1329 + b = in_blk[1]
1330 + c = in_blk[2]
1331 + d = in_blk[3]
1332 + if WORD_BIGENDIAN:
1333 + a = byteswap32(a)
1334 + b = byteswap32(b)
1335 + c = byteswap32(c)
1336 + d = byteswap32(d)
1337 + e = 0
1338 + f = 0
1339 + g = 0
1340 + h = 0
1341 + t1 = 0
1342 + t2 = 0
1343 + t3 = 0
1344 + t4 = 0
1345 + t5 = 0
1346 + t6 = 0
1347 + t7 = 0
1348 + t8 = 0
1349 + t9 = 0
1350 + t10 = 0
1351 + t11 = 0
1352 + t12 = 0
1353 + t13 = 0
1354 + t14 = 0
1355 + t15 = 0
1356 + t16 = 0
1357 + a ^= key[4 * 0 + 8]
1358 + b ^= key[4 * 0 + 9]
1359 + c ^= key[4 * 0 + 10]
1360 + d ^= key[4 * 0 + 11]
1361 + t1 = a ^ d;
1362 + t2 = a & d;
1363 + t3 = c ^ t1;
1364 + t6 = b & t1;
1365 + t4 = b ^ t3;
1366 + t10 = (~t3) % 0x100000000;
1367 + h = t2 ^ t4;
1368 + t7 = a ^ t6;
1369 + t14 = (~t7) % 0x100000000;
1370 + t8 = c | t7;
1371 + t11 = t3 ^ t7;
1372 + g = t4 ^ t8;
1373 + t12 = h & t11;
1374 + f = t10 ^ t12;
1375 + e = t12 ^ t14
1376 + e = rotl32(e, 13)
1377 + g = rotl32(g, 3)
1378 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1379 + f ^= e ^ g
1380 + h = rotl32(h, 7)
1381 + f = rotl32(f, 1)
1382 + e ^= f ^ h
1383 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1384 + e = rotl32(e, 5)
1385 + g = rotl32(g, 22)
1386 + e ^= key[4 * 1 + 8]
1387 + f ^= key[4 * 1 + 9]
1388 + g ^= key[4 * 1 + 10]
1389 + h ^= key[4 * 1 + 11]
1390 + t1 = (~e) % 0x100000000;
1391 + t2 = f ^ t1;
1392 + t3 = e | t2;
1393 + t4 = h | t2;
1394 + t5 = g ^ t3;
1395 + c = h ^ t5;
1396 + t7 = f ^ t4;
1397 + t8 = t2 ^ c;
1398 + t9 = t5 & t7;
1399 + d = t8 ^ t9;
1400 + t11 = t5 ^ t7;
1401 + b = d ^ t11;
1402 + t13 = t8 & t11;
1403 + a = t5 ^ t13
1404 + a = rotl32(a, 13)
1405 + c = rotl32(c, 3)
1406 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1407 + b ^= a ^ c
1408 + d = rotl32(d, 7)
1409 + b = rotl32(b, 1)
1410 + a ^= b ^ d
1411 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1412 + a = rotl32(a, 5)
1413 + c = rotl32(c, 22)
1414 + a ^= key[4 * 2 + 8]
1415 + b ^= key[4 * 2 + 9]
1416 + c ^= key[4 * 2 + 10]
1417 + d ^= key[4 * 2 + 11]
1418 + t1 = (~a) % 0x100000000;
1419 + t2 = b ^ d;
1420 + t3 = c & t1;
1421 + t13 = d | t1;
1422 + e = t2 ^ t3;
1423 + t5 = c ^ t1;
1424 + t6 = c ^ e;
1425 + t7 = b & t6;
1426 + t10 = e | t5;
1427 + h = t5 ^ t7;
1428 + t9 = d | t7;
1429 + t11 = t9 & t10;
1430 + t14 = t2 ^ h;
1431 + g = a ^ t11;
1432 + t15 = g ^ t13;
1433 + f = t14 ^ t15
1434 + e = rotl32(e, 13)
1435 + g = rotl32(g, 3)
1436 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1437 + f ^= e ^ g
1438 + h = rotl32(h, 7)
1439 + f = rotl32(f, 1)
1440 + e ^= f ^ h
1441 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1442 + e = rotl32(e, 5)
1443 + g = rotl32(g, 22)
1444 + e ^= key[4 * 3 + 8]
1445 + f ^= key[4 * 3 + 9]
1446 + g ^= key[4 * 3 + 10]
1447 + h ^= key[4 * 3 + 11]
1448 + t1 = e ^ g;
1449 + t2 = h ^ t1;
1450 + t3 = e & t2;
1451 + t4 = h ^ t3;
1452 + t5 = f & t4;
1453 + c = t2 ^ t5;
1454 + t7 = e | c;
1455 + t8 = f | h;
1456 + t11 = e | h;
1457 + t9 = t4 & t7;
1458 + b = t8 ^ t9;
1459 + t12 = f ^ t11;
1460 + t13 = c ^ t9;
1461 + t15 = t3 ^ t8;
1462 + d = t12 ^ t13;
1463 + t16 = g & t15;
1464 + a = t12 ^ t16
1465 + a = rotl32(a, 13)
1466 + c = rotl32(c, 3)
1467 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1468 + b ^= a ^ c
1469 + d = rotl32(d, 7)
1470 + b = rotl32(b, 1)
1471 + a ^= b ^ d
1472 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1473 + a = rotl32(a, 5)
1474 + c = rotl32(c, 22)
1475 + a ^= key[4 * 4 + 8]
1476 + b ^= key[4 * 4 + 9]
1477 + c ^= key[4 * 4 + 10]
1478 + d ^= key[4 * 4 + 11]
1479 + t1 = a ^ d;
1480 + t2 = d & t1;
1481 + t3 = c ^ t2;
1482 + t4 = b | t3;
1483 + h = t1 ^ t4;
1484 + t6 = (~b) % 0x100000000;
1485 + t7 = t1 | t6;
1486 + e = t3 ^ t7;
1487 + t9 = a & e;
1488 + t10 = t1 ^ t6;
1489 + t11 = t4 & t10;
1490 + g = t9 ^ t11;
1491 + t13 = a ^ t3;
1492 + t14 = t10 & g;
1493 + f = t13 ^ t14
1494 + e = rotl32(e, 13)
1495 + g = rotl32(g, 3)
1496 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1497 + f ^= e ^ g
1498 + h = rotl32(h, 7)
1499 + f = rotl32(f, 1)
1500 + e ^= f ^ h
1501 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1502 + e = rotl32(e, 5)
1503 + g = rotl32(g, 22)
1504 + e ^= key[4 * 5 + 8]
1505 + f ^= key[4 * 5 + 9]
1506 + g ^= key[4 * 5 + 10]
1507 + h ^= key[4 * 5 + 11]
1508 + t1 = (~e) % 0x100000000;
1509 + t2 = e ^ f;
1510 + t3 = e ^ h;
1511 + t4 = g ^ t1;
1512 + t5 = t2 | t3;
1513 + a = t4 ^ t5;
1514 + t7 = h & a;
1515 + t8 = t2 ^ a;
1516 + t10 = t1 | a;
1517 + b = t7 ^ t8;
1518 + t11 = t2 | t7;
1519 + t12 = t3 ^ t10;
1520 + t14 = f ^ t7;
1521 + c = t11 ^ t12;
1522 + t15 = b & t12;
1523 + d = t14 ^ t15
1524 + a = rotl32(a, 13)
1525 + c = rotl32(c, 3)
1526 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1527 + b ^= a ^ c
1528 + d = rotl32(d, 7)
1529 + b = rotl32(b, 1)
1530 + a ^= b ^ d
1531 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1532 + a = rotl32(a, 5)
1533 + c = rotl32(c, 22)
1534 + a ^= key[4 * 6 + 8]
1535 + b ^= key[4 * 6 + 9]
1536 + c ^= key[4 * 6 + 10]
1537 + d ^= key[4 * 6 + 11]
1538 + t1 = (~a) % 0x100000000;
1539 + t2 = a ^ d;
1540 + t3 = b ^ t2;
1541 + t4 = t1 | t2;
1542 + t5 = c ^ t4;
1543 + f = b ^ t5;
1544 + t13 = (~t5) % 0x100000000;
1545 + t7 = t2 | f;
1546 + t8 = d ^ t7;
1547 + t9 = t5 & t8;
1548 + g = t3 ^ t9;
1549 + t11 = t5 ^ t8;
1550 + e = g ^ t11;
1551 + t14 = t3 & t11;
1552 + h = t13 ^ t14
1553 + e = rotl32(e, 13)
1554 + g = rotl32(g, 3)
1555 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1556 + f ^= e ^ g
1557 + h = rotl32(h, 7)
1558 + f = rotl32(f, 1)
1559 + e ^= f ^ h
1560 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1561 + e = rotl32(e, 5)
1562 + g = rotl32(g, 22)
1563 + e ^= key[4 * 7 + 8]
1564 + f ^= key[4 * 7 + 9]
1565 + g ^= key[4 * 7 + 10]
1566 + h ^= key[4 * 7 + 11]
1567 + t1 = (~g) % 0x100000000;
1568 + t2 = f ^ g;
1569 + t3 = f | t1;
1570 + t4 = h ^ t3;
1571 + t5 = e & t4;
1572 + t7 = e ^ h;
1573 + d = t2 ^ t5;
1574 + t8 = f ^ t5;
1575 + t9 = t2 | t8;
1576 + t11 = h & t3;
1577 + b = t7 ^ t9;
1578 + t12 = t5 ^ b;
1579 + t15 = t1 | t4;
1580 + t13 = d & t12;
1581 + c = t11 ^ t13;
1582 + t16 = t12 ^ c;
1583 + a = t15 ^ t16
1584 + a = rotl32(a, 13)
1585 + c = rotl32(c, 3)
1586 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1587 + b ^= a ^ c
1588 + d = rotl32(d, 7)
1589 + b = rotl32(b, 1)
1590 + a ^= b ^ d
1591 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1592 + a = rotl32(a, 5)
1593 + c = rotl32(c, 22)
1594 + a ^= key[4 * 8 + 8]
1595 + b ^= key[4 * 8 + 9]
1596 + c ^= key[4 * 8 + 10]
1597 + d ^= key[4 * 8 + 11]
1598 + t1 = a ^ d;
1599 + t2 = a & d;
1600 + t3 = c ^ t1;
1601 + t6 = b & t1;
1602 + t4 = b ^ t3;
1603 + t10 = (~t3) % 0x100000000;
1604 + h = t2 ^ t4;
1605 + t7 = a ^ t6;
1606 + t14 = (~t7) % 0x100000000;
1607 + t8 = c | t7;
1608 + t11 = t3 ^ t7;
1609 + g = t4 ^ t8;
1610 + t12 = h & t11;
1611 + f = t10 ^ t12;
1612 + e = t12 ^ t14
1613 + e = rotl32(e, 13)
1614 + g = rotl32(g, 3)
1615 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1616 + f ^= e ^ g
1617 + h = rotl32(h, 7)
1618 + f = rotl32(f, 1)
1619 + e ^= f ^ h
1620 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1621 + e = rotl32(e, 5)
1622 + g = rotl32(g, 22)
1623 + e ^= key[4 * 9 + 8]
1624 + f ^= key[4 * 9 + 9]
1625 + g ^= key[4 * 9 + 10]
1626 + h ^= key[4 * 9 + 11]
1627 + t1 = (~e) % 0x100000000;
1628 + t2 = f ^ t1;
1629 + t3 = e | t2;
1630 + t4 = h | t2;
1631 + t5 = g ^ t3;
1632 + c = h ^ t5;
1633 + t7 = f ^ t4;
1634 + t8 = t2 ^ c;
1635 + t9 = t5 & t7;
1636 + d = t8 ^ t9;
1637 + t11 = t5 ^ t7;
1638 + b = d ^ t11;
1639 + t13 = t8 & t11;
1640 + a = t5 ^ t13
1641 + a = rotl32(a, 13)
1642 + c = rotl32(c, 3)
1643 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1644 + b ^= a ^ c
1645 + d = rotl32(d, 7)
1646 + b = rotl32(b, 1)
1647 + a ^= b ^ d
1648 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1649 + a = rotl32(a, 5)
1650 + c = rotl32(c, 22)
1651 + a ^= key[4 * 10 + 8]
1652 + b ^= key[4 * 10 + 9]
1653 + c ^= key[4 * 10 + 10]
1654 + d ^= key[4 * 10 + 11]
1655 + t1 = (~a) % 0x100000000;
1656 + t2 = b ^ d;
1657 + t3 = c & t1;
1658 + t13 = d | t1;
1659 + e = t2 ^ t3;
1660 + t5 = c ^ t1;
1661 + t6 = c ^ e;
1662 + t7 = b & t6;
1663 + t10 = e | t5;
1664 + h = t5 ^ t7;
1665 + t9 = d | t7;
1666 + t11 = t9 & t10;
1667 + t14 = t2 ^ h;
1668 + g = a ^ t11;
1669 + t15 = g ^ t13;
1670 + f = t14 ^ t15
1671 + e = rotl32(e, 13)
1672 + g = rotl32(g, 3)
1673 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1674 + f ^= e ^ g
1675 + h = rotl32(h, 7)
1676 + f = rotl32(f, 1)
1677 + e ^= f ^ h
1678 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1679 + e = rotl32(e, 5)
1680 + g = rotl32(g, 22)
1681 + e ^= key[4 * 11 + 8]
1682 + f ^= key[4 * 11 + 9]
1683 + g ^= key[4 * 11 + 10]
1684 + h ^= key[4 * 11 + 11]
1685 + t1 = e ^ g;
1686 + t2 = h ^ t1;
1687 + t3 = e & t2;
1688 + t4 = h ^ t3;
1689 + t5 = f & t4;
1690 + c = t2 ^ t5;
1691 + t7 = e | c;
1692 + t8 = f | h;
1693 + t11 = e | h;
1694 + t9 = t4 & t7;
1695 + b = t8 ^ t9;
1696 + t12 = f ^ t11;
1697 + t13 = c ^ t9;
1698 + t15 = t3 ^ t8;
1699 + d = t12 ^ t13;
1700 + t16 = g & t15;
1701 + a = t12 ^ t16
1702 + a = rotl32(a, 13)
1703 + c = rotl32(c, 3)
1704 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1705 + b ^= a ^ c
1706 + d = rotl32(d, 7)
1707 + b = rotl32(b, 1)
1708 + a ^= b ^ d
1709 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1710 + a = rotl32(a, 5)
1711 + c = rotl32(c, 22)
1712 + a ^= key[4 * 12 + 8]
1713 + b ^= key[4 * 12 + 9]
1714 + c ^= key[4 * 12 + 10]
1715 + d ^= key[4 * 12 + 11]
1716 + t1 = a ^ d;
1717 + t2 = d & t1;
1718 + t3 = c ^ t2;
1719 + t4 = b | t3;
1720 + h = t1 ^ t4;
1721 + t6 = (~b) % 0x100000000;
1722 + t7 = t1 | t6;
1723 + e = t3 ^ t7;
1724 + t9 = a & e;
1725 + t10 = t1 ^ t6;
1726 + t11 = t4 & t10;
1727 + g = t9 ^ t11;
1728 + t13 = a ^ t3;
1729 + t14 = t10 & g;
1730 + f = t13 ^ t14
1731 + e = rotl32(e, 13)
1732 + g = rotl32(g, 3)
1733 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1734 + f ^= e ^ g
1735 + h = rotl32(h, 7)
1736 + f = rotl32(f, 1)
1737 + e ^= f ^ h
1738 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1739 + e = rotl32(e, 5)
1740 + g = rotl32(g, 22)
1741 + e ^= key[4 * 13 + 8]
1742 + f ^= key[4 * 13 + 9]
1743 + g ^= key[4 * 13 + 10]
1744 + h ^= key[4 * 13 + 11]
1745 + t1 = (~e) % 0x100000000;
1746 + t2 = e ^ f;
1747 + t3 = e ^ h;
1748 + t4 = g ^ t1;
1749 + t5 = t2 | t3;
1750 + a = t4 ^ t5;
1751 + t7 = h & a;
1752 + t8 = t2 ^ a;
1753 + t10 = t1 | a;
1754 + b = t7 ^ t8;
1755 + t11 = t2 | t7;
1756 + t12 = t3 ^ t10;
1757 + t14 = f ^ t7;
1758 + c = t11 ^ t12;
1759 + t15 = b & t12;
1760 + d = t14 ^ t15
1761 + a = rotl32(a, 13)
1762 + c = rotl32(c, 3)
1763 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1764 + b ^= a ^ c
1765 + d = rotl32(d, 7)
1766 + b = rotl32(b, 1)
1767 + a ^= b ^ d
1768 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1769 + a = rotl32(a, 5)
1770 + c = rotl32(c, 22)
1771 + a ^= key[4 * 14 + 8]
1772 + b ^= key[4 * 14 + 9]
1773 + c ^= key[4 * 14 + 10]
1774 + d ^= key[4 * 14 + 11]
1775 + t1 = (~a) % 0x100000000;
1776 + t2 = a ^ d;
1777 + t3 = b ^ t2;
1778 + t4 = t1 | t2;
1779 + t5 = c ^ t4;
1780 + f = b ^ t5;
1781 + t13 = (~t5) % 0x100000000;
1782 + t7 = t2 | f;
1783 + t8 = d ^ t7;
1784 + t9 = t5 & t8;
1785 + g = t3 ^ t9;
1786 + t11 = t5 ^ t8;
1787 + e = g ^ t11;
1788 + t14 = t3 & t11;
1789 + h = t13 ^ t14
1790 + e = rotl32(e, 13)
1791 + g = rotl32(g, 3)
1792 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1793 + f ^= e ^ g
1794 + h = rotl32(h, 7)
1795 + f = rotl32(f, 1)
1796 + e ^= f ^ h
1797 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1798 + e = rotl32(e, 5)
1799 + g = rotl32(g, 22)
1800 + e ^= key[4 * 15 + 8]
1801 + f ^= key[4 * 15 + 9]
1802 + g ^= key[4 * 15 + 10]
1803 + h ^= key[4 * 15 + 11]
1804 + t1 = (~g) % 0x100000000;
1805 + t2 = f ^ g;
1806 + t3 = f | t1;
1807 + t4 = h ^ t3;
1808 + t5 = e & t4;
1809 + t7 = e ^ h;
1810 + d = t2 ^ t5;
1811 + t8 = f ^ t5;
1812 + t9 = t2 | t8;
1813 + t11 = h & t3;
1814 + b = t7 ^ t9;
1815 + t12 = t5 ^ b;
1816 + t15 = t1 | t4;
1817 + t13 = d & t12;
1818 + c = t11 ^ t13;
1819 + t16 = t12 ^ c;
1820 + a = t15 ^ t16
1821 + a = rotl32(a, 13)
1822 + c = rotl32(c, 3)
1823 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1824 + b ^= a ^ c
1825 + d = rotl32(d, 7)
1826 + b = rotl32(b, 1)
1827 + a ^= b ^ d
1828 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1829 + a = rotl32(a, 5)
1830 + c = rotl32(c, 22)
1831 + a ^= key[4 * 16 + 8]
1832 + b ^= key[4 * 16 + 9]
1833 + c ^= key[4 * 16 + 10]
1834 + d ^= key[4 * 16 + 11]
1835 + t1 = a ^ d;
1836 + t2 = a & d;
1837 + t3 = c ^ t1;
1838 + t6 = b & t1;
1839 + t4 = b ^ t3;
1840 + t10 = (~t3) % 0x100000000;
1841 + h = t2 ^ t4;
1842 + t7 = a ^ t6;
1843 + t14 = (~t7) % 0x100000000;
1844 + t8 = c | t7;
1845 + t11 = t3 ^ t7;
1846 + g = t4 ^ t8;
1847 + t12 = h & t11;
1848 + f = t10 ^ t12;
1849 + e = t12 ^ t14
1850 + e = rotl32(e, 13)
1851 + g = rotl32(g, 3)
1852 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1853 + f ^= e ^ g
1854 + h = rotl32(h, 7)
1855 + f = rotl32(f, 1)
1856 + e ^= f ^ h
1857 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1858 + e = rotl32(e, 5)
1859 + g = rotl32(g, 22)
1860 + e ^= key[4 * 17 + 8]
1861 + f ^= key[4 * 17 + 9]
1862 + g ^= key[4 * 17 + 10]
1863 + h ^= key[4 * 17 + 11]
1864 + t1 = (~e) % 0x100000000;
1865 + t2 = f ^ t1;
1866 + t3 = e | t2;
1867 + t4 = h | t2;
1868 + t5 = g ^ t3;
1869 + c = h ^ t5;
1870 + t7 = f ^ t4;
1871 + t8 = t2 ^ c;
1872 + t9 = t5 & t7;
1873 + d = t8 ^ t9;
1874 + t11 = t5 ^ t7;
1875 + b = d ^ t11;
1876 + t13 = t8 & t11;
1877 + a = t5 ^ t13
1878 + a = rotl32(a, 13)
1879 + c = rotl32(c, 3)
1880 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1881 + b ^= a ^ c
1882 + d = rotl32(d, 7)
1883 + b = rotl32(b, 1)
1884 + a ^= b ^ d
1885 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1886 + a = rotl32(a, 5)
1887 + c = rotl32(c, 22)
1888 + a ^= key[4 * 18 + 8]
1889 + b ^= key[4 * 18 + 9]
1890 + c ^= key[4 * 18 + 10]
1891 + d ^= key[4 * 18 + 11]
1892 + t1 = (~a) % 0x100000000;
1893 + t2 = b ^ d;
1894 + t3 = c & t1;
1895 + t13 = d | t1;
1896 + e = t2 ^ t3;
1897 + t5 = c ^ t1;
1898 + t6 = c ^ e;
1899 + t7 = b & t6;
1900 + t10 = e | t5;
1901 + h = t5 ^ t7;
1902 + t9 = d | t7;
1903 + t11 = t9 & t10;
1904 + t14 = t2 ^ h;
1905 + g = a ^ t11;
1906 + t15 = g ^ t13;
1907 + f = t14 ^ t15
1908 + e = rotl32(e, 13)
1909 + g = rotl32(g, 3)
1910 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1911 + f ^= e ^ g
1912 + h = rotl32(h, 7)
1913 + f = rotl32(f, 1)
1914 + e ^= f ^ h
1915 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1916 + e = rotl32(e, 5)
1917 + g = rotl32(g, 22)
1918 + e ^= key[4 * 19 + 8]
1919 + f ^= key[4 * 19 + 9]
1920 + g ^= key[4 * 19 + 10]
1921 + h ^= key[4 * 19 + 11]
1922 + t1 = e ^ g;
1923 + t2 = h ^ t1;
1924 + t3 = e & t2;
1925 + t4 = h ^ t3;
1926 + t5 = f & t4;
1927 + c = t2 ^ t5;
1928 + t7 = e | c;
1929 + t8 = f | h;
1930 + t11 = e | h;
1931 + t9 = t4 & t7;
1932 + b = t8 ^ t9;
1933 + t12 = f ^ t11;
1934 + t13 = c ^ t9;
1935 + t15 = t3 ^ t8;
1936 + d = t12 ^ t13;
1937 + t16 = g & t15;
1938 + a = t12 ^ t16
1939 + a = rotl32(a, 13)
1940 + c = rotl32(c, 3)
1941 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
1942 + b ^= a ^ c
1943 + d = rotl32(d, 7)
1944 + b = rotl32(b, 1)
1945 + a ^= b ^ d
1946 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
1947 + a = rotl32(a, 5)
1948 + c = rotl32(c, 22)
1949 + a ^= key[4 * 20 + 8]
1950 + b ^= key[4 * 20 + 9]
1951 + c ^= key[4 * 20 + 10]
1952 + d ^= key[4 * 20 + 11]
1953 + t1 = a ^ d;
1954 + t2 = d & t1;
1955 + t3 = c ^ t2;
1956 + t4 = b | t3;
1957 + h = t1 ^ t4;
1958 + t6 = (~b) % 0x100000000;
1959 + t7 = t1 | t6;
1960 + e = t3 ^ t7;
1961 + t9 = a & e;
1962 + t10 = t1 ^ t6;
1963 + t11 = t4 & t10;
1964 + g = t9 ^ t11;
1965 + t13 = a ^ t3;
1966 + t14 = t10 & g;
1967 + f = t13 ^ t14
1968 + e = rotl32(e, 13)
1969 + g = rotl32(g, 3)
1970 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
1971 + f ^= e ^ g
1972 + h = rotl32(h, 7)
1973 + f = rotl32(f, 1)
1974 + e ^= f ^ h
1975 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
1976 + e = rotl32(e, 5)
1977 + g = rotl32(g, 22)
1978 + e ^= key[4 * 21 + 8]
1979 + f ^= key[4 * 21 + 9]
1980 + g ^= key[4 * 21 + 10]
1981 + h ^= key[4 * 21 + 11]
1982 + t1 = (~e) % 0x100000000;
1983 + t2 = e ^ f;
1984 + t3 = e ^ h;
1985 + t4 = g ^ t1;
1986 + t5 = t2 | t3;
1987 + a = t4 ^ t5;
1988 + t7 = h & a;
1989 + t8 = t2 ^ a;
1990 + t10 = t1 | a;
1991 + b = t7 ^ t8;
1992 + t11 = t2 | t7;
1993 + t12 = t3 ^ t10;
1994 + t14 = f ^ t7;
1995 + c = t11 ^ t12;
1996 + t15 = b & t12;
1997 + d = t14 ^ t15
1998 + a = rotl32(a, 13)
1999 + c = rotl32(c, 3)
2000 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2001 + b ^= a ^ c
2002 + d = rotl32(d, 7)
2003 + b = rotl32(b, 1)
2004 + a ^= b ^ d
2005 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2006 + a = rotl32(a, 5)
2007 + c = rotl32(c, 22)
2008 + a ^= key[4 * 22 + 8]
2009 + b ^= key[4 * 22 + 9]
2010 + c ^= key[4 * 22 + 10]
2011 + d ^= key[4 * 22 + 11]
2012 + t1 = (~a) % 0x100000000;
2013 + t2 = a ^ d;
2014 + t3 = b ^ t2;
2015 + t4 = t1 | t2;
2016 + t5 = c ^ t4;
2017 + f = b ^ t5;
2018 + t13 = (~t5) % 0x100000000;
2019 + t7 = t2 | f;
2020 + t8 = d ^ t7;
2021 + t9 = t5 & t8;
2022 + g = t3 ^ t9;
2023 + t11 = t5 ^ t8;
2024 + e = g ^ t11;
2025 + t14 = t3 & t11;
2026 + h = t13 ^ t14
2027 + e = rotl32(e, 13)
2028 + g = rotl32(g, 3)
2029 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2030 + f ^= e ^ g
2031 + h = rotl32(h, 7)
2032 + f = rotl32(f, 1)
2033 + e ^= f ^ h
2034 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2035 + e = rotl32(e, 5)
2036 + g = rotl32(g, 22)
2037 + e ^= key[4 * 23 + 8]
2038 + f ^= key[4 * 23 + 9]
2039 + g ^= key[4 * 23 + 10]
2040 + h ^= key[4 * 23 + 11]
2041 + t1 = (~g) % 0x100000000;
2042 + t2 = f ^ g;
2043 + t3 = f | t1;
2044 + t4 = h ^ t3;
2045 + t5 = e & t4;
2046 + t7 = e ^ h;
2047 + d = t2 ^ t5;
2048 + t8 = f ^ t5;
2049 + t9 = t2 | t8;
2050 + t11 = h & t3;
2051 + b = t7 ^ t9;
2052 + t12 = t5 ^ b;
2053 + t15 = t1 | t4;
2054 + t13 = d & t12;
2055 + c = t11 ^ t13;
2056 + t16 = t12 ^ c;
2057 + a = t15 ^ t16
2058 + a = rotl32(a, 13)
2059 + c = rotl32(c, 3)
2060 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2061 + b ^= a ^ c
2062 + d = rotl32(d, 7)
2063 + b = rotl32(b, 1)
2064 + a ^= b ^ d
2065 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2066 + a = rotl32(a, 5)
2067 + c = rotl32(c, 22)
2068 + a ^= key[4 * 24 + 8]
2069 + b ^= key[4 * 24 + 9]
2070 + c ^= key[4 * 24 + 10]
2071 + d ^= key[4 * 24 + 11]
2072 + t1 = a ^ d;
2073 + t2 = a & d;
2074 + t3 = c ^ t1;
2075 + t6 = b & t1;
2076 + t4 = b ^ t3;
2077 + t10 = (~t3) % 0x100000000;
2078 + h = t2 ^ t4;
2079 + t7 = a ^ t6;
2080 + t14 = (~t7) % 0x100000000;
2081 + t8 = c | t7;
2082 + t11 = t3 ^ t7;
2083 + g = t4 ^ t8;
2084 + t12 = h & t11;
2085 + f = t10 ^ t12;
2086 + e = t12 ^ t14
2087 + e = rotl32(e, 13)
2088 + g = rotl32(g, 3)
2089 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2090 + f ^= e ^ g
2091 + h = rotl32(h, 7)
2092 + f = rotl32(f, 1)
2093 + e ^= f ^ h
2094 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2095 + e = rotl32(e, 5)
2096 + g = rotl32(g, 22)
2097 + e ^= key[4 * 25 + 8]
2098 + f ^= key[4 * 25 + 9]
2099 + g ^= key[4 * 25 + 10]
2100 + h ^= key[4 * 25 + 11]
2101 + t1 = (~e) % 0x100000000;
2102 + t2 = f ^ t1;
2103 + t3 = e | t2;
2104 + t4 = h | t2;
2105 + t5 = g ^ t3;
2106 + c = h ^ t5;
2107 + t7 = f ^ t4;
2108 + t8 = t2 ^ c;
2109 + t9 = t5 & t7;
2110 + d = t8 ^ t9;
2111 + t11 = t5 ^ t7;
2112 + b = d ^ t11;
2113 + t13 = t8 & t11;
2114 + a = t5 ^ t13
2115 + a = rotl32(a, 13)
2116 + c = rotl32(c, 3)
2117 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2118 + b ^= a ^ c
2119 + d = rotl32(d, 7)
2120 + b = rotl32(b, 1)
2121 + a ^= b ^ d
2122 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2123 + a = rotl32(a, 5)
2124 + c = rotl32(c, 22)
2125 + a ^= key[4 * 26 + 8]
2126 + b ^= key[4 * 26 + 9]
2127 + c ^= key[4 * 26 + 10]
2128 + d ^= key[4 * 26 + 11]
2129 + t1 = (~a) % 0x100000000;
2130 + t2 = b ^ d;
2131 + t3 = c & t1;
2132 + t13 = d | t1;
2133 + e = t2 ^ t3;
2134 + t5 = c ^ t1;
2135 + t6 = c ^ e;
2136 + t7 = b & t6;
2137 + t10 = e | t5;
2138 + h = t5 ^ t7;
2139 + t9 = d | t7;
2140 + t11 = t9 & t10;
2141 + t14 = t2 ^ h;
2142 + g = a ^ t11;
2143 + t15 = g ^ t13;
2144 + f = t14 ^ t15
2145 + e = rotl32(e, 13)
2146 + g = rotl32(g, 3)
2147 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2148 + f ^= e ^ g
2149 + h = rotl32(h, 7)
2150 + f = rotl32(f, 1)
2151 + e ^= f ^ h
2152 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2153 + e = rotl32(e, 5)
2154 + g = rotl32(g, 22)
2155 + e ^= key[4 * 27 + 8]
2156 + f ^= key[4 * 27 + 9]
2157 + g ^= key[4 * 27 + 10]
2158 + h ^= key[4 * 27 + 11]
2159 + t1 = e ^ g;
2160 + t2 = h ^ t1;
2161 + t3 = e & t2;
2162 + t4 = h ^ t3;
2163 + t5 = f & t4;
2164 + c = t2 ^ t5;
2165 + t7 = e | c;
2166 + t8 = f | h;
2167 + t11 = e | h;
2168 + t9 = t4 & t7;
2169 + b = t8 ^ t9;
2170 + t12 = f ^ t11;
2171 + t13 = c ^ t9;
2172 + t15 = t3 ^ t8;
2173 + d = t12 ^ t13;
2174 + t16 = g & t15;
2175 + a = t12 ^ t16
2176 + a = rotl32(a, 13)
2177 + c = rotl32(c, 3)
2178 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2179 + b ^= a ^ c
2180 + d = rotl32(d, 7)
2181 + b = rotl32(b, 1)
2182 + a ^= b ^ d
2183 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2184 + a = rotl32(a, 5)
2185 + c = rotl32(c, 22)
2186 + a ^= key[4 * 28 + 8]
2187 + b ^= key[4 * 28 + 9]
2188 + c ^= key[4 * 28 + 10]
2189 + d ^= key[4 * 28 + 11]
2190 + t1 = a ^ d;
2191 + t2 = d & t1;
2192 + t3 = c ^ t2;
2193 + t4 = b | t3;
2194 + h = t1 ^ t4;
2195 + t6 = (~b) % 0x100000000;
2196 + t7 = t1 | t6;
2197 + e = t3 ^ t7;
2198 + t9 = a & e;
2199 + t10 = t1 ^ t6;
2200 + t11 = t4 & t10;
2201 + g = t9 ^ t11;
2202 + t13 = a ^ t3;
2203 + t14 = t10 & g;
2204 + f = t13 ^ t14
2205 + e = rotl32(e, 13)
2206 + g = rotl32(g, 3)
2207 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2208 + f ^= e ^ g
2209 + h = rotl32(h, 7)
2210 + f = rotl32(f, 1)
2211 + e ^= f ^ h
2212 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2213 + e = rotl32(e, 5)
2214 + g = rotl32(g, 22)
2215 + e ^= key[4 * 29 + 8]
2216 + f ^= key[4 * 29 + 9]
2217 + g ^= key[4 * 29 + 10]
2218 + h ^= key[4 * 29 + 11]
2219 + t1 = (~e) % 0x100000000;
2220 + t2 = e ^ f;
2221 + t3 = e ^ h;
2222 + t4 = g ^ t1;
2223 + t5 = t2 | t3;
2224 + a = t4 ^ t5;
2225 + t7 = h & a;
2226 + t8 = t2 ^ a;
2227 + t10 = t1 | a;
2228 + b = t7 ^ t8;
2229 + t11 = t2 | t7;
2230 + t12 = t3 ^ t10;
2231 + t14 = f ^ t7;
2232 + c = t11 ^ t12;
2233 + t15 = b & t12;
2234 + d = t14 ^ t15
2235 + a = rotl32(a, 13)
2236 + c = rotl32(c, 3)
2237 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2238 + b ^= a ^ c
2239 + d = rotl32(d, 7)
2240 + b = rotl32(b, 1)
2241 + a ^= b ^ d
2242 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2243 + a = rotl32(a, 5)
2244 + c = rotl32(c, 22)
2245 + a ^= key[4 * 30 + 8]
2246 + b ^= key[4 * 30 + 9]
2247 + c ^= key[4 * 30 + 10]
2248 + d ^= key[4 * 30 + 11]
2249 + t1 = (~a) % 0x100000000;
2250 + t2 = a ^ d;
2251 + t3 = b ^ t2;
2252 + t4 = t1 | t2;
2253 + t5 = c ^ t4;
2254 + f = b ^ t5;
2255 + t13 = (~t5) % 0x100000000;
2256 + t7 = t2 | f;
2257 + t8 = d ^ t7;
2258 + t9 = t5 & t8;
2259 + g = t3 ^ t9;
2260 + t11 = t5 ^ t8;
2261 + e = g ^ t11;
2262 + t14 = t3 & t11;
2263 + h = t13 ^ t14
2264 + e = rotl32(e, 13)
2265 + g = rotl32(g, 3)
2266 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2267 + f ^= e ^ g
2268 + h = rotl32(h, 7)
2269 + f = rotl32(f, 1)
2270 + e ^= f ^ h
2271 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2272 + e = rotl32(e, 5)
2273 + g = rotl32(g, 22)
2274 + e ^= key[4 * 31 + 8]
2275 + f ^= key[4 * 31 + 9]
2276 + g ^= key[4 * 31 + 10]
2277 + h ^= key[4 * 31 + 11]
2278 + t1 = (~g) % 0x100000000;
2279 + t2 = f ^ g;
2280 + t3 = f | t1;
2281 + t4 = h ^ t3;
2282 + t5 = e & t4;
2283 + t7 = e ^ h;
2284 + d = t2 ^ t5;
2285 + t8 = f ^ t5;
2286 + t9 = t2 | t8;
2287 + t11 = h & t3;
2288 + b = t7 ^ t9;
2289 + t12 = t5 ^ b;
2290 + t15 = t1 | t4;
2291 + t13 = d & t12;
2292 + c = t11 ^ t13;
2293 + t16 = t12 ^ c;
2294 + a = t15 ^ t16
2295 + a ^= key[4 * 32 + 8]
2296 + b ^= key[4 * 32 + 9]
2297 + c ^= key[4 * 32 + 10]
2298 + d ^= key[4 * 32 + 11]
2299 + if WORD_BIGENDIAN:
2300 + a = byteswap32(a)
2301 + b = byteswap32(b)
2302 + c = byteswap32(c)
2303 + d = byteswap32(d)
2304 + in_blk[0] = a
2305 + in_blk[1] = b
2306 + in_blk[2] = c
2307 + in_blk[3] = d
2308 +
2309 +def decrypt(key, in_blk):
2310 + # serpent_generate.py
2311 + a = in_blk[0]
2312 + b = in_blk[1]
2313 + c = in_blk[2]
2314 + d = in_blk[3]
2315 + if WORD_BIGENDIAN:
2316 + a = byteswap32(a)
2317 + b = byteswap32(b)
2318 + c = byteswap32(c)
2319 + d = byteswap32(d)
2320 + e = 0
2321 + f = 0
2322 + g = 0
2323 + h = 0
2324 + t1 = 0
2325 + t2 = 0
2326 + t3 = 0
2327 + t4 = 0
2328 + t5 = 0
2329 + t6 = 0
2330 + t7 = 0
2331 + t8 = 0
2332 + t9 = 0
2333 + t10 = 0
2334 + t11 = 0
2335 + t12 = 0
2336 + t13 = 0
2337 + t14 = 0
2338 + t15 = 0
2339 + t16 = 0
2340 + a ^= key[4 * 32 + 8]
2341 + b ^= key[4 * 32 + 9]
2342 + c ^= key[4 * 32 + 10]
2343 + d ^= key[4 * 32 + 11]
2344 + t1 = a & b;
2345 + t2 = a | b;
2346 + t3 = c | t1;
2347 + t4 = d & t2;
2348 + h = t3 ^ t4;
2349 + t6 = (~d) % 0x100000000;
2350 + t7 = b ^ t4;
2351 + t8 = h ^ t6;
2352 + t11 = c ^ t7;
2353 + t9 = t7 | t8;
2354 + f = a ^ t9;
2355 + t12 = d | f;
2356 + e = t11 ^ t12;
2357 + t14 = a & h;
2358 + t15 = t3 ^ f;
2359 + t16 = e ^ t14;
2360 + g = t15 ^ t16
2361 + e ^= key[4 * 31 + 8]
2362 + f ^= key[4 * 31 + 9]
2363 + g ^= key[4 * 31 + 10]
2364 + h ^= key[4 * 31 + 11]
2365 + g = rotr32(g, 22)
2366 + e = rotr32(e, 5)
2367 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2368 + e ^= f ^ h
2369 + h = rotr32(h, 7)
2370 + f = rotr32(f, 1)
2371 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2372 + f ^= e ^ g
2373 + g = rotr32(g, 3)
2374 + e = rotr32(e, 13)
2375 + t1 = (~e) % 0x100000000;
2376 + t2 = e ^ f;
2377 + t3 = g ^ t2;
2378 + t4 = g | t1;
2379 + t5 = h ^ t4;
2380 + t13 = h & t1;
2381 + b = t3 ^ t5;
2382 + t7 = t3 & t5;
2383 + t8 = t2 ^ t7;
2384 + t9 = f | t8;
2385 + d = t5 ^ t9;
2386 + t11 = f | d;
2387 + a = t8 ^ t11;
2388 + t14 = t3 ^ t11;
2389 + c = t13 ^ t14
2390 + a ^= key[4 * 30 + 8]
2391 + b ^= key[4 * 30 + 9]
2392 + c ^= key[4 * 30 + 10]
2393 + d ^= key[4 * 30 + 11]
2394 + c = rotr32(c, 22)
2395 + a = rotr32(a, 5)
2396 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2397 + a ^= b ^ d
2398 + d = rotr32(d, 7)
2399 + b = rotr32(b, 1)
2400 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2401 + b ^= a ^ c
2402 + c = rotr32(c, 3)
2403 + a = rotr32(a, 13)
2404 + t1 = (~c) % 0x100000000;
2405 + t2 = b & t1;
2406 + t3 = d ^ t2;
2407 + t4 = a & t3;
2408 + t5 = b ^ t1;
2409 + h = t4 ^ t5;
2410 + t7 = b | h;
2411 + t8 = a & t7;
2412 + f = t3 ^ t8;
2413 + t10 = a | d;
2414 + t11 = t1 ^ t7;
2415 + e = t10 ^ t11;
2416 + t13 = a ^ c;
2417 + t14 = b & t10;
2418 + t15 = t4 | t13;
2419 + g = t14 ^ t15
2420 + e ^= key[4 * 29 + 8]
2421 + f ^= key[4 * 29 + 9]
2422 + g ^= key[4 * 29 + 10]
2423 + h ^= key[4 * 29 + 11]
2424 + g = rotr32(g, 22)
2425 + e = rotr32(e, 5)
2426 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2427 + e ^= f ^ h
2428 + h = rotr32(h, 7)
2429 + f = rotr32(f, 1)
2430 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2431 + f ^= e ^ g
2432 + g = rotr32(g, 3)
2433 + e = rotr32(e, 13)
2434 + t1 = g ^ h;
2435 + t2 = g | h;
2436 + t3 = f ^ t2;
2437 + t4 = e & t3;
2438 + b = t1 ^ t4;
2439 + t6 = e ^ h;
2440 + t7 = f | h;
2441 + t8 = t6 & t7;
2442 + d = t3 ^ t8;
2443 + t10 = (~e) % 0x100000000;
2444 + t11 = g ^ d;
2445 + t12 = t10 | t11;
2446 + a = t3 ^ t12;
2447 + t14 = g | t4;
2448 + t15 = t7 ^ t14;
2449 + t16 = d | t10;
2450 + c = t15 ^ t16
2451 + a ^= key[4 * 28 + 8]
2452 + b ^= key[4 * 28 + 9]
2453 + c ^= key[4 * 28 + 10]
2454 + d ^= key[4 * 28 + 11]
2455 + c = rotr32(c, 22)
2456 + a = rotr32(a, 5)
2457 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2458 + a ^= b ^ d
2459 + d = rotr32(d, 7)
2460 + b = rotr32(b, 1)
2461 +
2462 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2463 + b ^= a ^ c
2464 + c = rotr32(c, 3)
2465 + a = rotr32(a, 13)
2466 + t1 = b ^ c;
2467 + t2 = b | c;
2468 + t3 = a ^ c;
2469 + t7 = a ^ d;
2470 + t4 = t2 ^ t3;
2471 + t5 = d | t4;
2472 + t9 = t2 ^ t7;
2473 + e = t1 ^ t5;
2474 + t8 = t1 | t5;
2475 + t11 = a & t4;
2476 + g = t8 ^ t9;
2477 + t12 = e | t9;
2478 + f = t11 ^ t12;
2479 + t14 = a & g;
2480 + t15 = t2 ^ t14;
2481 + t16 = e & t15;
2482 + h = t4 ^ t16
2483 + e ^= key[4 * 27 + 8]
2484 + f ^= key[4 * 27 + 9]
2485 + g ^= key[4 * 27 + 10]
2486 + h ^= key[4 * 27 + 11]
2487 + g = rotr32(g, 22)
2488 + e = rotr32(e, 5)
2489 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2490 + e ^= f ^ h
2491 + h = rotr32(h, 7)
2492 + f = rotr32(f, 1)
2493 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2494 + f ^= e ^ g
2495 + g = rotr32(g, 3)
2496 + e = rotr32(e, 13)
2497 + t1 = f ^ h;
2498 + t2 = (~t1) % 0x100000000;
2499 + t3 = e ^ g;
2500 + t4 = g ^ t1;
2501 + t7 = e | t2;
2502 + t5 = f & t4;
2503 + t8 = h ^ t7;
2504 + t11 = (~t4) % 0x100000000;
2505 + a = t3 ^ t5;
2506 + t9 = t3 | t8;
2507 + t14 = h & t11;
2508 + d = t1 ^ t9;
2509 + t12 = a | d;
2510 + b = t11 ^ t12;
2511 + t15 = t3 ^ t12;
2512 + c = t14 ^ t15
2513 + a ^= key[4 * 26 + 8]
2514 + b ^= key[4 * 26 + 9]
2515 + c ^= key[4 * 26 + 10]
2516 + d ^= key[4 * 26 + 11]
2517 + c = rotr32(c, 22)
2518 + a = rotr32(a, 5)
2519 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2520 + a ^= b ^ d
2521 + d = rotr32(d, 7)
2522 + b = rotr32(b, 1)
2523 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2524 + b ^= a ^ c
2525 + c = rotr32(c, 3)
2526 + a = rotr32(a, 13)
2527 + t1 = a ^ d;
2528 + t2 = a & b;
2529 + t3 = b ^ c;
2530 + t4 = a ^ t3;
2531 + t5 = b | d;
2532 + t7 = c | t1;
2533 + h = t4 ^ t5;
2534 + t8 = b ^ t7;
2535 + t11 = (~t2) % 0x100000000;
2536 + t9 = t4 & t8;
2537 + f = t1 ^ t9;
2538 + t13 = t9 ^ t11;
2539 + t12 = h & f;
2540 + g = t12 ^ t13;
2541 + t15 = a & d;
2542 + t16 = c ^ t13;
2543 + e = t15 ^ t16
2544 + e ^= key[4 * 25 + 8]
2545 + f ^= key[4 * 25 + 9]
2546 + g ^= key[4 * 25 + 10]
2547 + h ^= key[4 * 25 + 11]
2548 + g = rotr32(g, 22)
2549 + e = rotr32(e, 5)
2550 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2551 + e ^= f ^ h
2552 + h = rotr32(h, 7)
2553 + f = rotr32(f, 1)
2554 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2555 + f ^= e ^ g
2556 + g = rotr32(g, 3)
2557 + e = rotr32(e, 13)
2558 + t1 = (~e) % 0x100000000
2559 + t2 = e ^ f
2560 + t3 = t1 | t2
2561 + t4 = h ^ t3
2562 + t7 = h & t2
2563 + t5 = g ^ t4
2564 + t8 = t1 ^ t7
2565 + c = t2 ^ t5
2566 + t11 = e & t4
2567 + t9 = c & t8
2568 + t14 = t5 ^ t8
2569 + b = t4 ^ t9
2570 + t12 = t5 | b
2571 + d = t11 ^ t12
2572 + a = d ^ t14
2573 + a ^= key[4 * 24 + 8]
2574 + b ^= key[4 * 24 + 9]
2575 + c ^= key[4 * 24 + 10]
2576 + d ^= key[4 * 24 + 11]
2577 + c = rotr32(c, 22)
2578 + a = rotr32(a, 5)
2579 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2580 + a ^= b ^ d
2581 + d = rotr32(d, 7)
2582 + b = rotr32(b, 1)
2583 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2584 + b ^= a ^ c
2585 + c = rotr32(c, 3)
2586 + a = rotr32(a, 13)
2587 + t1 = a & b;
2588 + t2 = a | b;
2589 + t3 = c | t1;
2590 + t4 = d & t2;
2591 + h = t3 ^ t4;
2592 + t6 = (~d) % 0x100000000;
2593 + t7 = b ^ t4;
2594 + t8 = h ^ t6;
2595 + t11 = c ^ t7;
2596 + t9 = t7 | t8;
2597 + f = a ^ t9;
2598 + t12 = d | f;
2599 + e = t11 ^ t12;
2600 + t14 = a & h;
2601 + t15 = t3 ^ f;
2602 + t16 = e ^ t14;
2603 + g = t15 ^ t16
2604 + e ^= key[4 * 23 + 8]
2605 + f ^= key[4 * 23 + 9]
2606 + g ^= key[4 * 23 + 10]
2607 + h ^= key[4 * 23 + 11]
2608 + g = rotr32(g, 22)
2609 + e = rotr32(e, 5)
2610 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2611 + e ^= f ^ h
2612 + h = rotr32(h, 7)
2613 + f = rotr32(f, 1)
2614 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2615 + f ^= e ^ g
2616 + g = rotr32(g, 3)
2617 + e = rotr32(e, 13)
2618 + t1 = (~e) % 0x100000000;
2619 + t2 = e ^ f;
2620 + t3 = g ^ t2;
2621 + t4 = g | t1;
2622 + t5 = h ^ t4;
2623 + t13 = h & t1;
2624 + b = t3 ^ t5;
2625 + t7 = t3 & t5;
2626 + t8 = t2 ^ t7;
2627 + t9 = f | t8;
2628 + d = t5 ^ t9;
2629 + t11 = f | d;
2630 + a = t8 ^ t11;
2631 + t14 = t3 ^ t11;
2632 + c = t13 ^ t14
2633 + a ^= key[4 * 22 + 8]
2634 + b ^= key[4 * 22 + 9]
2635 + c ^= key[4 * 22 + 10]
2636 + d ^= key[4 * 22 + 11]
2637 + c = rotr32(c, 22)
2638 + a = rotr32(a, 5)
2639 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2640 + a ^= b ^ d
2641 + d = rotr32(d, 7)
2642 + b = rotr32(b, 1)
2643 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2644 + b ^= a ^ c
2645 + c = rotr32(c, 3)
2646 + a = rotr32(a, 13)
2647 + t1 = (~c) % 0x100000000;
2648 + t2 = b & t1;
2649 + t3 = d ^ t2;
2650 + t4 = a & t3;
2651 + t5 = b ^ t1;
2652 + h = t4 ^ t5;
2653 + t7 = b | h;
2654 + t8 = a & t7;
2655 + f = t3 ^ t8;
2656 + t10 = a | d;
2657 + t11 = t1 ^ t7;
2658 + e = t10 ^ t11;
2659 + t13 = a ^ c;
2660 + t14 = b & t10;
2661 + t15 = t4 | t13;
2662 + g = t14 ^ t15
2663 + e ^= key[4 * 21 + 8]
2664 + f ^= key[4 * 21 + 9]
2665 + g ^= key[4 * 21 + 10]
2666 + h ^= key[4 * 21 + 11]
2667 + g = rotr32(g, 22)
2668 + e = rotr32(e, 5)
2669 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2670 + e ^= f ^ h
2671 + h = rotr32(h, 7)
2672 + f = rotr32(f, 1)
2673 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2674 + f ^= e ^ g
2675 + g = rotr32(g, 3)
2676 + e = rotr32(e, 13)
2677 + t1 = g ^ h;
2678 + t2 = g | h;
2679 + t3 = f ^ t2;
2680 + t4 = e & t3;
2681 + b = t1 ^ t4;
2682 + t6 = e ^ h;
2683 + t7 = f | h;
2684 + t8 = t6 & t7;
2685 + d = t3 ^ t8;
2686 + t10 = (~e) % 0x100000000;
2687 + t11 = g ^ d;
2688 + t12 = t10 | t11;
2689 + a = t3 ^ t12;
2690 + t14 = g | t4;
2691 + t15 = t7 ^ t14;
2692 + t16 = d | t10;
2693 + c = t15 ^ t16
2694 + a ^= key[4 * 20 + 8]
2695 + b ^= key[4 * 20 + 9]
2696 + c ^= key[4 * 20 + 10]
2697 + d ^= key[4 * 20 + 11]
2698 + c = rotr32(c, 22)
2699 + a = rotr32(a, 5)
2700 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2701 + a ^= b ^ d
2702 + d = rotr32(d, 7)
2703 + b = rotr32(b, 1)
2704 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2705 + b ^= a ^ c
2706 + c = rotr32(c, 3)
2707 + a = rotr32(a, 13)
2708 + t1 = b ^ c;
2709 + t2 = b | c;
2710 + t3 = a ^ c;
2711 + t7 = a ^ d;
2712 + t4 = t2 ^ t3;
2713 + t5 = d | t4;
2714 + t9 = t2 ^ t7;
2715 + e = t1 ^ t5;
2716 + t8 = t1 | t5;
2717 + t11 = a & t4;
2718 + g = t8 ^ t9;
2719 + t12 = e | t9;
2720 + f = t11 ^ t12;
2721 + t14 = a & g;
2722 + t15 = t2 ^ t14;
2723 + t16 = e & t15;
2724 + h = t4 ^ t16
2725 + e ^= key[4 * 19 + 8]
2726 + f ^= key[4 * 19 + 9]
2727 + g ^= key[4 * 19 + 10]
2728 + h ^= key[4 * 19 + 11]
2729 + g = rotr32(g, 22)
2730 + e = rotr32(e, 5)
2731 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2732 + e ^= f ^ h
2733 + h = rotr32(h, 7)
2734 + f = rotr32(f, 1)
2735 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2736 + f ^= e ^ g
2737 + g = rotr32(g, 3)
2738 + e = rotr32(e, 13)
2739 + t1 = f ^ h;
2740 + t2 = (~t1) % 0x100000000;
2741 + t3 = e ^ g;
2742 + t4 = g ^ t1;
2743 + t7 = e | t2;
2744 + t5 = f & t4;
2745 + t8 = h ^ t7;
2746 + t11 = (~t4) % 0x100000000;
2747 + a = t3 ^ t5;
2748 + t9 = t3 | t8;
2749 + t14 = h & t11;
2750 + d = t1 ^ t9;
2751 + t12 = a | d;
2752 + b = t11 ^ t12;
2753 + t15 = t3 ^ t12;
2754 + c = t14 ^ t15
2755 + a ^= key[4 * 18 + 8]
2756 + b ^= key[4 * 18 + 9]
2757 + c ^= key[4 * 18 + 10]
2758 + d ^= key[4 * 18 + 11]
2759 + c = rotr32(c, 22)
2760 + a = rotr32(a, 5)
2761 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2762 + a ^= b ^ d
2763 + d = rotr32(d, 7)
2764 + b = rotr32(b, 1)
2765 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2766 + b ^= a ^ c
2767 + c = rotr32(c, 3)
2768 + a = rotr32(a, 13)
2769 + t1 = a ^ d;
2770 + t2 = a & b;
2771 + t3 = b ^ c;
2772 + t4 = a ^ t3;
2773 + t5 = b | d;
2774 + t7 = c | t1;
2775 + h = t4 ^ t5;
2776 + t8 = b ^ t7;
2777 + t11 = (~t2) % 0x100000000;
2778 + t9 = t4 & t8;
2779 + f = t1 ^ t9;
2780 + t13 = t9 ^ t11;
2781 + t12 = h & f;
2782 + g = t12 ^ t13;
2783 + t15 = a & d;
2784 + t16 = c ^ t13;
2785 + e = t15 ^ t16
2786 + e ^= key[4 * 17 + 8]
2787 + f ^= key[4 * 17 + 9]
2788 + g ^= key[4 * 17 + 10]
2789 + h ^= key[4 * 17 + 11]
2790 + g = rotr32(g, 22)
2791 + e = rotr32(e, 5)
2792 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2793 + e ^= f ^ h
2794 + h = rotr32(h, 7)
2795 + f = rotr32(f, 1)
2796 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2797 + f ^= e ^ g
2798 + g = rotr32(g, 3)
2799 + e = rotr32(e, 13)
2800 + t1 = (~e) % 0x100000000
2801 + t2 = e ^ f
2802 + t3 = t1 | t2
2803 + t4 = h ^ t3
2804 + t7 = h & t2
2805 + t5 = g ^ t4
2806 + t8 = t1 ^ t7
2807 + c = t2 ^ t5
2808 + t11 = e & t4
2809 + t9 = c & t8
2810 + t14 = t5 ^ t8
2811 + b = t4 ^ t9
2812 + t12 = t5 | b
2813 + d = t11 ^ t12
2814 + a = d ^ t14
2815 + a ^= key[4 * 16 + 8]
2816 + b ^= key[4 * 16 + 9]
2817 + c ^= key[4 * 16 + 10]
2818 + d ^= key[4 * 16 + 11]
2819 + c = rotr32(c, 22)
2820 + a = rotr32(a, 5)
2821 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2822 + a ^= b ^ d
2823 + d = rotr32(d, 7)
2824 + b = rotr32(b, 1)
2825 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2826 + b ^= a ^ c
2827 + c = rotr32(c, 3)
2828 + a = rotr32(a, 13)
2829 + t1 = a & b;
2830 + t2 = a | b;
2831 + t3 = c | t1;
2832 + t4 = d & t2;
2833 + h = t3 ^ t4;
2834 + t6 = (~d) % 0x100000000;
2835 + t7 = b ^ t4;
2836 + t8 = h ^ t6;
2837 + t11 = c ^ t7;
2838 + t9 = t7 | t8;
2839 + f = a ^ t9;
2840 + t12 = d | f;
2841 + e = t11 ^ t12;
2842 + t14 = a & h;
2843 + t15 = t3 ^ f;
2844 + t16 = e ^ t14;
2845 + g = t15 ^ t16
2846 + e ^= key[4 * 15 + 8]
2847 + f ^= key[4 * 15 + 9]
2848 + g ^= key[4 * 15 + 10]
2849 + h ^= key[4 * 15 + 11]
2850 + g = rotr32(g, 22)
2851 + e = rotr32(e, 5)
2852 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2853 + e ^= f ^ h
2854 + h = rotr32(h, 7)
2855 + f = rotr32(f, 1)
2856 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2857 + f ^= e ^ g
2858 + g = rotr32(g, 3)
2859 + e = rotr32(e, 13)
2860 + t1 = (~e) % 0x100000000;
2861 + t2 = e ^ f;
2862 + t3 = g ^ t2;
2863 + t4 = g | t1;
2864 + t5 = h ^ t4;
2865 + t13 = h & t1;
2866 + b = t3 ^ t5;
2867 + t7 = t3 & t5;
2868 + t8 = t2 ^ t7;
2869 + t9 = f | t8;
2870 + d = t5 ^ t9;
2871 + t11 = f | d;
2872 + a = t8 ^ t11;
2873 + t14 = t3 ^ t11;
2874 + c = t13 ^ t14
2875 + a ^= key[4 * 14 + 8]
2876 + b ^= key[4 * 14 + 9]
2877 + c ^= key[4 * 14 + 10]
2878 + d ^= key[4 * 14 + 11]
2879 + c = rotr32(c, 22)
2880 + a = rotr32(a, 5)
2881 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2882 + a ^= b ^ d
2883 + d = rotr32(d, 7)
2884 + b = rotr32(b, 1)
2885 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2886 + b ^= a ^ c
2887 + c = rotr32(c, 3)
2888 + a = rotr32(a, 13)
2889 + t1 = (~c) % 0x100000000;
2890 + t2 = b & t1;
2891 + t3 = d ^ t2;
2892 + t4 = a & t3;
2893 + t5 = b ^ t1;
2894 + h = t4 ^ t5;
2895 + t7 = b | h;
2896 + t8 = a & t7;
2897 + f = t3 ^ t8;
2898 + t10 = a | d;
2899 + t11 = t1 ^ t7;
2900 + e = t10 ^ t11;
2901 + t13 = a ^ c;
2902 + t14 = b & t10;
2903 + t15 = t4 | t13;
2904 + g = t14 ^ t15
2905 + e ^= key[4 * 13 + 8]
2906 + f ^= key[4 * 13 + 9]
2907 + g ^= key[4 * 13 + 10]
2908 + h ^= key[4 * 13 + 11]
2909 + g = rotr32(g, 22)
2910 + e = rotr32(e, 5)
2911 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2912 + e ^= f ^ h
2913 + h = rotr32(h, 7)
2914 + f = rotr32(f, 1)
2915 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2916 + f ^= e ^ g
2917 + g = rotr32(g, 3)
2918 + e = rotr32(e, 13)
2919 + t1 = g ^ h;
2920 + t2 = g | h;
2921 + t3 = f ^ t2;
2922 + t4 = e & t3;
2923 + b = t1 ^ t4;
2924 + t6 = e ^ h;
2925 + t7 = f | h;
2926 + t8 = t6 & t7;
2927 + d = t3 ^ t8;
2928 + t10 = (~e) % 0x100000000;
2929 + t11 = g ^ d;
2930 + t12 = t10 | t11;
2931 + a = t3 ^ t12;
2932 + t14 = g | t4;
2933 + t15 = t7 ^ t14;
2934 + t16 = d | t10;
2935 + c = t15 ^ t16
2936 + a ^= key[4 * 12 + 8]
2937 + b ^= key[4 * 12 + 9]
2938 + c ^= key[4 * 12 + 10]
2939 + d ^= key[4 * 12 + 11]
2940 + c = rotr32(c, 22)
2941 + a = rotr32(a, 5)
2942 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
2943 + a ^= b ^ d
2944 + d = rotr32(d, 7)
2945 + b = rotr32(b, 1)
2946 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
2947 + b ^= a ^ c
2948 + c = rotr32(c, 3)
2949 + a = rotr32(a, 13)
2950 + t1 = b ^ c;
2951 + t2 = b | c;
2952 + t3 = a ^ c;
2953 + t7 = a ^ d;
2954 + t4 = t2 ^ t3;
2955 + t5 = d | t4;
2956 + t9 = t2 ^ t7;
2957 + e = t1 ^ t5;
2958 + t8 = t1 | t5;
2959 + t11 = a & t4;
2960 + g = t8 ^ t9;
2961 + t12 = e | t9;
2962 + f = t11 ^ t12;
2963 + t14 = a & g;
2964 + t15 = t2 ^ t14;
2965 + t16 = e & t15;
2966 + h = t4 ^ t16
2967 + e ^= key[4 * 11 + 8]
2968 + f ^= key[4 * 11 + 9]
2969 + g ^= key[4 * 11 + 10]
2970 + h ^= key[4 * 11 + 11]
2971 + g = rotr32(g, 22)
2972 + e = rotr32(e, 5)
2973 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
2974 + e ^= f ^ h
2975 + h = rotr32(h, 7)
2976 + f = rotr32(f, 1)
2977 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
2978 + f ^= e ^ g
2979 + g = rotr32(g, 3)
2980 + e = rotr32(e, 13)
2981 + t1 = f ^ h;
2982 + t2 = (~t1) % 0x100000000;
2983 + t3 = e ^ g;
2984 + t4 = g ^ t1;
2985 + t7 = e | t2;
2986 + t5 = f & t4;
2987 + t8 = h ^ t7;
2988 + t11 = (~t4) % 0x100000000;
2989 + a = t3 ^ t5;
2990 + t9 = t3 | t8;
2991 + t14 = h & t11;
2992 + d = t1 ^ t9;
2993 + t12 = a | d;
2994 + b = t11 ^ t12;
2995 + t15 = t3 ^ t12;
2996 + c = t14 ^ t15
2997 + a ^= key[4 * 10 + 8]
2998 + b ^= key[4 * 10 + 9]
2999 + c ^= key[4 * 10 + 10]
3000 + d ^= key[4 * 10 + 11]
3001 + c = rotr32(c, 22)
3002 + a = rotr32(a, 5)
3003 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
3004 + a ^= b ^ d
3005 + d = rotr32(d, 7)
3006 + b = rotr32(b, 1)
3007 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
3008 + b ^= a ^ c
3009 + c = rotr32(c, 3)
3010 + a = rotr32(a, 13)
3011 + t1 = a ^ d;
3012 + t2 = a & b;
3013 + t3 = b ^ c;
3014 + t4 = a ^ t3;
3015 + t5 = b | d;
3016 + t7 = c | t1;
3017 + h = t4 ^ t5;
3018 + t8 = b ^ t7;
3019 + t11 = (~t2) % 0x100000000;
3020 + t9 = t4 & t8;
3021 + f = t1 ^ t9;
3022 + t13 = t9 ^ t11;
3023 + t12 = h & f;
3024 + g = t12 ^ t13;
3025 + t15 = a & d;
3026 + t16 = c ^ t13;
3027 + e = t15 ^ t16
3028 + e ^= key[4 * 9 + 8]
3029 + f ^= key[4 * 9 + 9]
3030 + g ^= key[4 * 9 + 10]
3031 + h ^= key[4 * 9 + 11]
3032 + g = rotr32(g, 22)
3033 + e = rotr32(e, 5)
3034 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
3035 + e ^= f ^ h
3036 + h = rotr32(h, 7)
3037 + f = rotr32(f, 1)
3038 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
3039 + f ^= e ^ g
3040 + g = rotr32(g, 3)
3041 + e = rotr32(e, 13)
3042 + t1 = (~e) % 0x100000000
3043 + t2 = e ^ f
3044 + t3 = t1 | t2
3045 + t4 = h ^ t3
3046 + t7 = h & t2
3047 + t5 = g ^ t4
3048 + t8 = t1 ^ t7
3049 + c = t2 ^ t5
3050 + t11 = e & t4
3051 + t9 = c & t8
3052 + t14 = t5 ^ t8
3053 + b = t4 ^ t9
3054 + t12 = t5 | b
3055 + d = t11 ^ t12
3056 + a = d ^ t14
3057 + a ^= key[4 * 8 + 8]
3058 + b ^= key[4 * 8 + 9]
3059 + c ^= key[4 * 8 + 10]
3060 + d ^= key[4 * 8 + 11]
3061 + c = rotr32(c, 22)
3062 + a = rotr32(a, 5)
3063 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
3064 + a ^= b ^ d
3065 + d = rotr32(d, 7)
3066 + b = rotr32(b, 1)
3067 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
3068 + b ^= a ^ c
3069 + c = rotr32(c, 3)
3070 + a = rotr32(a, 13)
3071 + t1 = a & b;
3072 + t2 = a | b;
3073 + t3 = c | t1;
3074 + t4 = d & t2;
3075 + h = t3 ^ t4;
3076 + t6 = (~d) % 0x100000000;
3077 + t7 = b ^ t4;
3078 + t8 = h ^ t6;
3079 + t11 = c ^ t7;
3080 + t9 = t7 | t8;
3081 + f = a ^ t9;
3082 + t12 = d | f;
3083 + e = t11 ^ t12;
3084 + t14 = a & h;
3085 + t15 = t3 ^ f;
3086 + t16 = e ^ t14;
3087 + g = t15 ^ t16
3088 + e ^= key[4 * 7 + 8]
3089 + f ^= key[4 * 7 + 9]
3090 + g ^= key[4 * 7 + 10]
3091 + h ^= key[4 * 7 + 11]
3092 + g = rotr32(g, 22)
3093 + e = rotr32(e, 5)
3094 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
3095 + e ^= f ^ h
3096 + h = rotr32(h, 7)
3097 + f = rotr32(f, 1)
3098 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
3099 + f ^= e ^ g
3100 + g = rotr32(g, 3)
3101 + e = rotr32(e, 13)
3102 + t1 = (~e) % 0x100000000;
3103 + t2 = e ^ f;
3104 + t3 = g ^ t2;
3105 + t4 = g | t1;
3106 + t5 = h ^ t4;
3107 + t13 = h & t1;
3108 + b = t3 ^ t5;
3109 + t7 = t3 & t5;
3110 + t8 = t2 ^ t7;
3111 + t9 = f | t8;
3112 + d = t5 ^ t9;
3113 + t11 = f | d;
3114 + a = t8 ^ t11;
3115 + t14 = t3 ^ t11;
3116 + c = t13 ^ t14
3117 + a ^= key[4 * 6 + 8]
3118 + b ^= key[4 * 6 + 9]
3119 + c ^= key[4 * 6 + 10]
3120 + d ^= key[4 * 6 + 11]
3121 + c = rotr32(c, 22)
3122 + a = rotr32(a, 5)
3123 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
3124 + a ^= b ^ d
3125 + d = rotr32(d, 7)
3126 + b = rotr32(b, 1)
3127 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
3128 + b ^= a ^ c
3129 + c = rotr32(c, 3)
3130 + a = rotr32(a, 13)
3131 + t1 = (~c) % 0x100000000;
3132 + t2 = b & t1;
3133 + t3 = d ^ t2;
3134 + t4 = a & t3;
3135 + t5 = b ^ t1;
3136 + h = t4 ^ t5;
3137 + t7 = b | h;
3138 + t8 = a & t7;
3139 + f = t3 ^ t8;
3140 + t10 = a | d;
3141 + t11 = t1 ^ t7;
3142 + e = t10 ^ t11;
3143 + t13 = a ^ c;
3144 + t14 = b & t10;
3145 + t15 = t4 | t13;
3146 + g = t14 ^ t15
3147 + e ^= key[4 * 5 + 8]
3148 + f ^= key[4 * 5 + 9]
3149 + g ^= key[4 * 5 + 10]
3150 + h ^= key[4 * 5 + 11]
3151 + g = rotr32(g, 22)
3152 + e = rotr32(e, 5)
3153 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
3154 + e ^= f ^ h
3155 + h = rotr32(h, 7)
3156 + f = rotr32(f, 1)
3157 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
3158 + f ^= e ^ g
3159 + g = rotr32(g, 3)
3160 + e = rotr32(e, 13)
3161 + t1 = g ^ h;
3162 + t2 = g | h;
3163 + t3 = f ^ t2;
3164 + t4 = e & t3;
3165 + b = t1 ^ t4;
3166 + t6 = e ^ h;
3167 + t7 = f | h;
3168 + t8 = t6 & t7;
3169 + d = t3 ^ t8;
3170 + t10 = (~e) % 0x100000000;
3171 + t11 = g ^ d;
3172 + t12 = t10 | t11;
3173 + a = t3 ^ t12;
3174 + t14 = g | t4;
3175 + t15 = t7 ^ t14;
3176 + t16 = d | t10;
3177 + c = t15 ^ t16
3178 + a ^= key[4 * 4 + 8]
3179 + b ^= key[4 * 4 + 9]
3180 + c ^= key[4 * 4 + 10]
3181 + d ^= key[4 * 4 + 11]
3182 + c = rotr32(c, 22)
3183 + a = rotr32(a, 5)
3184 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
3185 + a ^= b ^ d
3186 + d = rotr32(d, 7)
3187 + b = rotr32(b, 1)
3188 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
3189 + b ^= a ^ c
3190 + c = rotr32(c, 3)
3191 + a = rotr32(a, 13)
3192 + t1 = b ^ c;
3193 + t2 = b | c;
3194 + t3 = a ^ c;
3195 + t7 = a ^ d;
3196 + t4 = t2 ^ t3;
3197 + t5 = d | t4;
3198 + t9 = t2 ^ t7;
3199 + e = t1 ^ t5;
3200 + t8 = t1 | t5;
3201 + t11 = a & t4;
3202 + g = t8 ^ t9;
3203 + t12 = e | t9;
3204 + f = t11 ^ t12;
3205 + t14 = a & g;
3206 + t15 = t2 ^ t14;
3207 + t16 = e & t15;
3208 + h = t4 ^ t16
3209 + e ^= key[4 * 3 + 8]
3210 + f ^= key[4 * 3 + 9]
3211 + g ^= key[4 * 3 + 10]
3212 + h ^= key[4 * 3 + 11]
3213 + g = rotr32(g, 22)
3214 + e = rotr32(e, 5)
3215 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
3216 + e ^= f ^ h
3217 + h = rotr32(h, 7)
3218 + f = rotr32(f, 1)
3219 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
3220 + f ^= e ^ g
3221 + g = rotr32(g, 3)
3222 + e = rotr32(e, 13)
3223 + t1 = f ^ h;
3224 + t2 = (~t1) % 0x100000000;
3225 + t3 = e ^ g;
3226 + t4 = g ^ t1;
3227 + t7 = e | t2;
3228 + t5 = f & t4;
3229 + t8 = h ^ t7;
3230 + t11 = (~t4) % 0x100000000;
3231 + a = t3 ^ t5;
3232 + t9 = t3 | t8;
3233 + t14 = h & t11;
3234 + d = t1 ^ t9;
3235 + t12 = a | d;
3236 + b = t11 ^ t12;
3237 + t15 = t3 ^ t12;
3238 + c = t14 ^ t15
3239 + a ^= key[4 * 2 + 8]
3240 + b ^= key[4 * 2 + 9]
3241 + c ^= key[4 * 2 + 10]
3242 + d ^= key[4 * 2 + 11]
3243 + c = rotr32(c, 22)
3244 + a = rotr32(a, 5)
3245 + c ^= d ^ ((b << 7) & 0xFFFFFFFF)
3246 + a ^= b ^ d
3247 + d = rotr32(d, 7)
3248 + b = rotr32(b, 1)
3249 + d ^= c ^ ((a << 3) & 0xFFFFFFFF)
3250 + b ^= a ^ c
3251 + c = rotr32(c, 3)
3252 + a = rotr32(a, 13)
3253 + t1 = a ^ d;
3254 + t2 = a & b;
3255 + t3 = b ^ c;
3256 + t4 = a ^ t3;
3257 + t5 = b | d;
3258 + t7 = c | t1;
3259 + h = t4 ^ t5;
3260 + t8 = b ^ t7;
3261 + t11 = (~t2) % 0x100000000;
3262 + t9 = t4 & t8;
3263 + f = t1 ^ t9;
3264 + t13 = t9 ^ t11;
3265 + t12 = h & f;
3266 + g = t12 ^ t13;
3267 + t15 = a & d;
3268 + t16 = c ^ t13;
3269 + e = t15 ^ t16
3270 + e ^= key[4 * 1 + 8]
3271 + f ^= key[4 * 1 + 9]
3272 + g ^= key[4 * 1 + 10]
3273 + h ^= key[4 * 1 + 11]
3274 + g = rotr32(g, 22)
3275 + e = rotr32(e, 5)
3276 + g ^= h ^ ((f << 7) & 0xFFFFFFFF)
3277 + e ^= f ^ h
3278 + h = rotr32(h, 7)
3279 + f = rotr32(f, 1)
3280 + h ^= g ^ ((e << 3) & 0xFFFFFFFF)
3281 + f ^= e ^ g
3282 + g = rotr32(g, 3)
3283 + e = rotr32(e, 13)
3284 + t1 = (~e) % 0x100000000
3285 + t2 = e ^ f
3286 + t3 = t1 | t2
3287 + t4 = h ^ t3
3288 + t7 = h & t2
3289 + t5 = g ^ t4
3290 + t8 = t1 ^ t7
3291 + c = t2 ^ t5
3292 + t11 = e & t4
3293 + t9 = c & t8
3294 + t14 = t5 ^ t8
3295 + b = t4 ^ t9
3296 + t12 = t5 | b
3297 + d = t11 ^ t12
3298 + a = d ^ t14
3299 + a ^= key[4 * 0 + 8]
3300 + b ^= key[4 * 0 + 9]
3301 + c ^= key[4 * 0 + 10]
3302 + d ^= key[4 * 0 + 11]
3303 + if WORD_BIGENDIAN:
3304 + a = byteswap32(a)
3305 + b = byteswap32(b)
3306 + c = byteswap32(c)
3307 + d = byteswap32(d)
3308 + in_blk[0] = a
3309 + in_blk[1] = b
3310 + in_blk[2] = c
3311 + in_blk[3] = d
3312 +
3313 +__testkey = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f'
3314 +__testdat = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
3315 +assert '\xde&\x9f\xf83\xe42\xb8[.\x88\xd2p\x1c\xe7\\' == Serpent(__testkey).encrypt(__testdat)
3316 +assert __testdat == Serpent(__testkey).decrypt('\xde&\x9f\xf83\xe42\xb8[.\x88\xd2p\x1c\xe7\\')
3317 +
3318 +#CBC Encrypt - Jason Reaves
3319 +def serpent_cbc_encrypt(key, data, iv='\x00'*16):
3320 + out = ""
3321 + last = iv
3322 + for i in range((len(data)/16)):
3323 + temp = data[i*16:(i+1)*16]
3324 + to_encode = ""
3325 + for j in range(4):
3326 + temp1 = struct.unpack_from('<I', temp[j*4:])[0]
3327 + temp2 = struct.unpack_from('<I', last[j*4:])[0]
3328 + to_encode += struct.pack('<I',((temp1 ^ temp2) & 0xffffffff))
3329 + last= Serpent(key).encrypt(to_encode)
3330 + out += last
3331 + #print(binascii.hexlify(Serpent(key).encrypt(data)))
3332 + return out
3333 +
3334 +#CBC Decrypt - Jason Reaves
3335 +def serpent_cbc_decrypt(key,data,iv='\x00'*16):
3336 + out2 = ""
3337 + last = iv
3338 + for i in range((len(data)/16)):
3339 + temp = Serpent(key).decrypt(data[i*16:(i+1)*16])
3340 + to_decode = ""
3341 + for j in range(4):
3342 + temp1 = struct.unpack_from('<I', temp[j*4:])[0]
3343 + temp2 = struct.unpack_from('<I', last[j*4:])[0]
3344 + to_decode += struct.pack('<I',((temp1 ^ temp2) & 0xffffffff))
3345 + out2 += to_decode
3346 + last = data[i*16:(i+1)*16]
3347 + return out2
3348 diff --git a/lib/server.py b/lib/server.py
3349 index 38bf514..a248f1a 100644
3350 --- a/lib/server.py
3351 +++ b/lib/server.py
3352 @@ -167,10 +167,9 @@ class Server(object):
3353 if x == udp_server_socket:
3354 bytes_address_pair = udp_server_socket.recvfrom(PACKET_SIZE)
3355 message = bytes_address_pair[0]
3356 - address = bytes_address_pair[1]
3357 - print message
3358 + address = bytes_address_pair[1][0]
3359 for c in self.clients:
3360 - self.clients[c].udp_data_received(message)
3361 + self.clients[c].udp_data_received(address, message)
3362 for x in iwtd:
3363 if x in self.clients:
3364 self.clients[x].socket_readable_notification()
3365
  1. I haven't thoroughly read through this library. It has many many lines of single letter variables. I don't know if this is the standard in implementation of encryption algos but it looks like it will be very difficult to properly fit in head. In addition, the purported authors insist that the implementation is very slow. I don't know how important that is since nothing gets decrypted until the hash is validated, which should be much faster. []

Alcuin

Friday, July 30th, 2021

Below is an updated version of the IRC emulator using UDP for links between servers that I published recently. In this update:

  • A new name: alcuin
  • Refactored into multiple files for ease of reading
  • Messages are now sent between servers with hashes attached so that garbage messages can be easily discarded
  • Apparently JOIN/PART messages are actually supported, your server just has to be running in time to catch them
  • Added a perfunctory amount of documentation in README.txt

Download the source.

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/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/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

Prototype UDP based IRC Emulator

Wednesday, July 28th, 2021

I have hacked up a very simple IRC server written in python that broadcasts messages via UDP to a list of other servers of the same type.
The original IRC server is from this Shithub. I made this mostly just to experiment with UDP and learn something about the IRC protocol.

At the moment all it will do is share PRIVMSGs between servers. It doesn't handle pretty much anything else, such as for example JOIN/PART messages.

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

The Comms Chronicle

Sunday, July 4th, 2021

I attempt here to gather together and make a succinct chronical of the comms network(s) efforts discussed in the logs over time.

The first item I can recall proposed was gossipd.

This item is no longer under discussion for reasons that are not clear to me due to my inability to properly study and digest the comment thread in the above article.

As Freenode IRC's ultimate decline and collapse has become evidently imminent, asciilifeform has begun evacuation procedures. The first step he took was to stand up dulapnet, his own ircd running the Unreal ircd.

Subsequently, members of #asciilifeform, including signpost, gregorynyssa, asciilifeform, and myself attempted to form a new type of IRC network in which each participant runs his own ircd. Each ircd would be linked to each other ircd. In addition to being decentralized, such a network would have the advantage of being accessible via any existing IRC compliant client. We then discovered that IRC networks do not allow for cycles, and can only exist in a tree topology. This topology was not acceptable to asciilifeform or signpost due to centralizing requirement for there to be a root node1.

Discussion moved on to another type of network in which the peers would communicate via UDP, but clients would connect to peers using the IRC protocol. It would be trivial to architect such a network using shared secret keys. However, asciilifeform also proposes that this network should allow unknowns to broadcast to the network via a PoW mechanism. This would allow unknown and untrusted entities to gain entry into the network without manual intervention, which would allow anyone to drop in for a chat while at the same time minimizing the attack surface of the network, eliminating the need for anyone to check their email ever again.

At the present time, this type of network is not feasible due to the apparent intractability of sharing the required difficulty level for the PoW mechanism. I'm unaware of any further actions I or any other interested parties can take to make progress down this path.

Signpost is investigating a third network type based on the Kademlia DHT. Objections to this approach are here

It is interesting to note that Freenode ultimately did not succumb to any sort of technical attack such as a ddos, but due to sabotage by long time admins who walked away with user data stored in a centralized database.

  1. This network is notable that it is the first publicly available prototype of any potential comms network discussed in the logs []

Log link index

Sunday, June 13th, 2021

A .sql file containing an index that allows searching by content linked to in the logs is available here. It's only about 200mb compressed.