-
Notifications
You must be signed in to change notification settings - Fork 1
/
mscchelper-0.02b.py
308 lines (273 loc) · 11.1 KB
/
mscchelper-0.02b.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# Microsoft Comic Chat Helper for Freenode V0.02b
#
# README:
# This script allows Microsoft Comic Chat 2.5 (4.71.2302) to be used with
# Freenode.
#
# Freenode does not use some formatting that MSCC depends on for operation. As
# a result, MSCC will crash upon joining Freenode channels. This script
# provides a fix by creating a ``server socket'' on loclhost. Once MSCC
# connects to it, the script establishes a connection with Freenode. It begins
# forwarding MSCC's input to Freenode and sanitizing Freenode's responses
# before sending them back to MSCC.
#
# USAGE:
# Run this script, enter Freenode username and password, leave it running in background.
# Connect MSCC to the address '127.0.0.1:6660' or whatever options are set.
# If you are a terrible person, hard-code Freenode username and password into this script.
#
# CHANGES:
# 7/1/2014 - V0.01 - Initial Release. Raleigh NC to Little Rock AK
# 7/2/2014 - V0.02 - Disabled removing channel mode list (not necessary)
# - Added SSL support
# - Made passwords secure
# - Little Rock AK to the middle of nowhere in Oklahoma
# V0.02a - Made use numeral cues (as opposed to language cues) for control
# - Added some more exception handling
# 7/3/2014 - V0.02b - Fixed greedy sanitation bug (deleting too many chars)
# - Made empty recv()s throw ConnectionAbortedError
# - Restructured exception handling
# - Fixed abort on MSCC disconnect or connection lost
# KNOWN ISSUES:
# - Can't send messages containing ` JOIN #'
# - Blocking operations (connect, waitign for client connect) cause hang that can't be aborted with ^C ^X ^Z.
# - After a connection dies, sockets stay alive for a long time. Can only be
# 'solved' by: 1) Lowering socket TCP timeout (non-fix)
# 2) Checking for TCP pings/aknowlegements (nasty)
# 3) Sending IRC pings in paralell (ineffective)
# - Can't run headless with logging.
#------------------------------------------------------------------------------
# (c) 2014 Christian Chapman under the 1988 MIT license
import sys;
import string;
import base64;
import getpass;
import errno;
import socket;
import ssl;
import select;
# Options for IRC Server
host = 'irc.freenode.net';
port = 7000;
# If you choose to hardcode remember to comment out 'getCreds()' line.
#str_username = '';
#str_pass = '';
#str_authident = str_username;
# Options for MSCC connection
str_localAddr = '127.0.0.1';
n_localPort = 6660;
str_msccBuf = '';
str_ircBuf = '';
# Sanitizes raw Freenode message so that it won't crash MSCC. Currently
# performs 4 manipulations, all related to joining channels:
# 1) Add `:' to initial `JOIN' message.
# 2) (DISABLED) Remove channel MODE message.
# 3) Change channel user list delimiter from `@' to `='.
# 4) Prefix channel user list with a `:'.
#
# Inputs:
# str_in: UTF-8 string of raw messages from Freenode terminated by '\r\n.'
# str_usr: String containing username
# Returns: Sanitized messages in the same format.
def sanitizeMscc(str_in, str_usr):
# Add `:' to initial `JOIN' message.
str_san = str_in.replace('JOIN #','JOIN :#');
# Remove channel MODE message.
#if ' MODE #' in str_san:
# idx = str_san.find(' MODE #');
# # We expect all input to be terminated.
# en = str_san.find('\r\n', idx);
# st = str_san.rfind('\r\n', idx);
# if st < 0:
# str_san = str_san[en + 4:]; # It's at the very beginning
# else:
# str_san = str_san[0:st + 4] + str_san[en + 4:];
# Change channel user list delimiter from `@' to `='.
# Prefix channel user list with a `:'.
idx = str_san.find('353' + str_usr + ' @ #');
while idx > 0:
idx = str_san.find(' @ #', idx);
str_end = str_san[idx + 2:];
li = str_end.split(None, 1);
str_san = str_san[0:idx] + ' = #' + li[0] + ' :' + li[1];
idx = str_san.find('353' + str_usr + ' @ #');
# Removes garbage response about MSCC's `ISIRCX' query
idx = str_san.find('ISIRCX :No such channel');
while idx > 0:
# We expect all input to be terminated.
en = str_san.find('\r\n', idx);
st = str_san.rfind('\r\n', 0, idx);
if st < 0:
str_san = str_san[en + 2:]; # It's at the very beginning
else:
str_san = str_san[0:st + 2] + str_san[en + 2:];
idx = str_san.find('ISIRCX :No such channel');
return str_san;
# Get info
def getCreds():
str_usr = input('Username: ');
str_auth = str_usr;
str_pass = getpass.getpass();
return (str_usr, str_auth, str_pass);
(str_username, str_authident, str_pass) = getCreds();
# Outer loop makes sockets. If some connection is lost, it will restart to here.
while(True):
try:
# Create a server for MSCC to connect.
try:
skt_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
skt_server.bind((str_localAddr, n_localPort));
skt_server.listen(5);
except socket.error as msg:
print('Failed to create socket. Error code: ' + str(msg[0]) + \
' , Error message : ' + msg[1]);
sys.exit();
print('Created server socket. Waiting for MSCC to connect...');
# Get MSCC's connection to skt_server.
(skt_mscc, addr_mscc) = skt_server.accept();
print('Client Connected.');
# Connect to IRC. (SSL)
skt_irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
skt_irc = ssl.wrap_socket(skt_irc);
try:
print('Resolving host...');
remote_ip = socket.gethostbyname( host );
except socket.gaierror:
# Could not resolve.
print('Hostname could not be resolved. Exiting.');
sys.exit();
# Connect, wait until socket is connected.
print('Connecting to IRC...');
skt_irc.connect((remote_ip , port));
print('Connected to ' + host + ' on ip ' + remote_ip);
skt_irc.setblocking(False);
# Request SASL.
skt_irc.sendall(bytes('CAP REQ :sasl\r\n','utf-8'));
# Send client info
skt_irc.sendall(bytes('NICK ' + str_username + '\r\n','utf-8'));
skt_irc.sendall(bytes('USER ' + str_username + ' MSCC 1 :'
+ str_authident + '\r\n','utf-8'));
print('Sent client info.');
# Receive into backup buffer to forward to MSCC after SASL is done.
# If MSCC does not receive the server's initial messages, it will think
# that it isn't connected.
# 'ACK :sasl' *SHOULD* arrive after all these messages.
str_ircBuf = '';
while('ACK :sasl' not in str_ircBuf):
select.select([skt_irc],[],[]);
b = skt_irc.recv(4096);
if(len(b)==0):
raise ConnectionAbortedError('No more data in socket..');
str_ircBuf = str_ircBuf + b.decode('utf-8', errors='replace');
print(b.decode(sys.stdout.encoding, errors='replace'));
skt_irc.sendall(('AUTHENTICATE PLAIN \r\n').encode('utf-8', errors='replace'));
print('AUTHENTICATE PLAIN');
# Wait until 'AUTHENTICATE +' to send SASL auth string.
str_buf = '';
while('AUTHENTICATE +' not in str_buf):
select.select([skt_irc],[],[]);
print('recv');
print(b);
b = skt_irc.recv(4096);
if(len(b)==0):
raise ConnectionAbortedError('No more data in socket.');
str_buf = str_buf + b.decode('utf-8', errors='replace');
print(b.decode(sys.stdout.encoding, errors='replace'));
# Make and send SASL auth string.
str_sasl = str_username + '\u0000' + str_authident + '\u0000' + str_pass;
b_sasl = str_sasl.encode('utf-8', errors='replace');
str_saslText = 'AUTHENTICATE ' + \
base64.b64encode(b_sasl).decode('utf-8', errors='replace') + \
'\r\n';
b_saslText = str_saslText.encode('utf-8', errors='replace');
skt_irc.sendall(b_saslText);
print('AUTHENTICATE ****');
# Wait until you receive 'authentication successful'
str_buf = '';
while(' :SASL authentication successful' not in str_buf):
select.select([skt_irc],[],[]);
b = skt_irc.recv(4096);
if(len(b)==0):
raise ConnectionAbortedError('No more data in socket.');
str_buf = str_buf + b.decode('utf-8', errors='replace');
print(b.decode(sys.stdout.encoding, errors='replace'));
if('904 ' + str_username in str_buf or
'905 ' + str_username in str_buf ):
print('SASL authentication failed! Please reenter credentials.');
(str_username, str_authident, str_pass) = getCreds();
raise ConnectionAbortedError('SASL auth failed.');
# End SASL communication
skt_irc.sendall(('CAP END\r\n').encode('utf-8', errors='replace'));
print('SASL authenticated successfully!');
# Recall that we earlier initialized our buffer with Freenode's initial messages.
# (Freenode will (mostly) ignore MSCC's attempt to reauth)
# In case we stopped in the middle of a message, add a delimiter.
str_ircBuf = str_ircBuf + '\r\n';
# main loop
# Forward IRC socket received data to MSCC & vice-versa.
while(True):
(li_rready, _, _) = select.select([skt_mscc, skt_irc],[],[]);
try:
# Forward MSCC to Freenode
if skt_mscc in li_rready:
b_msccBuf = skt_mscc.recv(4096);
if(len(b_msccBuf)==0):
raise ConnectionAbortedError('No more data in socket.');
skt_irc.sendall(b_msccBuf);
print( ' > ' + b_msccBuf.decode(sys.stdout.encoding, errors='replace'));
b_msccBuf = b'';
# Forward IRC to MSCC
if skt_irc in li_rready:
b = skt_irc.recv(4096);
if(len(b)==0):
raise ConnectionAbortedError('No more data in socket.');
str_ircBuf = str_ircBuf + b.decode('utf-8', errors='replace');
# Sometimes the buffer will chop a message in the middle.
if '\r\n' in str_ircBuf:
li = str_ircBuf.rsplit('\r\n', 1);
str_ircBuf = li[0];
str_ircBuf = str_ircBuf + '\r\n';
str_ircBufCutOff = li[1];
# If there's no carriage return in the buffer, it's a really long message. We need to finish loading at least all of one message into str_ircBuf, so go back to beginning.
else:
continue;
# Sanitize the buffer for MSCC and send
str_ircBuf = sanitizeMscc(str_ircBuf, str_username);
skt_mscc.sendall(str_ircBuf.encode('utf-8', errors='replace'));
print(bytes(str_ircBuf,'utf-8').decode(sys.stdout.encoding, errors='replace'));
# Include the beginning of the cut off message to be sent next time.
str_ircBuf = str_ircBufCutOff;
str_ircBufCutOff = '';
except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
# Not enough TLS packets have come in for us to read any SSL data.
continue;
except (ConnectionAbortedError, ConnectionResetError):
raise;
except socket.error as e:
error = e.args[0];
if e != errno.EAGAIN and e != errno.EWOULDBLOCK:
raise e;
else:
# No data to read.
continue;
except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
# Not enough TLS packets have come in for us to read any SSL data.
continue;
except (ConnectionAbortedError, ConnectionResetError) as e:
skt_mscc.close();
skt_irc.close();
error = e.args[0];
print(error);
print('Connection aborted.');
continue;
except socket.error as e:
error = e.args[0];
if error != errno.EAGAIN and error != errno.EWOULDBLOCK:
# a "real" error occurred
print('Socket error: ');
print(error);
print(type(e));
raise ;
else:
# No data to read.
pass;