Guest User

Microsoft Comic Chat Helper for Freenode V0.02b

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