#!/usr/bin/env python import sys import os import socket import select import string import re import time import thread import msnlib import msncb """ MSN Client Daemon This is a MSN client that reads commands from a named pipe, using a little text-only protocol. It's main use is to serve as a 'glue' to implement clients in other languages. """ def null(s): "Null function, useful to void debug ones" pass # # This are the callback replacements, which only handle the output and then # call the original callbacks to do the lower level stuff # # basic classes m = msnlib.msnd() m.cb = msncb.cb() # status change def cb_iln(md, type, tid, params): t = params.split() status = msnlib.reverse_status[t[0]] email = t[1] equeue.append('STCH %s %s\n' % (email, status)) msncb.cb_iln(md, type, tid, params) m.cb.iln = cb_iln def cb_nln(md, type, tid, params): status = msnlib.reverse_status[tid] t = string.split(params) email = t[0] equeue.append('STCH %s %s\n' % (email, status)) msncb.cb_nln(md, type, tid, params) m.cb.nln = cb_nln def cb_fln(md, type, tid, params): email = tid u = m.users[email] discarded = 0 if u.sbd and u.sbd.msgqueue: discarded = len(u.sbd.msgqueue) equeue.append('STCH %s offline %d\n' % (email, discarded)) msncb.cb_fln(md, type, tid, params) m.cb.fln = cb_fln # server disconnect def cb_out(md, type, tid, params): equeue.append('ERR SERV_DISC Server sent disconnect\n') msncb.cb_out(md, type, tid, params) m.cb.out = cb_out # message def cb_msg(md, type, tid, params, sbd): t = string.split(tid) email = t[0] # messages from hotmail are only when we connect, and send things # regarding, aparently, hotmail issues. we ignore them (basically # because i couldn't care less; however if somebody has intrest in # these and provides some debug output i'll be happy to implement # parsing). if email == 'Hotmail': return # parse lines = string.split(params, '\n') headers = {} eoh = 1 for i in lines: # end of headers if i == '\r': break tv = string.split(i, ':') type = tv[0] value = string.join(tv[1:], ':') value = string.strip(value) headers[type] = value eoh += 1 if headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-msmsgscontrol': # the typing notices equeue.append('TYPING %s\n' % email) else: # messages equeue.append('MSG %d %d %s\n%s\n' % \ (len(lines), eoh, email, string.join(lines, '\n')) ) msncb.cb_msg(md, type, tid, params, sbd) m.cb.msg = cb_msg # join a conversation and send pending messages def cb_joi(md, type, tid, params, sbd): email = tid if len(sbd.msgqueue) > 0: equeue.append('MFLUSH %s\n' % email) msncb.cb_joi(md, type, tid, params, sbd) m.cb.joi = cb_joi # server errors def cb_err(md, errno, params): if not msncb.error_table.has_key(errno): desc = 'Unknown' else: desc = msncb.error_table[errno] equeue.append('ERR %s %s\n' % (errno, desc)) msncb.cb_err(md, errno, params) m.cb.err = cb_err # users add, delete and modify def cb_add(md, type, tid, params): t = params.split() type = t[0] if type == 'RL' or type == 'FL': email = t[2] if type == 'RL': equeue.append('UADD %s\n' % email) elif type == 'FL': equeue.append('ADDFL %s\n' % email) msncb.cb_add(md, type, tid, params) m.cb.add = cb_add def cb_rem(md, type, tid, params): t = params.split() type = t[0] if type == 'RL' or type == 'FL': email = t[2] if type == 'RL': equeue.append('UDEL %s\n' % email) elif type == 'FL': equeue.append('DELFL %s\n' % email) msncb.cb_rem(md, type, tid, params) m.cb.rem = cb_rem def login(email, password): # login to msn printl('Logging in... ', c.green, 1) try: m.login() printl('done\n', c.green, 1) except 'AuthError', info: errno = int(info[0]) if not msncb.error_table.has_key(errno): desc = 'Unknown' else: desc = msncb.error_table[errno] perror('Error: %s\n' % desc) quit(1) except KeyboardInterrupt: quit() except ('SocketError', socket.error), info: perror('Network error: ' + str(info) + '\n') quit(1) except: pexc('Exception logging in\n') quit(1) # # the pipe read # # read from the pipe, c being the pipe socket passed from the caller def pipe_read(c): global m global addr global equeue # we don't worry about lines too much in this implementation because # we use datagrams. however, when using stream sockets you should s, addr = c.recvfrom(4 * 1024) # input buffer, should be enough print '<--', s, try: s = s.split(' ', 1) if len(s) == 2: cmd, params = s else: cmd = s[0] params = '' cmd = cmd.strip() if params: params = params.strip() params = params.split(' ') except: psend(c, 'ERR EINVAL\n') return if cmd == 'LOGIN': if len(params) != 2: psend(c, 'ERR PARAMS\n') return try: email, pwd = params m.email = email m.pwd = pwd m.login() m.sync() except 'AuthError', info: errno = int(info[0]) if not msncb.error_table.has_key(errno): desc = 'Unknown' else: desc = msncb.error_table[errno] psend(c, 'ERR MSN %d %s\n' % (errno, desc)) return except ('SocketError', socket.error), info: psend(c, 'ERR SOCK %s\n' % str(info)) return psend(c, 'OK\n') return elif cmd == 'LOGOFF': m.disconnect() psend(c, 'OK\n') return # if we are not connected, the following commands are not available if not m.fd: psend(c, 'ERR ENOTCONN\n') return if cmd == 'STATUS': status = string.join(params, ' ') if not m.change_status(status): psend(c, 'ERR UNK STATUS\n') else: psend(c, 'OK\n') return if cmd == 'POLL': equeue.append('POLLEND\n') for evt in equeue: #psend(c, evt) print "-----> ", evt, '<-----\n', equeue = [] return if cmd == 'GETCL': psend(c, 'CL %d\n' % len(m.users.keys()) ) for email in m.users.keys(): u = m.users[email] status = msnlib.reverse_status[u.status] psend(c, '%s %s %s\n' % (status, email, u.nick)) return if cmd == 'GETRCL': psend(c, 'CL %d\n' % len(m.reverse.keys()) ) for email in m.reverse.keys(): u = m.reverse[email] status = msnlib.reverse_status[u.status] psend(c, '%s %s %s\n' % (status, email, u.nick)) return if cmd == 'INFO': if len(params) != 1: psend(c, 'ERR PARAMS\n') return if not m.users.has_key(email): psend(c, 'ERR UNK USER\n') u = m.users[email] psend(c, 'email = %s\n' % email) psend(c, 'nick = %s\n' % u.nick) psend(c, 'homep = %s\n' % u.homep) psend(c, 'workp = %s\n' % u.workp) psend(c, 'mobilep = %s\n' % u.mobilep) psend(c, '\n') return if cmd == 'ADD': if len(params) != 2: psend(c, 'ERR PARAMS\n') return nick, email = params m.useradd(email, nick) psend(c, 'OK\n') return if cmd == 'DEL': if len(params) != 1: psend(c, 'ERR PARAMS\n') return m.userdel(params) psend(c, 'OK\n') return if cmd == 'NICK': if len(params) != 1: psend(c, 'ERR PARAMS\n') return m.change_nick(params) psend(c, 'OK\n') return if cmd == 'PRIV': if len(params) != 2: psend(c, 'ERR PARAMS\n') return try: public = int(p[0]) auth = int(p[1]) if public not in (0, 1) or auth not in (0, 1): raise except: psend(c, 'ERR EINVAL\n') return m.privacy(public, auth) psend(c, 'OK\n') return if cmd == 'SENDMSG': params = string.join(params, ' ') params = string.split(params, '\n', 2) params, msg = params params = string.split(params, ' ') if len(params) < 2: psend(c, 'ERR PARAMS\n') return lines = params[0] email = params[1] msg = msg m.sendmsg(email, msg) psend(c, 'OK\n') return # if we got here is because the command is unknown psend(c, 'ERR UNK\n') return # # now the real thing # # void the debug msnlib.debug = null msncb.debug = null # POLL event queue # We implement it in a very, very efficient way: text =) # Yes, it's actually a list, but just because .append() is readable # and allow us to keep track of the number of pending events equeue = [] msnin = [] msnout = [] salir = 0 # open the socket for local communication # we use datagram sockets to avoid complex reads and writes for now, but the # protocol is line-oriented and perfectly capable of working over a stream # socket. def msnlogin(user,password): print 'Login: ' m.email = user m.pwd = password m.login() m.sync() # m.change_status('online') m.change_status('busy') print 'OK' def msnhandler(remote): pattern = re.compile ('MSG\s+\d+\s+\d+\s+(?P[^;]+);.*charset[^;]+;(?P.*)$') global equeue global msnin global msnout # loop, waiting for connections while 1: infd = outfd = [] # if we are connected, poll from msn if m.fd != None: t = m.pollable() infd = t[0] outfd = t[1] #infd.append(pipe) fds = select.select(infd, outfd, [], 0) for i in fds[0] + fds[1]: # see msnlib.msnd.pollable.__doc__ try: m.read(i) except ('SocketError', socket.error), err: if i != m: # user closed a connection # note that messages can be # lost here equeue.append('SCLOSE USER %s %d\n' % (i.emails[0], len(i.msgqueue)) ) m.close(i) else: # main socket closed # report equeue.append('SCLOSE MAIN\n') quit(1) for evt in equeue: evt = evt.replace('\n',';') evt = evt.replace('\r','') result = '' result = pattern.match(evt) if result: # print evt data = result.group('data') #data = data.replace('\n','') data = data.replace(';','') data = data.replace(' ','') if data: print '>>> ', len(data) msnin.append(data) equeue = [] time.sleep(0.01) def sockethandler_listen(port, socket): global msnout global msnin socket.bind(('', port)) socket.listen(1) mainsocks, read, write = [], [], [] mainsocks.append(socket) read.append(socket) write.append(socket) conectado = 0 while 1: reader, writer, error = select.select(read, write, []) for sockobj in reader: if sockobj in mainsocks: newsock, address = sockobj.accept() if not conectado: m.change_status('online') conectado = 1 thread.start_new(test_peer,(remote_user,thread)) print 'Connect:', address read.append(newsock) write.append(newsock) else: data = sockobj.recv(1024) if not data: sockobj.close() read.remove(sockobj) write.remove(sockobj) else: msnout.append(data.encode('base64')) for sockobj in writer: if sockobj not in mainsocks: if msnin: sockobj.send(msnin.pop(0).decode('base64')) time.sleep(0.01) def sockethandler(addr,port, socket): global msnout global msnin mainsocks, read, write = [], [], [] newsock = socket.connect((addr,port)) print 'Connected' thread.start_new(test_peer,(remote_user,thread)) read.append(socket) write.append(socket) while 1: reader, writer, error = select.select(read, write, []) for sockobj in reader: data = sockobj.recv(1024) if data: msnout.append(data.encode('base64')) else: sockobj.close() sys.exit(1) for sockobj in writer: if msnin: sockobj.send(msnin.pop(0).decode('base64')) time.sleep(0.01) def test_peer(remote_user, self): global salir while 1: time.sleep(30) for email in m.users.keys(): u = m.users[email] status = msnlib.reverse_status[u.status] if ((u.email == remote_user) and (status == 'offline')): print "Socket remoto desconectado" salir = 1 break def getopts(argv): opts = {} while argv: if argv[0][0] == '-': opts[argv[0]] = argv[1] argv = argv[2:] else: argv = argv[1:] return opts if __name__ == '__main__': args = getopts(sys.argv) usuario = '' password = '' remote_user = '' user_connected = 0 if args.has_key('-u'): usuario = args['-u'] if args.has_key('-p'): password = args['-p'] if args.has_key('-r'): remote_user = args['-r'] if not (usuario and password and remote_user): print 'Parametros incorrectos' print ' -u (msn-user)' print ' -p (msn-pass)' print ' -r (remote-msn)' print ' -L (puerto-listen) ! -R (ip-conn) -P (port-conn)' sys.exit() msnlogin(usuario,password) thread.start_new(msnhandler,(remote_user,)) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if args.has_key('-L'): thread.start_new(sockethandler_listen,(int(args['-L']),server)) elif args.has_key('-R') and args.has_key('-P'): m.change_status('online') print "Waiting for client..." while not user_connected: for email in m.users.keys(): u = m.users[email] status = msnlib.reverse_status[u.status] if (u.email == remote_user) and (status == 'online'): user_connected = 1 break time.sleep(1) thread.start_new(sockethandler,(args['-R'],int(args['-P']),server)) else: print 'Especifique tipo de conexion' sys.exit() while 1: if salir: sys.exit(0) if msnout: mensaje = msnout.pop(0) print '<<< ', len(mensaje) if m.sendmsg(remote_user, mensaje) == -2: print "Paquete demasiado grande" sys.exit(1) time.sleep(0.01)