# # 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()