-
-
Save Darksecond/e7b6729bd94888686885c054b0351a8d to your computer and use it in GitHub Desktop.
Comic Chat fixer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Comic Chat fixer MITM proxy: fixes Comic Chat to (sort of) work with modern | |
# IRC servers. Tested with Microsoft Chat 2.5 on Windows XP, 8 and 10 | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
import getopt, re, socket, sys, threading, time | |
try: | |
import ssl | |
except ImportError: | |
ssl = None | |
def thread_c2s(client, client_addr, password, host, port, use_ssl): | |
f = client.makefile() | |
queued_lines = [] | |
if password: | |
client.sendall(':cchat.proxy 464 * :Password incorrect\r\n') | |
while True: | |
line = f.readline().rstrip('\r\n') | |
if line[:12] == 'OPER (null) ': | |
if password == line[12:]: | |
print '[-] {0}:{1} authenticated successfully'.format(*client_addr) | |
break | |
else: | |
print '[!] {0}:{1} failed to authenticate'.format(*client_addr) | |
client.sendall(':cchat.proxy 464 * :Password incorrect\r\n') | |
else: | |
queued_lines.append(line) | |
irc = socket.create_connection((host, port)) | |
if use_ssl: | |
if not ssl: raise Exception('no ssl module') | |
irc = ssl.wrap_socket(irc) | |
for line in queued_lines: | |
irc.sendall(line + '\r\n') | |
t = threading.Thread(target=thread_s2c, args=(client, client_addr, irc)) | |
t.daemon = True | |
t.start() | |
try: | |
while True: | |
line = f.readline() | |
irc.sendall(line) | |
if len(line) == 0 or line[:5] == 'QUIT ': break | |
except KeyboardInterrupt: | |
sys.exit(1) | |
except: | |
pass | |
try: | |
irc.close() | |
except: | |
pass | |
try: | |
client.close() | |
except: | |
pass | |
def thread_s2c(client, client_addr, irc): | |
f = irc.makefile() | |
srv_prefix = '@+' | |
try: | |
while True: | |
line = f.readline() | |
split = line.split(' ') | |
if len(split) > 2: | |
if split[0] == 'ERROR': | |
client.sendall(line) | |
break | |
elif split[1] == '005': | |
# Get PREFIX= to fix ranks in the NAMES response | |
match = re.search(''' PREFIX=\(([^\)]+)\)([^\s]+)''', line) | |
if match: | |
srv_prefix = match.group(2) | |
elif split[1] == 'JOIN' and split[2][0] != ':': | |
# Main purpose of the proxy. Fixes a crash bug with newer | |
# ircds, which send JOIN confirmations like this: | |
# | |
# :nick!user@host JOIN #channel | |
# | |
# instead of this: | |
# | |
# :nick!user@host JOIN :#channel | |
# | |
# CChat expects the channel name to have a : before the | |
# name. If it doesn't, it will crash, since it somehow | |
# attempts a stricmp(0). | |
split[2] = ':' + split[2] | |
elif split[1] == '353': | |
# Convert additional ranks to regular op | |
for i in range(5, len(split)): | |
rank = '' | |
nick = '' | |
for char in split[i]: | |
if char == '+' and rank != '@': | |
# voice | |
rank = '+' | |
elif char in srv_prefix: | |
# everything unknown to CChat becomes op | |
rank = '@' | |
elif char != ':': | |
# not a rank | |
nick += char | |
split[i] = (split[i][0] == ':' and ':' or '') + rank + nick | |
line = ' '.join(split) | |
# Comic Chat will stop receiving if it receives a line longer than | |
# 512 bytes, including the trailing CRLF. | |
client.sendall(line.rstrip('\r\n')[:510] + '\r\n') | |
except KeyboardInterrupt: | |
sys.exit(1) | |
try: | |
irc.close() | |
except: | |
pass | |
try: | |
client.close() | |
except: | |
pass | |
def main(): | |
bind_host = '' | |
bind_port = 6461 | |
password = None | |
options, remainder = getopt.getopt(sys.argv[1:], 'h:p:a:', ['bindhost=', 'bindport=', 'password=']) | |
for opt, arg in options: | |
if opt in ('-h', '--bindhost'): | |
bind_host = arg | |
elif opt in ('-p', '--bindport'): | |
bind_port = int(arg) | |
elif opt in ('-a', '--password'): | |
password = arg | |
if bind_port < 0 or bind_port > 65535 or len(remainder) < 1: | |
print 'Usage: proxy.py [-h bindhost] [-p bindport] [-a password] server [[+]port]' | |
sys.exit(1) | |
host = remainder[0] | |
if len(remainder) > 1: | |
if remainder[1][0] == '+': | |
use_ssl = True | |
port = int(remainder[1][1:]) | |
else: | |
use_ssl = False | |
port = int(remainder[1]) | |
else: | |
use_ssl = False | |
port = 6667 | |
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
server.bind((bind_host, bind_port)) | |
server.listen(5) | |
print '[-] Waiting for connections' | |
try: | |
while True: | |
client, client_addr = server.accept() | |
print '[-] Connection from {0}:{1}'.format(*client_addr) | |
t = threading.Thread(target=thread_c2s, args=(client, client_addr, password, host, port, use_ssl)) | |
t.daemon = True | |
t.start() | |
except KeyboardInterrupt: | |
server.close() | |
sys.exit(1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment