diff -pruN msnlib-3.3/ahttplib.py aw/msnlib-3.3/ahttplib.py
--- msnlib-3.3/ahttplib.py	1969-12-31 21:00:00.000000000 -0300
+++ aw/msnlib-3.3/ahttplib.py	2004-06-18 00:23:37.000000000 -0300
@@ -0,0 +1,808 @@
+#                                                           #
+# $Id: ahttplib.py,v 1.36 2004/03/23 03:19:40 aweil Exp $   #
+#                                                           #
+# ######################################################### #
+#                                                           #
+# - It would be nice REPLACE putheader with putheaders(dic) #
+#                                                           #
+# ######################################################### #
+import socket
+import errno
+import async2
+import types            #our target completer checks types..
+import SimpleChannel    #used in http_sock to write/read to a string..
+
+SSL_ERRORSET = [socket.SSL_ERROR_EOF,\
+				socket.SSL_ERROR_INVALID_ERROR_CODE, socket.SSL_ERROR_SSL,\
+				socket.SSL_ERROR_SYSCALL, socket.SSL_ERROR_WANT_CONNECT,\
+				socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE,\
+				socket.SSL_ERROR_WANT_X509_LOOKUP, socket.SSL_ERROR_ZERO_RETURN]
+
+HTTP_PORT = 80
+newline = '\r\n'
+_header = '%s: %s'+newline
+
+# Errors
+_state_error = 'Invalid state for requested operation'
+_cant_connect = 'Can\'t connect'
+
+
+enable_log = False
+
+def log(s):
+	global enable_log
+	if enable_log:
+		print s
+
+
+def __make_http_socket_over ( dispatcher_specie ):
+	"""
+	This function makes http_socket over an asyncronous socket dispatcher 
+	given. Specially crafted to be called with: dispatcher / ssldispatcher
+	"""
+	class _int_http_socket(dispatcher_specie):
+		_init = 'init'
+		_connected = 'connected'
+		_closed = 'closed'
+		
+		def __init__(self, target, request=''):
+			"""target should be (host, port)"""
+			dispatcher_specie.__init__(self)
+			self._status = self.__class__._init
+			self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+			self.target=target
+			self.buffer = request
+			#we are the writer!
+			self._reset_recv('')
+			self.connect( target )
+			
+		def __str__ (self):
+			return '<%s httpsocket->%s>'%(self.status(),repr(self.target))
+			
+		def status (self):
+			return self._status
+		
+		def connected (self):
+			return self.status() == self.__class__._connected
+		
+		def closed (self):
+			return self.status() == self.__class__._closed
+			
+		def _reset_recv(self, data):
+			self.channel = SimpleChannel.SimpleChannel()
+			self.writeon = self.channel.get_writer()
+			self.append(data)
+			
+		def append_data(self, request):
+			#if self.closed:
+			#       raise Exception('http socket closed!')
+			self.buffer+=request
+	
+		def handle_connect(self):
+			self._status = self.__class__._connected
+	
+		def append(self, str):
+			""" Appends a string to received data """
+			self.writeon.write(str)
+	
+		def handle_read(self):
+			try:
+				data = self.recv(8192)
+				self.append(data)
+				#log('data:'+repr(data))
+			except socket.sslerror, e:
+				print 'SSLError ->',str(e)
+				if e[0] == socket.SSL_ERROR_EOF:
+					self.handle_close()
+				elif e[0] in SSL_ERRORSET:
+					self.handle_error()
+			except Exception, e:
+				print '\rException reading!!!:',e
+				#raise
+	
+		def writable(self):
+			return len(self.buffer) > 0
+	
+		def handle_write(self):
+			try:
+				sent = self.send(self.buffer)
+				self.buffer = self.buffer[sent:]
+			except socket.sslerror, e:
+				print 'SSLError ->',str(e)
+				if e[0] == socket.SSL_ERROR_EOF:
+					self.handle_close()
+				elif e[0] in SSL_ERRORSET:
+					self.handle_error()
+			except Exception, e:
+				print '\rException writting!!!:',e
+				
+	
+		def handle_close(self):
+			self._status = self.__class__._closed
+			#XXX TODO 
+			
+		def set_socket(self, socket):
+			ret = dispatcher_specie.set_socket(self, socket)
+			self.channel = SimpleChannel.SimpleChannel()
+			self.writeon = self.channel.get_writer()
+			return ret
+			
+	#__make_http_socket_over's body:
+	return _int_http_socket
+
+
+class ObjectMerger:
+	"""
+	Object 'Merger'.
+	
+	Returns an instance that takes methods from two given instances/objects. 
+	In order, as if one have the subclass methods of the other.
+	"""
+	def __init__(self, super, _self):
+		self.___super = super
+		self.___self = _self
+		
+	def __getattr__ ( self, name ):
+		if not name.startswith('___'):
+			super = self.___super
+			_self = self.___self
+			if hasattr(_self, name):
+				return getattr(_self, name)
+			elif hasattr(super, name):
+				return getattr(super, name)
+			
+		return getattr(ObjectMerger, name)
+
+class AttrXlater:
+	"""Attribute translator"""
+	def __init__(self, _self, methodxchange={}):
+		self.___mxlate = methodxchange
+		self.___self = _self
+	def __getattr__ ( self, name ):
+		_self = self.___self
+		if not name.startswith('___'):
+			if self.___mxlate.has_key(name):
+				name = self.___mxlate[name]
+			if hasattr(_self, name):
+				return getattr(_self, name)
+		return getattr(AttrXlater, name)
+		
+		
+class ssldispatcher(async2.dispatcher):
+	def handle_connect_event(self):
+		try:
+			sslsock = socket.ssl(self.socket) #no certs this time, sorry
+		except Exception, e:
+			print 'EXCEPTION!!! not catching!', e
+			print 'calling handle expt event()'
+			self.handle_expt_event()
+		else:
+			#we must use the ssl from here..
+			oldsocket = self.socket
+			newsocket = AttrXlater( \
+							ObjectMerger(oldsocket, sslsock), \
+							{'recv':'read','send':'write'})
+			self.set_socket(newsocket)
+			#everything went fine, so.. go on!
+			async2.dispatcher.handle_connect_event(self)
+	
+class multidispatcher(async2.dispatcher):
+	"""kind of weird protocol stack"""
+	def __init__(self, sock=None, map=None):
+		async2.dispatcher.__init__(self, sock=None, map=None)
+		self.socketstack = []
+	def push_socket(self, newsocket):
+		self.socketstack.append(self.set_socket(newsocket))
+	def pop_socket(self):
+		return self.set_socket(self.socketstack.pop())
+
+http_socket = __make_http_socket_over (multidispatcher)
+https_socket = __make_http_socket_over (ssldispatcher)
+
+
+class ConnectionException(Exception):
+	pass
+
+
+class ResponseException(Exception):
+	def __init__(self, str):
+		self.value = str
+	def __str__(self):
+		return repr(self.value)
+
+
+class HttpResponseReader:
+	"""
+	HttpResponseReader main objetive is to recognize different fields from a
+	http request's answer and provide it.
+	"""
+	# response status
+	_response = 'response'
+	_headers = 'headers'
+	_body = 'body'
+	_finished = 'finished'
+	
+	def __init__(self, stream, errors='strict'):
+		self._status = HttpResponseReader._response
+		self.stream = stream
+		self.errors = errors
+		self.buffer = ''
+		self.headers = {}
+		self.code = None
+		self.__clen = None
+		self.rcode = 0 #default response code
+		self.httpver = '1.0'
+		#internal vars
+		self.statusmsg = '<unset>'
+		self.remain = ''
+
+	def status(self):
+		return self._status
+
+	def finished(self):
+		return self.status()==HttpResponseReader._finished
+	
+	def __str__ (self):
+		return 'Response([%s,%d,%s],%s,%s,%s)' % \
+			(self.httpver, self.rcode, self.statusmsg[:10], \
+			str(self.headers)[:10], self.buffer[:15], self.remain[:10])
+
+	def read(self, size=1024*1024):
+		self.buffer+=self.stream.read(size)
+		cont=True
+		while cont:
+			cont = self.peek()
+		if self.finished():
+			return self
+		return ''
+
+	def peek(self):
+		"""
+		Tries to read one piece of data from our buffer
+
+		Returns
+				True if made some change, and
+				False if no modified anything
+		"""
+		if self._status==HttpResponseReader._finished:
+			#print 'finished'
+			return False
+
+		elif self._status==HttpResponseReader._response:
+			#print 'in response'
+			sp = self.buffer.split(newline,1)
+			if len(sp)<>2:
+				return False
+			statusline, self.buffer = sp
+			log('status line:'+statusline)
+			try:
+				self.httpver, statusline=statusline.split(' ',1)
+				try:
+					rcode, self.statusline=statusline.split(' ',1)
+					self.rcode = int(rcode)
+					if self.rcode>=400:
+						self._status=HttpResponseReader._finished
+						return False
+					if self.rcode>=500:
+						self._status=HttpResponseReader._finished
+						return False
+				except:
+					pass
+			except:
+				pass
+			self._status=HttpResponseReader._headers
+
+		elif self._status==HttpResponseReader._headers:
+			#print 'in headers'
+			sp = self.buffer.split(newline,1)
+			if len(sp)==2:
+				header, self.buffer = sp
+			else:
+				return False
+			
+			if len(header)==0:
+				if self.rcode==100:
+					self._status=HttpResponseReader._response
+				elif self.rcode<400:
+					self._status=HttpResponseReader._body
+				else:
+					raise ResponseException('Response code=%d(%s)'% \
+							(self.rcode,header))
+				return True
+			
+			sp = header.split(': ',1)
+			if len(sp)<>2:
+				#reset would be welcomed
+				raise ResponseException('Invalid header: "%s"'%header)
+			self.headers[sp[0]]=sp[1]
+
+		elif self._status==HttpResponseReader._body:
+			#print 'in body'
+			if self.__clen==None:
+				try:
+					self.__clen = int(self.headers['Content-Length'])
+				except:
+					#if there's no Content-Length header, we finish.
+					#returning this object, providing read() function
+					#to return received contents!
+					log('no content-length')
+					self._status = HttpResponseReader._finished
+					return True
+			clen=self.__clen
+			log('len %d - clen %d'%(len(self.buffer),clen))
+			if len(self.buffer)>=clen:
+				self.remain = self.buffer[clen:]
+				self.buffer = self.buffer[:clen]
+				self._status = HttpResponseReader._finished
+			else:
+				return False
+		return True
+
+	def show(self):
+		print '\r'+(70*'-')
+		print 'received: ',repr(self.buffer+'.. ')
+		print 'statusmsg',self.statusline, ' rcode:',self.rcode
+		print 'buffer',self.buffer
+		print 'headers',self.headers
+		print 'remain',self.remain
+
+
+
+def complete_peer(dst, port=80, proto='http'):
+	if type(dst)==types.StringType:
+		dst = (proto, dst, port)
+	return dst
+
+
+_default_http_ver = '1.0'
+_http_ver_11 = '1.1'
+_HTTP_HOST_HDR = 'Host'
+	
+class AHttpConnection:
+	# status
+	_idle = 'idle'
+	_connected = 'connected'
+	_finished = 'finished'
+	socket_class = http_socket
+	default_port = HTTP_PORT
+	default_proto = 'http'
+
+	def __init__ (  self, dst, proxy=None, http_version=_default_http_ver):
+		self.sock=None
+		self.headers={}
+		self.response=None
+		self.respvalue=None
+		self._status=AHttpConnection._idle
+		self.httpver = http_version
+		superc = self.__class__
+		self.dst = complete_peer(dst, port=superc.default_port, \
+					proto=superc.default_proto)
+		##
+		##by request variables
+		##
+		#self._headers_sent = {} # contains headers sent in last request
+		#now, send it always!
+		self._headers_sent = False
+		self.version = http_version
+			
+	def request( self, request, method='GET', headers={}, data='', version=None):
+		"""Makes a complete request calling subfunctions"""
+		self.putrequest(request, method, headers=headers, version=version)
+		self.endheaders()
+		self.send(data+newline)
+
+	# medium level functions
+	def connect ( self ):
+		if self.status()==AHttpConnection._connected:
+			return
+		self.sock = self.__class__.socket_class( (self.dst[1],self.dst[2]) )
+		self._status = AHttpConnection._connected
+
+	def putrequest( self, request, method='GET', headers={}, version=None):
+		if version==None:
+			version=self.httpver
+		self.version = version
+		self.connect()
+		_request = method + ' ' + request + ' HTTP/'+ version
+		self._send(_request+newline)
+		
+		for key in headers.keys():
+			self.putheader(key, headers[key])
+
+	def putheader( self, key, value ):
+		self._send(_header%(key,value))
+		#self._headers_sent[key] = value
+		self._headers_sent = True
+		
+	def endheaders( self ):
+		global _HTTP_HOST_HDR
+		if self.version==_http_ver_11:
+			#if not self._headers_sent.has_key(_HTTP_HOST_HDR):
+			#now always send Host header! yeaph!
+			self.putheader(_HTTP_HOST_HDR, self.dst[1])
+		if self._headers_sent:
+			self._send(newline)
+			self._headers_sent = {}
+
+	def read( self ):
+		return self.get_response()
+
+	def get_response( self ):
+		"""
+		-If we don't have a connect, create it and read().
+		-If we have a connection:
+			-read
+			-keep it until it finish.
+		-If finished:
+			return it, else
+			return none
+		"""
+		log('getresp')
+		if self.status() <>  AHttpConnection._connected:
+			#print 'not connected'
+			log('not connected')
+			raise ConnectionException('not connected')
+
+		if self.response==None:
+			log('build response reader')
+			self.response=HttpResponseReader(self.sock.channel.get_reader())
+		if not self.response.finished():
+			log('not finished.. tring to read')
+			self.response.read()
+		if not self.response.finished():
+			return None
+		log('finished.. go on!')
+		#we finished return the response received!
+		response=self.response
+		self.response=None
+		self.sock._reset_recv(response.remain)
+		return response
+
+	## new functions
+	def status (self):
+		return self._status
+
+	def finished( self ):
+		return self.status()==AHttpConnection._finished
+
+	def close ( self ):
+		self.sock.close()
+		self._status = AHttpConnection._finished
+
+	def send( self, data ):
+		""" our send function is non blocking """
+		self._send(data)
+	##
+	## internal functions
+	##
+		
+	def _send( self, data ):
+		self.sock.append_data( data )
+	##
+	## weird functions
+	##
+	def signal_ssl_socket(self):
+		"""
+		This function switchs the socket used by asyncronous socket dispatcher
+		"""
+		prevsock = self.sock.socket
+		try:
+			#is this blocking? i think..
+			sslsock = socket.ssl(prevsock) #no certs this time, sorry
+		except Exception, e:
+			#we shoould raise something heavy here
+			#'cause it used to skip connect() exceptions
+			print 'EXCEPTION!!! not catching!', e
+			print 're raising error!'
+			raise
+		else:
+			#we must use the ssl from here..
+			newsocket = AttrXlater( \
+							ObjectMerger(prevsock, sslsock), \
+							{'recv':'read','send':'write'})
+			self.sock.push_socket(newsocket)
+			return True
+			#everything went fine, so.. go on!
+		
+	
+
+class AHttpsConnection(AHttpConnection):
+	socket_class = https_socket
+	default_port = 443
+	default_proto = 'https'
+
+
+class AProxyHttpConnection(AHttpConnection):
+	_sslidle = 'idle'
+	_sslhandshake = 'sslhands'
+	_sslconnected = 'connected'
+	
+	def __init__ (  self, dst, proxy, http_version=_default_http_ver, \
+					proxy_http_ver=None):
+		#setup target to know the kind of end-connection.
+		self.target= complete_peer(dst) 
+		self.proxy = complete_peer(proxy)
+		AHttpConnection.__init__( self, dst, http_version)
+		self.cached_request = None
+		self.sslstat = AProxyHttpConnection._sslidle
+		self._proxied_headers = None #keeps sent headers..
+		self.proxy_http_version = proxy_http_ver
+		
+	def connect ( self ):
+		if self.status()==AHttpConnection._connected:
+			return
+		self.sock = self.__class__.socket_class( (self.proxy[1],self.proxy[2]) )
+		self._status = AHttpConnection._connected
+		
+	def putrequest( self, request, method='GET', headers={}, version=None):
+		if self.target[0]=='http':
+			#simplest proxy connection, append proxy
+			_request= 'http://'+str(self.target[1])
+			if self.proxy[2]<>80:
+				_request+=':'+str(self.target[2])
+			if request[0]<>'/':
+				_request+='/'
+			_request += request
+			
+		elif self.target[0]=='https':
+				#self.proxy_http_version
+			#we must switch method
+			if self.sslstat <> AProxyHttpConnection._sslconnected:
+				self.cached_request = (request, method, version, headers)
+				method = 'CONNECT'
+				_request = '%s:%d'%(self.target[1], self.target[2])
+				headers = {}
+			else: 
+				#here the requests are directly to the destination host..
+				#rewrite no required!
+				_request = request
+		#finally all keeps calling the superclass..
+		AHttpConnection.putrequest(self, _request, method, headers, version)
+			
+	def get_response (self):
+		if self.target[0]<>'https':
+			return AHttpConnection.get_response(self)
+		#we have an ssl connection..
+		what = self.sock.channel.st1.getvalue()
+		#log('RECV<'+repr(what)+'>')
+		if self.sslstat == AProxyHttpConnection._sslidle:
+			r = AHttpConnection.get_response(self)
+			if r<>None:
+				self.sslstat = AProxyHttpConnection._sslhandshake
+				self.response = r #we setup the response again..
+				return None #keep connecting..
+				
+		elif self.sslstat == AProxyHttpConnection._sslhandshake:
+			if self.signal_ssl_socket():
+				self.sslstat = AProxyHttpConnection._sslconnected
+			return None
+					
+		elif self.sslstat == AProxyHttpConnection._sslconnected:
+			#inject inside request
+			if self.cached_request<>None:
+				new = self.cached_request
+				self.cached_request = None
+				##request, method='GET', headers={}, data='', version=None
+				##Internally we know that our sslstat is connected..
+				self.request(request=new[0], method=new[1], headers=new[3], \
+						version=new[2])
+			#return ''
+			self.response = None
+			return AHttpConnection.get_response(self)
+
+
+
+
+def get_system_proxy_config(proto='http'):
+	pdic = {'http':('http','localhost', 3128)}
+	#return ('192.168.254.254', 80)
+	return pdic[proto]
+
+def mini_loop(conn): #some connection to loop
+	r = None
+	while r==None:
+		async2.looponce(0.5)
+		try:
+			r = conn.get_response()
+		except ResponseException, e:
+			print e
+			log(str(e))
+		except Exception, e:
+			print e
+			pass
+	return r
+
+
+
+if __name__=='__main__':
+	import unittest
+	import sys          #for argv
+	import StringIO     #for Response tests
+
+	class testHttpResponseReader(unittest.TestCase):
+		"""This class tests handling of responses given by http servers"""
+		def test_resp_reader_1(self):
+			"""Test HttpResponseReader()"""
+			test=''
+			test+='HTTP/1.1 200 OK\r\n'
+			test+='Server: Microsoft-IIS/5.0\r\n'
+			test+='Date: Wed, 11 Feb 2004 00:58:39 GMT\r\n'
+			test+='X-MSN-Messenger: SessionID=367754021.32470; GW-IP=' \
+					'207.46.110.25\r\n'
+			test+='Content-Length: 18\r\n'
+			test+='Content-type: application/x-msn-messenger\r\n'
+			test+='\r\n'
+			test+='VER 1 MSNP8 CVR0\r\n'
+			ios = StringIO.StringIO(test)
+			rr = HttpResponseReader(ios)
+			r = rr.read()
+			self.failIf(r=='')
+			#print r
+		def test_resp_reader_2(self):
+			"""Test2 HttpResponseReader()"""
+			#
+			# This empty response caused troubles
+			#
+			test ='HTTP/1.1 200 OK\r\n'
+			test+='Server: Microsoft-IIS/5.0\r\n'
+			test+='Date: Tue, 17 Feb 2004 04:59:04 GMT\r\n'
+			test+='X-MSN-Messenger: SessionID=646347394.16934; GW-IP=' \
+					'207.46.110.47\r\n'
+			test+='Content-Length: 0\r\n'
+			test+='Content-type: application/x-msn-messenger\r\n'
+			test+='\r\n'
+			#print 'len is:',len(test)
+			assert len(test)==210
+			ios = StringIO.StringIO(test)
+			rr = HttpResponseReader(ios)
+			r = rr.read()
+			self.failIf(r=='')
+			#print r
+		def test_resp_reader_3(self):
+			"""Test3 HttpResponseReader()"""
+			test ='HTTP/1.1 100 Continue\r\n'
+			test+='Server: Microsoft-IIS/5.0\r\n'
+			test+='Date: Tue, 17 Feb 2004 04:59:04 GMT\r\n'
+			test+='\r\n'
+			test+='HTTP/1.1 200 OK\r\n'
+			test+='Server: Microsoft-IIS/5.0\r\n'
+			test+='Date: Tue, 17 Feb 2004 04:59:04 GMT\r\n'
+			test+='X-MSN-Messenger: SessionID=646347394.16934; GW-IP=' \
+					'207.46.110.47\r\n'
+			test+='Content-Length: 0\r\n'
+			test+='Content-type: application/x-msn-messenger\r\n'
+			test+='\r\n'
+			#print 'len is:',len(test)
+			ios = StringIO.StringIO(test)
+			rr = HttpResponseReader(ios)
+			r = rr.read()
+			self.failIf(r=='')
+			#print r
+		def test_new(self):
+			"""Test4 HttpResponseReader() - A wrong response.."""
+			test ='HTTP/1.1 200 OK\r\n'
+			test+='Server: Microsoft-IIS/4.0\r\n'
+			test+='Content-Location: https://32.104.16.39/Default.htm\r\n'
+			test+='Date: Sun, 07 Mar 2004 01:22:59 GMT\r\n'
+			test+='Content-Type: text/html\r\n'
+			test+='Accept-Ranges: bytes\r\n'
+			test+='Last-Modified: Mon, 08 Jul 2002 13:40:42 GMT\r\n'
+			test+='ETag: "06992d8526c21:1b2b"\r\n'
+			test+='Content-Length: 287\r\n'
+			test+='\r\n'
+			test+='<!-- Copyright (c) 1998 BroadVision, Inc.  All rights reserved. -->\r\n'
+			test+='<html>\r\n'
+			test+='<head>\r\n'
+			test+='<meta http-equiv="refresh"\r\n'
+			test+='content="0;URL=/cgi-bin/preprd.dll/bkb/init.do?UseBVCookieToBrowse=No">\r\n'
+			test+='<'
+			ios = StringIO.StringIO(test)
+			rr = HttpResponseReader(ios)
+			r = rr.read()
+			self.failIf(r<>'')
+		
+
+	class testHttpConnection(unittest.TestCase):
+		"""This class tests simplest http servers connection"""
+		def test_simplest_connection(self):
+			"""1 Test AHttpConnection()"""
+			print
+			dsthost = 'localhost'
+			if len(sys.argv)>1:
+				dsthost = sys.argv[1]
+			h = AHttpConnection(dsthost, http_version='1.0')
+			h.connect()
+			h.request('/')
+			r = mini_loop(h)
+			assert r<>None
+			self.failIf(r==None)
+			print 'received: ',repr(r.buffer[:20]+'..')
+			h.close()
+			
+		def test_simple_connection(self):
+			"""1 Test AHttpConnection()/1.1"""
+			print
+			dsthost = 'localhost'
+			if len(sys.argv)>1:
+				dsthost = sys.argv[1]
+			h = AHttpConnection(dsthost, http_version='1.1')
+			h.connect()
+			h.request('/')
+			r = mini_loop(h)
+			assert r<>None
+			self.failIf(r==None)
+			print 'received: ',repr(r.buffer[:20]+'..')
+			h.close()
+		
+		
+	class testProxyHttpConnection(unittest.TestCase):
+		"""Test some different proxy usage"""
+		def test_proxy_connection(self):
+			""" 2 Test AHttpConnection() - via proxy"""
+			print
+			h = AProxyHttpConnection('www.google.com', \
+					proxy=get_system_proxy_config(), http_version='1.0')
+			h.connect()
+			h.request('/')
+			r = mini_loop(h)
+			self.failIf(r==None)
+			print 'received: ',repr(r.buffer[:20]+'.. ')
+			h.sock.close()
+		
+		def test_proxy_ssl_connection(self):
+			"""3 Test AHttpConnection() -> proxied ssl"""
+			h = AProxyHttpConnection( ('https','www.bostonaccess.com.ar', 443),\
+					proxy=('http','localhost',8888), http_version='1.0')
+			h.connect()
+			h.request('/',method='GET')
+			r = mini_loop(h)
+			self.failIf(r==None)
+			print 
+			print 'received: ',repr(r.buffer[:20]+'.. ')
+			self.failIf(not \
+				r.buffer.startswith('<!-- Copyright (c) 1998 BroadVision') )
+			h.sock.close()
+			
+		def test_proxy_ssl11_connection(self):
+			"""3 Test AHttpConnection() -> proxied ssl(http 1.1)"""
+			h = AProxyHttpConnection( ('https','www.bostonaccess.com.ar', 443),\
+					proxy=('http','localhost',8888), http_version='1.1')
+			h.connect()
+			h.request('/',method='GET')
+			r = mini_loop(h)
+			self.failIf(r==None)
+			print 
+			print 'received: ',repr(r.buffer[:20]+'.. ')
+			self.failIf(not \
+				r.buffer.startswith('<!-- Copyright (c) 1998 BroadVision') )
+			h.sock.close()
+			
+	class testHttpsConnection(unittest.TestCase):
+		def test_ssl_connection(self):
+			"""4 Test AHttpsConnection() - ssl"""
+			h = AHttpsConnection('www.bostonaccess.com.ar', http_version='1.1')
+			h.connect()
+			h.request('/')
+			r = mini_loop(h)
+			self.failIf(r==None)
+			print 'received: ',repr(r.buffer[:20]+'.. ')
+			h.sock.close()
+			self.failIf(not r.buffer.startswith('<!-- Copyright (c) 1998 BroadVision') )
+
+			
+##        def test_ssl_proxy_connection(self):
+##            """Test AHttpsConnection() - ssl+proxy"""
+##            h = AHttpsConnection('www.bostonaccess.com.ar', \
+##                    proxy=get_system_proxy_config(), http_version='1.0')
+##            h.connect()
+##            h.request('/')
+##            r = mini_loop(h)
+##            self.failIf(r==None)
+##            print 'received: ',repr(r.buffer)
+			
+	suite = unittest.TestSuite()
+	suite.addTest(unittest.makeSuite(testHttpResponseReader))
+	suite.addTest(unittest.makeSuite(testHttpConnection))
+	suite.addTest(unittest.makeSuite(testHttpsConnection))
+	suite.addTest(unittest.makeSuite(testProxyHttpConnection))
+	unittest.TextTestRunner(verbosity=2).run(suite)
diff -pruN msnlib-3.3/async2.py aw/msnlib-3.3/async2.py
--- msnlib-3.3/async2.py	1969-12-31 21:00:00.000000000 -0300
+++ aw/msnlib-3.3/async2.py	2004-06-18 00:23:37.000000000 -0300
@@ -0,0 +1,550 @@
+#                                                                              #
+# $Id: async2.py,v 1.12 2004/03/23 03:19:40 aweil Exp $                        #
+#                                                                              #
+# asyncore's poll()/loop() replacement                                         #
+# changes:                                                                     #
+#   - ssldispatcher's recv()/send() are a bit deficient and not fully tested.  #
+#       but seems to works under simplest conditions                           #
+#   - Uses ObjectMerger and AttrXlater for ssldispatcher                       #
+#                                                                              #
+
+
+# -*- Mode: Python -*-
+#   Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
+#   Author: Sam Rushing <rushing@nightmare.com>
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+#
+#                         All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""Basic infrastructure for asynchronous socket service clients and servers.
+
+There are only two ways to have a program on a single processor do "more
+than one thing at a time".  Multi-threaded programming is the simplest and
+most popular way to do it, but there is another very different technique,
+that lets you have nearly all the advantages of multi-threading, without
+actually using multiple threads. it's really only practical if your program
+is largely I/O bound. If your program is CPU bound, then pre-emptive
+scheduled threads are probably what you really need. Network servers are
+rarely CPU-bound, however.
+
+If your operating system supports the select() system call in its I/O
+library (and nearly all do), then you can use it to juggle multiple
+communication channels at once; doing other work while your I/O is taking
+place in the "background."  Although this strategy can seem strange and
+complex, especially at first, it is in many ways easier to understand and
+control than multi-threaded programming. The module documented here solves
+many of the difficult problems for you, making the task of building
+sophisticated high-performance network servers and clients a snap.
+"""
+
+import exceptions
+import select
+import socket
+import sys
+import time
+
+import os
+from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
+	 ENOTCONN, ESHUTDOWN, EINTR, EISCONN
+
+try:
+	socket_map
+except NameError:
+	socket_map = {}
+
+class ExitNow(exceptions.Exception):
+	pass
+
+def read(obj):
+	try:
+		obj.handle_read_event()
+	except ExitNow:
+		raise
+	except:
+		obj.handle_error()
+
+def write(obj):
+	try:
+		obj.handle_write_event()
+	except ExitNow:
+		raise
+	except:
+		obj.handle_error()
+
+def readwrite(obj, flags):
+	try:
+		if flags & select.POLLIN:
+			obj.handle_read_event()
+		if flags & select.POLLOUT:
+			obj.handle_write_event()
+	except ExitNow:
+		raise
+	except:
+		obj.handle_error()
+
+
+def poll(timeout=0.0, map=None):
+	if map is None:
+		map = socket_map
+	if map:
+		r = []; w = []
+		for fd, obj in map.items():
+			if obj.readable():
+				r.append(fd)
+			if obj.writable():
+				w.append(fd)
+			if [] == r == w:
+				time.sleep(timeout)
+			else:
+				try:
+					r, w, _ = select.select(r, w, [], timeout)
+				except select.error, err:
+					if err[0] != errno.EINTR:
+						raise
+					else:
+						return
+					
+		for fd in r:
+			obj = map.get(fd)
+			if obj is None:
+				continue
+			read(obj)
+
+		for fd in w:
+			obj = map.get(fd)
+			if obj is None:
+				continue
+			write(obj)
+
+
+def poll2(timeout=0.0, map=None):
+	# Use the poll() support added to the select module in Python 2.0
+	if map is None:
+		map = socket_map
+	if timeout is not None:
+		# timeout is in milliseconds
+		timeout = int(timeout*1000)
+	pollster = select.poll()
+	if map:
+		for fd, obj in map.items():
+			flags = 0
+			if obj.readable():
+				flags = select.POLLIN
+			if obj.writable():
+				flags = flags | select.POLLOUT
+			if flags:
+				pollster.register(fd, flags)
+		try:
+			r = pollster.poll(timeout)
+		except select.error, err:
+			if err[0] != EINTR:
+				raise
+			r = []
+		for fd, flags in r:
+			obj = map.get(fd)
+			if obj is None:
+				continue
+			readwrite(obj, flags)
+
+poll3 = poll2                           # Alias for backward compatibility
+
+def looponce(timeout=0.0, use_poll=0, map=None):
+	if map is None:
+		map = socket_map
+	if use_poll and hasattr(select, 'poll'):
+		poll_fun = poll2
+	else:
+		poll_fun = poll
+	poll_fun(timeout, map)
+
+def loop(timeout=30.0, use_poll=0, map=None):
+	while map:
+		looponce(timeout, use_poll, map)
+
+class dispatcher:
+
+	debug = 0
+	connected = 0
+	accepting = 0
+	closing = 0
+	addr = None
+
+	def __init__(self, sock=None, map=None):
+		self._fileno = None
+		if map is None:
+			self._map = socket_map
+		else:
+			self._map = map
+
+		if sock:
+			self.set_socket(sock, map)
+			# I think it should inherit this anyway
+			self.socket.setblocking(0)
+			self.connected = 1
+			# XXX Does the constructor require that the socket passed
+			# be connected?
+			try:
+				self.addr = sock.getpeername()
+			except socket.error:
+				# The addr isn't crucial
+				pass
+		else:
+			self.socket = None
+
+	def __repr__(self):
+		status = [self.__class__.__module__+"."+self.__class__.__name__]
+		if self.accepting and self.addr:
+			status.append('listening')
+		elif self.connected:
+			status.append('connected')
+		if self.addr is not None:
+			try:
+				status.append('%s:%d' % self.addr)
+			except TypeError:
+				status.append(repr(self.addr))
+		return '<%s at %#x>' % (' '.join(status), id(self))
+
+	def add_channel(self, map=None):
+		#self.log_info('adding channel %s' % self)
+		if map is None:
+			map = self._map
+		map[self._fileno] = self
+
+	def del_channel(self, map=None):
+		fd = self._fileno
+		if map is None:
+			map = self._map
+		if map.has_key(fd):
+			#self.log_info('closing channel %d:%s' % (fd, self))
+			del map[fd]
+		self._fileno = None
+
+	def create_socket(self, family, type):
+		self.family_and_type = family, type
+		self.set_socket( socket.socket(family, type) )
+		self.socket.setblocking(0)
+
+	def set_socket(self, sock, map=None):
+		if self._fileno<>None:
+			self.del_channel(map)
+		oldsock = self.socket
+		self.socket = sock
+##        self.__dict__['socket'] = sock
+		self._fileno = sock.fileno()
+		self.add_channel(map)
+		return oldsock
+
+	def set_reuse_addr(self):
+		# try to re-use a server port if possible
+		try:
+			self.socket.setsockopt(
+				socket.SOL_SOCKET, socket.SO_REUSEADDR,
+				self.socket.getsockopt(socket.SOL_SOCKET,
+									   socket.SO_REUSEADDR) | 1
+				)
+		except socket.error:
+			pass
+
+	# ==================================================
+	# predicates for select()
+	# these are used as filters for the lists of sockets
+	# to pass to select().
+	# ==================================================
+
+	def readable(self):
+		return True
+
+	if os.name == 'mac':
+		# The macintosh will select a listening socket for
+		# write if you let it.  What might this mean?
+		def writable(self):
+			return not self.accepting
+	else:
+		def writable(self):
+			return True
+
+	# ==================================================
+	# socket object methods.
+	# ==================================================
+
+	def listen(self, num):
+		self.accepting = 1
+		if os.name == 'nt' and num > 5:
+			num = 1
+		return self.socket.listen(num)
+
+	def bind(self, addr):
+		self.addr = addr
+		return self.socket.bind(addr)
+
+	def connect(self, address):
+		self.connected = 0
+		err = self.socket.connect_ex(address)
+		# XXX Should interpret Winsock return values
+		if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
+			return
+		if err in (0, EISCONN):
+			self.addr = address
+			#self.connected = 1
+			self.handle_connect_event()
+		else:
+			raise socket.error, err
+
+	def accept(self):
+		# XXX can return either an address pair or None
+		try:
+			conn, addr = self.socket.accept()
+			return conn, addr
+		except socket.error, why:
+			if why[0] == EWOULDBLOCK:
+				pass
+			else:
+				raise socket.error, why
+
+	def send(self, data):
+		try:
+			result = self.socket.send(data)
+			return result
+		except socket.error, why:
+			if why[0] == EWOULDBLOCK:
+				return 0
+			else:
+				raise socket.error, why
+			return 0
+
+	def recv(self, buffer_size):
+		try:
+			data = self.socket.recv(buffer_size)
+			if not data:
+				# a closed connection is indicated by signaling
+				# a read condition, and having recv() return 0.
+				self.handle_close()
+				return ''
+			else:
+				return data
+		except socket.error, why:
+			# winsock sometimes throws ENOTCONN
+			if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
+				self.handle_close()
+				return ''
+			else:
+				raise socket.error, why
+
+	def close(self):
+		self.del_channel()
+		self.socket.close()
+		self.connected = 0 #from outside I don't want to test de inside socket
+
+	# cheap inheritance, used to pass all other attribute
+	# references to the underlying socket object.
+	def __getattr__(self, attr):
+		return getattr(self.socket, attr) #this could make an infinite loop..
+
+	# log and log_info may be overridden to provide more sophisticated
+	# logging and warning methods. In general, log is for 'hit' logging
+	# and 'log_info' is for informational, warning and error logging.
+
+	def log(self, message):
+		sys.stderr.write('log: %s\n' % str(message))
+
+	def log_info(self, message, type='info'):
+		if __debug__ or type != 'info':
+			print '%s: %s' % (type, message)
+
+	def handle_read_event(self):
+		if self.accepting:
+			# for an accepting socket, getting a read implies
+			# that we are connected
+			if not self.connected:
+				self.connected = 1
+			self.handle_accept() #self.connection should be setup inside i think
+		elif not self.connected:
+			self.handle_connect_event()
+			self.handle_read()
+		else:
+			self.handle_read()
+
+	def handle_write_event(self):
+		# getting a write implies that we are connected
+		if not self.connected:
+			self.handle_connect_event()
+		self.handle_write()
+		
+	def handle_connect_event(self):
+		"""This is for transparent socket wrap"""
+		self.connected = 1
+		self.handle_connect()
+
+	def handle_expt_event(self):
+		self.handle_expt()
+
+	def handle_error(self):
+		nil, t, v, tbinfo = compact_traceback()
+
+		# sometimes a user repr method will crash.
+		try:
+			self_repr = repr(self)
+		except:
+			self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
+
+		self.log_info(
+			'uncaptured python exception, closing channel %s (%s:%s %s)' % (
+				self_repr,
+				t,
+				v,
+				tbinfo
+				),
+			'error'
+			)
+		self.close()
+
+	def handle_expt(self):
+		self.log_info('unhandled exception', 'warning')
+
+	def handle_read(self):
+		self.log_info('unhandled read event', 'warning')
+
+	def handle_write(self):
+		self.log_info('unhandled write event', 'warning')
+
+	def handle_connect(self):
+		self.log_info('unhandled connect event', 'warning')
+
+	def handle_accept(self):
+		self.log_info('unhandled accept event', 'warning')
+
+	def handle_close(self):
+		self.log_info('unhandled close event', 'warning')
+		self.close()
+
+# ---------------------------------------------------------------------------
+# adds simple buffered output capability, useful for simple clients.
+# [for more sophisticated usage use asynchat.async_chat]
+# ---------------------------------------------------------------------------
+
+class dispatcher_with_send(dispatcher):
+
+	def __init__(self, sock=None):
+		dispatcher.__init__(self, sock)
+		self.out_buffer = ''
+
+	def initiate_send(self):
+		num_sent = 0
+		num_sent = dispatcher.send(self, self.out_buffer[:512])
+		self.out_buffer = self.out_buffer[num_sent:]
+
+	def handle_write(self):
+		self.initiate_send()
+
+	def writable(self):
+		return (not self.connected) or len(self.out_buffer)
+
+	def send(self, data):
+		if self.debug:
+			self.log_info('sending %s' % repr(data))
+		self.out_buffer = self.out_buffer + data
+		self.initiate_send()
+
+# ---------------------------------------------------------------------------
+# used for debugging.
+# ---------------------------------------------------------------------------
+
+def compact_traceback():
+	t, v, tb = sys.exc_info()
+	tbinfo = []
+	assert tb # Must have a traceback
+	while tb:
+		tbinfo.append((
+			tb.tb_frame.f_code.co_filename,
+			tb.tb_frame.f_code.co_name,
+			str(tb.tb_lineno)
+			))
+		tb = tb.tb_next
+
+	# just to be safe
+	del tb
+
+	file, function, line = tbinfo[-1]
+	info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
+	return (file, function, line), t, v, info
+
+def close_all(map=None):
+	if map is None:
+		map = socket_map
+	for x in map.values():
+		x.socket.close()
+	map.clear()
+
+# Asynchronous File I/O:
+#
+# After a little research (reading man pages on various unixen, and
+# digging through the linux kernel), I've determined that select()
+# isn't meant for doing asynchronous file i/o.
+# Heartening, though - reading linux/mm/filemap.c shows that linux
+# supports asynchronous read-ahead.  So _MOST_ of the time, the data
+# will be sitting in memory for us already when we go to read it.
+#
+# What other OS's (besides NT) support async file i/o?  [VMS?]
+#
+# Regardless, this is useful for pipes, and stdin/stdout...
+
+if os.name == 'posix':
+	import fcntl
+
+	class file_wrapper:
+		# here we override just enough to make a file
+		# look like a socket for the purposes of asyncore.
+
+		def __init__(self, fd):
+			self.fd = fd
+
+		def recv(self, *args):
+			return os.read(self.fd, *args)
+
+		def send(self, *args):
+			return os.write(self.fd, *args)
+
+		read = recv
+		write = send
+
+		def close(self):
+			return os.close(self.fd)
+
+		def fileno(self):
+			return self.fd
+
+	class file_dispatcher(dispatcher):
+
+		def __init__(self, fd):
+			dispatcher.__init__(self)
+			self.connected = 1
+			# set it to non-blocking mode
+			flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
+			flags = flags | os.O_NONBLOCK
+			fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+			self.set_file(fd)
+
+		def set_file(self, fd):
+			self._fileno = fd
+			self.socket = file_wrapper(fd)
+			self.add_channel()
diff -pruN msnlib-3.3/msncb.py aw/msnlib-3.3/msncb.py
--- msnlib-3.3/msncb.py	2003-09-22 02:27:52.000000000 -0300
+++ aw/msnlib-3.3/msncb.py	2004-06-18 00:23:37.000000000 -0300
@@ -1,5 +1,3 @@
-
-
 import string
 import urllib
 import md5
@@ -7,6 +5,7 @@ import md5
 import socket
 
 import msnlib
+import msn_s
 
 """
 This is the home for the msn callback class and examples (that might move to
@@ -20,9 +19,9 @@ All of them receive as their first argum
 the main connection object; you probably already know what it is.
 
 The models are:
-error: 		def cb_err(md, errno, params)
-server:		def cb_def(md, type, tid, params)
-switchboard:	def cb_usr(md, type, tid, params, sbd)
+error:          def cb_err(md, errno, params)
+server:         def cb_def(md, type, tid, params)
+switchboard:    def cb_usr(md, type, tid, params, sbd)
 
 See below for more examples.
 
@@ -30,7 +29,7 @@ Probably you should base your own callba
 thought with that in mind, so you can use yours as wrappers that handle only
 your app-specific code and forget about the protocol-specific mess.
 
-		Alberto (albertogli@telpin.com.ar)
+				Alberto (albertogli@telpin.com.ar)
 """
 
 
@@ -40,82 +39,83 @@ debug = msnlib.debug
 
 class cb:
 	def __init__(self):
-		self.unk = cb_unk	# unknown
-		self.err = cb_err	# server error
-		self.msg = cb_msg	# get a message
-		self.chl = cb_chl	# challenge
-		self.qry = cb_ign	# query response
-		self.iln = cb_iln	# status notification
-		self.chg = cb_ign	# status change
-		self.nln = cb_nln	# status notification
-		self.fln = cb_fln	# status offline
-		self.out = cb_out	# disconnect
-		self.blp = cb_ign	# privacy mode change
-		self.lst = cb_lst	# list requests
-		self.bpr = cb_bpr	# user info
-		self.gtc = cb_ign	# add notification
-		self.syn = cb_ign	# list sync confirmation
-		self.prp = cb_prp	# private info
-		self.lsg = cb_lsg	# group list
-		self.add = cb_add	# user add
-		self.rem = cb_rem	# user remove
-		self.adg = cb_adg	# group add
-		self.rmg = cb_rmg	# group del
-		self.reg = cb_reg	# group rename
-		self.rea = cb_rea	# nick change
-		self.rng = cb_rng	# switchboard invitation
-		self.iro = cb_iro	# multi-user chat
-		self.ans = cb_ans	# answer confirmation
-		self.xfr = cb_xfr	# switchboard request
-		self.usr = cb_usr	# sb request initial identification
-		self.cal = cb_ign	# call confirmation
-		self.joi = cb_joi	# session join
-		self.ack = cb_ack	# message acknowledge
-		self.nak = cb_nak	# message negative acknowledge
-		self.bye = cb_bye	# switchboard user disconnect
+		self.unk = cb_unk       # unknown
+		self.err = cb_err       # server error
+		self.msg = cb_msg       # get a message
+		self.chl = cb_chl       # challenge
+		self.qry = cb_ign       # query response
+		self.iln = cb_iln       # status notification
+		self.chg = cb_ign       # status change
+		self.nln = cb_nln       # status notification
+		self.fln = cb_fln       # status offline
+		self.out = cb_out       # disconnect
+		self.blp = cb_ign       # privacy mode change
+		self.lst = cb_lst       # list requests
+		self.bpr = cb_bpr       # user info
+		self.gtc = cb_ign       # add notification
+		self.syn = cb_ign       # list sync confirmation
+		self.prp = cb_prp       # private info
+		self.lsg = cb_lsg       # group list
+		self.add = cb_add       # user add
+		self.rem = cb_rem       # user remove
+		self.adg = cb_adg       # group add
+		self.rmg = cb_rmg       # group del
+		self.reg = cb_reg       # group rename
+		self.rea = cb_rea       # nick change
+		self.rng = cb_rng       # switchboard invitation
+		self.iro = cb_iro       # multi-user chat
+		self.ans = cb_ans       # answer confirmation
+		self.xfr = cb_xfr       # switchboard request
+		self.usr = cb_usr       # sb request initial identification
+		self.cal = cb_ign       # call confirmation
+		self.joi = cb_joi       # session join
+		self.ack = cb_ack       # message acknowledge
+		self.nak = cb_nak       # message negative acknowledge
+		self.bye = cb_bye       # switchboard user disconnect
+
 
 
-	
 error_table = {
-	-10: 'Local error',
-	200: 'Syntax error',
-	201: 'Invalid parameter',
-	205: 'Invalid user',
-	206: 'Domain name missing',
-	207: 'Already logged in',
-	208: 'Invalid username',
-	209: 'Invalid fusername',
-	210: 'User list full',
-	215: 'User already there',
-	216: 'User already on list',
-	217: 'User not online',
-	218: 'Already in mode',
-	219: 'User is in the opposite list',
-	280: 'Switchboard failed',
-	281: 'Transfer to switchboard failed',
-	300: 'Required field missing',
-	302: 'Not logged in',
-	500: 'Internal server error',
-	501: 'Database server error',
-	510: 'File operation failed',
-	520: 'Memory allocation failed',
-	600: 'Server is busy',
-	601: 'Server is unavaliable',
-	602: 'Peer nameserver is down',
-	603: 'Database connection failed',
-	604: 'Server is going down',
-	707: 'Could not create connection',
-	711: 'Write is blocking',
-	712: 'Session is overloaded',
-	713: 'Too many active users',
-	714: 'Too many sessions',
-	715: 'Not expected',
-	717: 'Bad friend file',
-	911: 'Authentication failed',
-	913: 'Not allowed when offline',
-	920: 'Not accepting new users',
+		-10: 'Local error',
+		200: 'Syntax error',
+		201: 'Invalid parameter',
+		205: 'Invalid user',
+		206: 'Domain name missing',
+		207: 'Already logged in',
+		208: 'Invalid username',
+		209: 'Invalid fusername',
+		210: 'User list full',
+		215: 'User already there',
+		216: 'User already on list',
+		217: 'User not online',
+		218: 'Already in mode',
+		219: 'User is in the opposite list',
+		280: 'Switchboard failed',
+		281: 'Transfer to switchboard failed',
+		300: 'Required field missing',
+		302: 'Not logged in',
+		500: 'Internal server error',
+		501: 'Database server error',
+		510: 'File operation failed',
+		520: 'Memory allocation failed',
+		600: 'Server is busy',
+		601: 'Server is unavaliable',
+		602: 'Peer nameserver is down',
+		603: 'Database connection failed',
+		604: 'Server is going down',
+		707: 'Could not create connection',
+		711: 'Write is blocking',
+		712: 'Session is overloaded',
+		713: 'Too many active users',
+		714: 'Too many sessions',
+		715: 'Not expected',
+		717: 'Bad friend file',
+		911: 'Authentication failed',
+		913: 'Not allowed when offline',
+		920: 'Not accepting new users',
 }
 
+
 def cb_err(md, errno, params):
 	"Handle server errors"
 	if not error_table.has_key(errno):
@@ -136,14 +136,14 @@ def cb_unk(md, type, tid, params):
 	debug('Error! unknown event type "%s"' % type)
 	debug('params: ' + str(params))
 
-	
+
 def cb_chl(md, type, tid, params):
 	"Handles the challenges"
 	if type != 'CHL': raise 'CallbackMess', (md, type, params)
 	hash = params + 'VT6PX?UQTM4WM%YR' # magic from www.hypothetic.org
 	hash = md5.md5(hash).hexdigest()
 	md._send('QRY', 'PROD0038W!61ZTF9 32')
-	md.fd.send(hash)
+	md.session.send(hash)
 
 
 def cb_ign(md, type, tid, params, nd = None):
@@ -154,7 +154,7 @@ def cb_ign(md, type, tid, params, nd = N
 def cb_out(md, type, tid, params):
 	"Server disconnected us"
 	debug('!!! Server closed the connection: ' + params)
-	
+
 
 def cb_iln(md, type, tid, params):
 	"Handles a friend status change"
@@ -183,7 +183,7 @@ def cb_nln(md, type, tid, params):
 	email = t[0]
 	if len(t) > 1: nick = urllib.unquote(t[1])
 	else: nick = ''
-	
+
 	md.users[email].status = status
 	md.users[email].realnick = nick
 	debug('FRIEND %s (%s) changed status to :%s:' % (nick, email, status))
@@ -226,15 +226,15 @@ def cb_lst(md, type, tid, params):
 		groups = p[2]
 	else:
 		groups = '0'
-	
+
 	# we only use one main group id
 	gid = groups.split(',')[0]
-	
+
 	if email in md.users.keys():
 		user = md.users[email]
 	else:
 		user = msnlib.user(email, nick, gid)
-	
+
 	# the list mask is a bitmask, composed of:
 	# FL: 1
 	# AL: 2
@@ -245,20 +245,20 @@ def cb_lst(md, type, tid, params):
 	if listmask & 1:
 		user.lists.append('F')
 		md.users[email] = user
-	
+
 	# in reverse
 	if listmask & 8:
 		user.lists.append('R')
 		md.reverse[email] = user
-	
+
 	# in allow
 	if listmask & 2:
 		user.lists.append('A')
-	
+
 	# in block
 	if listmask & 4:
 		user.lists.append('B')
-	
+
 	# save in the global last_lst the email, because BPRs might need it
 	md._last_lst = email
 
@@ -280,7 +280,7 @@ def cb_prp(md, type, tid, params):
 	type = t[0]
 	if len(t) > 1: param = urllib.unquote(t[1])
 	else: param = ''
-	
+
 	if   type == 'PHH': md.homep = param
 	elif type == 'PHW': md.workp = param
 	elif type == 'PHM': md.mobilep = param
@@ -288,7 +288,7 @@ def cb_prp(md, type, tid, params):
 
 
 def cb_add(md, type, tid, params):
-	"Handles a user add; both you adding a user and a user adding you" 
+	"Handles a user add; both you adding a user and a user adding you"
 	t = params.split(' ')
 	type = t[0]
 	if type == 'RL':
@@ -370,24 +370,20 @@ def cb_rng(md, type, tid, params):
 	port = int(port)
 	hash = t[2]
 	email = t[3]
+
+	sbd = msnlib.sbd(md.new_transport())
+	#sbd.sesion = md.new_transport() #msn_s.http_transport()
+	sbd.session.connect( (ip,port), servertype='SB' ) #async connect..
 	
-	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-	# we set the socket nonblocking so we don't block (duh!) on connect();
-	# it will be picked up later from the select loop and handled via the
-	# main read() call, which you will have to see to find out the rest.
-	fd.setblocking(0)
-	fd.connect_ex((ip, port))
-	
-	sbd = msnlib.sbd()
-	sbd.fd = fd
 	sbd.block = 0
-	sbd.state = 'cp'
+	#sbd.state = 'cp'
+	sbd.state = 're'
 	sbd.type = 'answer'
 	sbd.endpoint = (ip, port)
 	sbd.emails.append(email)
 	sbd.hash = hash
 	sbd.session_id = sid
-	md.submit_sbd(sbd) 		# it has the connect pending
+	md.submit_sbd(sbd)              # it has the connect pending
 
 
 def cb_xfr(md, type, tid, params):
@@ -396,11 +392,6 @@ def cb_xfr(md, type, tid, params):
 	ip, port = t[1].split(':')
 	port = int(port)
 	hash = t[3]
-
-	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-	fd.setblocking(0)		# see cb_rng
-	fd.connect_ex((ip, port))
-	
 	# look for the sbd, matching the tid
 	sbd = None
 	for i in md.sb_fds:
@@ -410,10 +401,13 @@ def cb_xfr(md, type, tid, params):
 	if not sbd:
 		debug('AIEEE: XFR without sbd!')
 		raise 'XFRError'
-	
-	sbd.fd = fd
+
+	#sbd.fd = fd
+	#it's crreated before..sbd.sesion = md.new_transport() #msn_s.http_transport()
+	sbd.session.connect( (ip,port), servertype='SB' ) #async connect..
 	sbd.block = 0
-	sbd.state = 'cp'
+	#sbd.state = 'cp'
+	sbd.state = 're'
 	sbd.endpoint = (ip, port)
 	sbd.hash = hash
 
@@ -447,12 +441,14 @@ def cb_joi(md, type, tid, params, sbd):
 		if email not in md.users.keys():
 			md.users[email] = msnlib.user(email)
 		debug('CALL: user %s joined chat with %s' % \
-			(email, sbd.emails[0]))
+				(email, sbd.emails[0]))
 	# otherwise (common path) set up the sbd and flush the messages
 	else:
 		sbd.state = 'es'
 		debug('CALL: user %s replied your chat request; flushing' % email)
+		#print 'aca 1'
 		md.sendmsg(email)
+		#print 'aca 2'
 		debug('CALL: message queue for %s flushed' % email)
 
 
@@ -475,7 +471,7 @@ def cb_ack(md, type, tid, params, sbd):
 def cb_nak(md, type, tid, params, sbd):
 	"Get a message negative acknowledge"
 	debug('NAK: tid:%s' % tid)
-	
+
 
 def cb_bye(md, type, tid, params, sbd):
 	"Handles a user sb disconnect"
@@ -487,4 +483,3 @@ def cb_bye(md, type, tid, params, sbd):
 	else:
 		debug('BYE: closing %s' % str(sbd))
 		md.close(sbd)
-
diff -pruN msnlib-3.3/msnlib.py aw/msnlib-3.3/msnlib.py
--- msnlib-3.3/msnlib.py	2003-11-11 23:34:10.000000000 -0300
+++ aw/msnlib-3.3/msnlib.py	2004-06-18 00:23:37.000000000 -0300
@@ -7,6 +7,9 @@ import socket
 import select
 import md5
 import urllib
+import ahttplib
+
+import msn_s
 
 """
 MSN Messenger Client Library
@@ -18,6 +21,7 @@ VERSION = 0x0303
 LOGIN_HOST = 'messenger.hotmail.com'
 LOGIN_PORT = 1863
 
+
 status_table = {
 	'online':       'NLN',
 	'away':         'AWY',
@@ -90,8 +94,8 @@ class sbd:
 	You will find more information in the doc directory.
 	"""
 
-	def __init__(self):
-		self.fd = None		# connection fd
+	def __init__(self, transport):
+		#self.fd = None		# connection fd
 		self.state = None	# connection's state (see doc above)
 		self.emails = []	# emails we talk to through
 		self.msgqueue = []	# outgoing message queue
@@ -103,21 +107,22 @@ class sbd:
 					# unique for consistency
 		self.block = 1		# blocking state
 		self.orig_tid = None	# tid of the original XFR
+		self.session = transport #sb_transport  # connection fd
 	
 	def __repr__(self):
-		return '<sbd: emails:%s state:%s fd:%d endpoint:%s>' % \
-			(str(self.emails), self.state, \
-			self.fileno(), self.endpoint)
+		return '<%d:sbd: emails:%s state:%s fd:%s endp:%s>' % \
+			(len(self.msgqueue), str(self.emails), self.state, \
+			str(self.session), self.endpoint)
 	
 	def fileno(self):
-		return self.fd.fileno()
+		#return self.fd.fileno()
+		return self.session.fileno()
 
 	def get_tid(self):
 		"Returns a valid tid as string"
 		self.tid = self.tid + 1
 		return str(self.tid - 1)
 
-	
 
 class msnd:
 	"""MSN Descriptor
@@ -154,8 +159,9 @@ class msnd:
 	working code.
 	"""
 	
-	def __init__(self):
-		self.fd = None			# socket fd
+	def __init__(self, transport='socket', proxy=('http','127.0.0.1',3128)):
+		#self.fd = None			# socket fd
+		self.session = None		# session instance 
 		self.sb_fds = []		# switchboard fds
 		self.tid = 1			# transaction id
 		
@@ -181,14 +187,17 @@ class msnd:
 		self.reverse = {}		# reverse user list
 		self.groups = {}		# group list
 		
+		self.transport = transport	#keeps the transport we'll use
+		self.proxy = proxy			#and the proxy address if we want..
 	
 	def __repr__(self):
-		return '<msnd object, fd:%s, email:%s, tid:%s>' % (self.fd,
-			self.email, self.tid)
+		return '<msnd object, fd:'+str(self.session)+', email:'+ \
+			str(self.email)+', tid:'+str(self.tid)+'>'
+
 	
 	def fileno(self):
 		"Useful for select()"
-		return self.fd.fileno()
+		return self.session.fileno()
 
 	def encode(self, s):
 		"Encodes a string from local encoding to utf8"
@@ -224,7 +233,8 @@ class msnd:
 		
 		iwtd = []
 		owtd = []
-		iwtd.append(self)
+		if self.session<>None and not self.session.isclosed():
+			iwtd.append(self)
 		for nd in self.sb_fds:
 			if nd.state == 'cp':	# connect is pending
 				owtd.append(nd)
@@ -234,8 +244,28 @@ class msnd:
 			else:			# readable!
 				iwtd.append(nd)
 		return (iwtd, owtd)
-		
-	
+
+
+	def poll(self):
+		ret = True
+		for s in self.sb_fds+[self]:
+			special = False
+			if s.__class__<>msnd:
+				if s.state=='cp' or s.state=='re':
+					special = True
+			if s.session.process() or special:
+				try:
+					self.read(s)
+				except Exception, e:
+					if self==s:
+						ret=False
+					print 'exception1: ', e
+				except:
+					print 'other than an exception has been raised!'
+					raise
+		return ret      
+
+
 	def get_tid(self):
 		"Returns a valid tid as string"
 		self.tid = self.tid + 1
@@ -249,32 +279,35 @@ class msnd:
 		if not nd:
 			nd = self
 		tid = nd.get_tid()
-		fd = nd.fd
+		#fd = nd.fd
+		session = nd.session
 		c = cmd + ' ' + tid
 		if params: c = c + ' ' + params
-		debug(str(fd.fileno()) + ' >>> ' + c)
+		#debug(str(fd.fileno()) + ' >>> ' + c)
 		if not raw:
 			c = c + '\r\n'
 		c = self.encode(c)
-		return fd.send(c)
+		#print 'sending :',repr(c)
+		return session.send(c)
 	
 	
-	def _recv(self, fd = None):
+	def _recv(self, session = None):
 		"Reads a command from the server, returns (cmd, tid, params)"
-		if not fd:
-			fd = self.fd
-		# cheap and dirty readline, FIXME
-		buf = ''
-		c = fd.recv(1)
-		while c != '\n' and c != '':
-			buf = buf + c
-			c = fd.recv(1)
-		
-		if c == '':
-			raise 'SocketError'
-		
-		buf = buf.strip()
-		pbuf = buf.split(' ')
+		if not session:
+			session = self.session
+		#print 'calling read line ... '
+		try:
+			pbuf = session.readline()
+		except:
+			raise 'no data yet'
+		#print 'after read line ... '
+		l = 0
+		for i in pbuf:
+			l += len(i)
+		if l==0:
+			raise 'no data yet'
+		#else:
+		#	print 'L is : ',l
 		
 		cmd = pbuf[0]
 		
@@ -290,18 +323,19 @@ class msnd:
 			tid = '0'
 			params = ''
 		
-		debug(str(fd.fileno()) + ' <<< ' + buf)
+		debug(repr(session) + ' <<< ' + str(pbuf))
 		return (cmd, tid, params)
 	
 	
-	def _recvmsg(self, msglen, fd = None):
+	def _recvmsg(self, msglen, session = None):
 		"Read a message from the server, returns it"
-		if not fd:
-			fd = self.fd
+		if not session: 
+			session = self.session
 		left = msglen
 		buf = ''
 		while len(buf) != msglen:
-			c = fd.recv(left)
+			#print 'calling recv with:', left
+			c = session.recv(left)
 			#debug(str(fd.fileno()) + ' <<< ' + buf)
 			buf = buf + c
 			left = left - len(c)
@@ -316,16 +350,18 @@ class msnd:
 		Note that if there is no such user, we create it in order to
 		be able to do operations on users that are not in our server
 		list."""
-		
+		#print repr(sbd)
 		self.sb_fds.append(sbd)
 		email = sbd.emails[0]
 		if email not in self.users.keys():
 			self.users[email] = user(email)
 		if self.users[email].sbd and self.users[email].sbd != sbd:
 			# override the sbd, but keep the message queue
+			#print 'aca cambiando el sbd..???'
 			sbd.msgqueue = self.users[email].sbd.msgqueue[:]
 			self.close(self.users[email].sbd)
 		self.users[email].sbd = sbd
+		#print 'after submit sbd..', self.sb_fds
 		return
 	
 	
@@ -410,8 +446,8 @@ class msnd:
 
 	def disconnect(self):
 		"Disconnect from the server"
-		self.fd.send('OUT\r\n')
-		self.fd.close()
+		self.session.send('OUT\r\n')
+		self.session.close()
 	
 	
 	def close(self, sb):
@@ -430,47 +466,58 @@ class msnd:
 		"Invites a user into an existing sbd"
 		self._send('CAL', email, nd = sbd)
 	
+	
+	def new_transport(self):
+		"""Returns a new transport instance"""
+		if self.transport=='socket':
+			return msn_s.raw_transport()
+		elif self.transport=='http':
+			return msn_s.http_transport()
+		elif self.transport=='proxy':
+			return msn_s.http_transport(self.proxy)
+		else:
+			raise Exception('Invalid transport!!')
+	
 	def login(self):
 		"Logins to the server, really boring"
-
 		# open socket
-		self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-		self.fd.connect((self.lhost, self.lport))
+		self.session = self.new_transport()
+		self.session.connect((self.lhost, self.lport))
 		
 		# version information
 		self._send('VER', 'MSNP8 CVR0')
-		
 		r = self._recv()
 		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
 			raise 'VersionError', r
 		
-		# lie the version, just in case
-		self._send('CVR', '0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS ' + self.email)
-		self._recv()	# we just don't care what we get
-	
-		# ask for notification server
-		self._send('USR', 'TWN I ' + self.email)
-
-		r = self._recv()
-		if r[0] != 'XFR' and r[2][0:2] != 'NS':
-			raise 'NSError', r
-
-		# parse the notification server ip and port (as int)
-		ns = string.split(r[2])[1]
-		self.ns = ns.split(':')
-		self.ns[1] = int(self.ns[1])
-		self.ns = tuple(self.ns)
-		
-		# close the fd and reopen it on the ns
-		self.fd.close()
-		self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-		self.fd.connect(self.ns)
-
-		# version, same as before
-		self._send('VER', 'MSNP8 CVR0')
-		r = self._recv()
-		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
-			raise 'VersionError', r
+		if self.transport == 'socket':
+			# lie the version, just in case
+			self._send('CVR', '0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS ' + self.email)
+			self._recv()	# we just don't care what we get
+	
+			# ask for notification server
+			self._send('USR', 'TWN I ' + self.email)
+
+			r = self._recv()
+			if r[0] != 'XFR' and r[2][0:2] != 'NS':
+				raise 'NSError', r
+
+			# parse the notification server ip and port (as int)
+			ns = string.split(r[2])[1]
+			self.ns = ns.split(':')
+			self.ns[1] = int(self.ns[1])
+			self.ns = tuple(self.ns)
+		
+			# close the fd and reopen it on the ns
+			self.session.close()
+			self.session = self.new_transport()
+			self.session.connect(self.ns)
+
+			# version, same as before
+			self._send('VER', 'MSNP8 CVR0')
+			r = self._recv()
+			if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
+				raise 'VersionError', r
 		
 		# lie the version, just in case
 		self._send('CVR', '0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS	' + self.email)
@@ -478,7 +525,6 @@ class msnd:
 	
 		# auth: send user, get hash
 		self._send('USR', 'TWN I ' + self.email)
-		
 		r = self._recv()
 		if r[0] != 'USR':
 			raise 'AuthError', r
@@ -489,25 +535,34 @@ class msnd:
 		self._send('USR', 'TWN S ' + passportid)
 
 		r = self._recv()
+		#print '4 -> ',repr(r)
 		if r[0] != 'USR' and r[2][0:2] != 'OK':
 			raise 'AuthError', r
 		self.nick = string.split(r[2])[2]
 		self.nick = urllib.unquote(self.nick)
-		
 		return 1
 
 
 	def passport_auth(self, hash):
 		"""Logins into passport and obtains an ID used for
 		authorization; it's a helper function for login"""
-		import urllib
-		import httplib
-		
+		def new_auth_con(self, target):
+			if self.transport=='proxy':
+				h = ahttplib.AProxyHttpConnection( \
+						target, proxy=self.proxy, http_version='1.0')
+			else:
+				h = ahttplib.AHttpsConnection( \
+						target, http_version='1.0')
+			return h
+		    
 		# initial connection
 		debug('PASSPORT begin')
-		nexus = urllib.urlopen('https://nexus.passport.com/rdr/pprdr.asp')
-		h = nexus.headers
-		purl = h['PassportURLs']
+		h = new_auth_con(self,('https','nexus.passport.com', 443))
+		h.connect()
+		h.request('/rdr/pprdr.asp')
+		r = ahttplib.mini_loop(h)
+		h.close()
+		purl = r.headers['PassportURLs']
 		
 		# parse the info
 		d = {}
@@ -531,28 +586,31 @@ class msnd:
 		
 		# connect to the given server
 		debug('SSL Connect to %s' % login_server)
-		ls = httplib.HTTPSConnection(login_host)
-		
+		ls = new_auth_con(self, ('https',login_host,443))
+		ls.connect()
 		# make the request
 		debug('SSL GET')
-		ls.request('GET', login_server, '', headers)
-		resp = ls.getresponse()
+		ls.request( login_server, method='GET', headers=headers)
+		resp = ahttplib.mini_loop(ls)
+		ls.close()
 		
 		# loop if we get redirects until we get a definitive answer
-		debug('SSL Response %d' % resp.status)
-		while resp.status == 302:
-			login_server = resp.getheader('Location')
+		#debug('SSL Response %d' % resp.status)
+		debug('SSL Response %d' % resp.rcode)
+		while resp.rcode == 302:
+			login_server = resp.headers['Location']
 			login_host = login_server.split('/')[2]
 			debug('SSL Redirect to %s' % login_server)
-			ls = httplib.HTTPSConnection(login_host)
+			ls = new_auth_con(self, ('https',login_host,443))
 			headers = { 'Authorization': ahead }
-			ls.request('GET', login_server, '', headers)
-			resp = ls.getresponse()
-			debug('SSL Response %d' % resp.status)
+			ls.request( login_server, method='GET', headers=headers)
+			resp = ahttplib.mini_loop(ls)
+			ls.close()
+			debug('SSL Response %d' % resp.rcode)
 		
 		# now we have a definitive answer, if it's not 200 (success)
 		# just raise AuthError
-		if resp.status != 200:
+		if resp.rcode != 200:
 			# for now we raise 911, which means authentication
 			# failed; but maybe we can get more detailed
 			# information
@@ -560,9 +618,9 @@ class msnd:
 
 		# and parse the headers to get the passport id
 		try:
-			ainfo = resp.getheader('Authentication-Info')
+			ainfo = resp.headers['Authentication-Info']
 		except:
-			ainfo = resp.getheader('WWW-Authenticate')
+			ainfo = resp.headers['WWW-Authenticate']
 		
 		d = {}
 		for i in ainfo.split(','):
@@ -581,17 +639,19 @@ class msnd:
 		"""
 		if not nd:
 			nd = self
-		
+		#else:
+			#if nd<>self: pass
+			
 		# handle different stages of switchboard initialization
 		if nd in self.sb_fds:
 			# connect pending
 			if nd.state == 'cp':
 				# see if the connect went well
-				r = nd.fd.getsockopt(socket.SOL_SOCKET,
-					socket.SO_ERROR)
-				if r != 0:
+				try:
+					nd.session.connect(nd.endpoint)
+				except:
 					raise 'SocketError', 'ConnectFailed'
-				nd.fd.setblocking(1)
+				nd.session.setblocking(1) # XXX WHY WE MADE THIS BLOCKING!?
 				nd.block = 1
 				nd.state = 're'
 				
@@ -608,9 +668,12 @@ class msnd:
 				nd.state = 'us'
 				return
 		
-		
-		
-		r = self._recv(nd.fd)
+		try:
+			r = self._recv(nd.session)
+			#print '_RECV: ',r
+		except:
+			#print 'no Recv!!'
+			return
 		type = r[0]
 		tid = r[1]
 		params = string.strip(r[2])
@@ -652,9 +715,8 @@ class msnd:
 		elif type == 'MSG':
 			params = tid + ' ' + params
 			mlen = int(r[2].split()[-1])
-			msg = self._recvmsg(mlen, nd.fd)
+			msg = self._recvmsg(mlen, nd.session)
 			self.cb.msg(self, type, params, msg, nd)
-		
 		else:
 			# catch server errors - always numeric type
 			try:
@@ -695,7 +757,7 @@ class msnd:
 		
 		# we don't have a connection
 		if not sb:
-			sb = sbd()
+			sb = sbd(self.new_transport())
 			sb.state = 'xf'
 			sb.type = 'invite'
 			sb.emails.append(email)
@@ -712,11 +774,13 @@ class msnd:
 		# it's not ready yet
 		elif sb.state != 'es':
 			sb.msgqueue.append(msg)
+			#print 'the sb is not ready yet..'
 			return 1
 		
 		# no more excuses, send it
 		else:
 			# we make a list with all the messages to send
+			#print 'send now??'
 			pend = sb.msgqueue
 			if msg:
 				pend.append(msg)
@@ -733,5 +797,3 @@ class msnd:
 		
 			return 2
 		
-		
-
diff -pruN msnlib-3.3/msn.py aw/msnlib-3.3/msn.py
--- msnlib-3.3/msn.py	1969-12-31 21:00:00.000000000 -0300
+++ aw/msnlib-3.3/msn.py	2004-06-18 00:23:37.000000000 -0300
@@ -0,0 +1,1611 @@
+#!/usr/bin/env python
+
+
+import sys
+import os
+import socket
+import select
+import string
+import traceback
+import urllib
+import time
+
+import msnlib
+import msncb
+
+win32 = sys.platform.startswith("win")
+if win32:
+    import msvcrt
+
+"""
+MSN Client
+
+This is a fully usable msn client, which also serves as reference
+implementation for msnlib-based code.
+For further information refer to the documentation or the source (which is
+always preferred).
+Please direct any comments to the msnlib mailing list,
+msnlib-devel@auriga.wearlab.de.
+You can find more information, and the package itself, at
+http://users.auriga.wearlab.de/~alb/msnlib
+"""
+
+
+#
+# constant strings
+#
+
+help = """\
+Command list:
+status [mode]   Shows the current status, or changes it to "mode", which can
+                be one of: online away busy brb phone lunch invisible idle
+q               Quits the program
+w               Prints your entire contact list
+ww              Prints your entire contact list, including email addresses
+e               Prints your online contacts
+ee              Prints your online contacts, including email addresses
+eg              Prints your online contacts with the groups
+wr              Prints your reverse contact list
+h               Shows your incoming message history
+add e n [g]     Adds the user "e" with the nick "n" to the group "g"
+del nick        Deletes the user with nick "nick"
+lignore [nick]  Locally ignores the user, or display the locally ignored users
+lunignore nick  Removes a user from the locally ignored users list
+block nick      Blocks a user
+unblock nick    Unblocks a blocked user
+g               Shows the group list
+gadd gname      Adds the group "gname"
+gdel gname      Deletes the group "gname". Note that all the users in the
+                group will be deleted too.
+gren old new    Renames the group "old" with the name "new"
+color [theme]   Shows or set the color theme to "theme"
+close nick      Closes the switchboard connection with "nick"
+config          Shows the configuration
+info [nick]     Shows the user information and pending messages (if any),
+                or our personal info
+nick newnick    Changes your nick to "newnick"
+privacy p a     Sets whether accept messages from people not on your list (p)
+                and require authorization (a)
+m nick text     Sends a message to "nick" with the "text"
+a text          Sends a message to the last person you sent a message to
+r text          Sends a message to the last person that sent you a message
+invite u1 to u2 Invites u1 into the chat with u2
+
+In most cases, where you are asked for a nick, you can alternatively enter the
+email.  This makes it easier to handle people with weird or long nicks.
+"""
+
+
+#
+# colors, for nice output
+#
+
+class color_color:
+    def __init__(self):
+        self.name =     'color'
+        self.black =    '\x1b[0;30m'
+        self.red =      '\x1b[0;31m'
+        self.green =    '\x1b[0;32m'
+        self.yellow =   '\x1b[0;33m'
+        self.blue =     '\x1b[0;34m'
+        self.magenta =  '\x1b[0;35m'
+        self.cyan =     '\x1b[0;36m'
+        self.white =    '\x1b[0;37m'
+        self.normal =   '\x1b[0m'
+        self.bold =     '\x1b[1m'
+        self.clear =    '\x1b[J'
+
+class color_bw:
+    def __init__(self):
+        self.name =     'bw'
+        self.black =    '\x1b[0;30m'
+        self.red =      '\x1b[0m'
+        self.green =    '\x1b[0m'
+        self.yellow =   '\x1b[0m'
+        self.blue =     '\x1b[0m'
+        self.magenta =  '\x1b[0m'
+        self.cyan =     '\x1b[0m'
+        self.white =    '\x1b[0m'
+        self.normal =   '\x1b[0m'
+        self.bold =     '\x1b[0m'
+        self.clear =    '\x1b[J'
+
+class color_null:
+    def __init__(self):
+        self.name =     'null'
+        self.black =    \
+        self.red =      \
+        self.green =    \
+        self.yellow =   \
+        self.blue =     \
+        self.magenta =  \
+        self.cyan =     \
+        self.white =    \
+        self.normal =   \
+        self.bold =     \
+        self.clear =    ''
+
+color_classes = {
+        'color':      color_color,
+        'bw':           color_bw,
+        'null':           color_null
+}
+c = color_classes['null']()
+
+
+#
+# different useful prints
+#
+
+def printl(line, color = c.normal, bold = 0):
+    "Prints a line with a color"
+    out = ''
+    if line and line[0] == '\r':
+        clear_line()
+    if bold:
+        out = c.bold
+    out = color + out + line + c.normal
+    safe_write(out)
+    safe_flush()
+
+def perror(line):
+    "Prints an error"
+    out = ''
+    out += c.yellow + c.bold + '!' + c.normal
+    out += c.red + c.bold + '!' + c.normal
+    out += c.blue + c.bold + '!' + c.normal
+    out += ' ' + c.green + c.bold + line + c.normal + '\a'
+    safe_write(out)
+    safe_flush()
+
+def pexc(line):
+    "Prints an exception"
+    out = '\n'
+    out += ( c.cyan + c.bold + '!' + c.normal ) * 3
+    safe_write(out)
+    safe_write(c.bold + line)
+    safe_flush()
+    traceback.print_exc()
+    safe_write(c.normal)
+    safe_write('\n')
+    safe_flush()
+
+def print_list(md, only_online = 0, userlist = None, include_emails = 0):
+    "Prints the user list"
+    if not userlist:
+        userlist = md.users
+    ul = userlist.keys()
+    ul.sort()
+    for email in ul:
+        u = userlist[email]
+        if u.status != 'FLN':
+            hl = 1
+        else:
+            if only_online: continue
+            hl = 0
+        status = msnlib.reverse_status[u.status]
+        printl('%7.7s :: %s ' % (status, u.nick), bold = hl)
+        if include_emails: printl('(%s) ' % (email), bold = hl)
+        printl('\n')
+
+def print_group_list(md):
+    "Prints the group list"
+    gids = md.groups.keys()
+    gids.sort()
+    for gid in gids:
+        printl('%3.3s :: %s\n' % (gid, md.groups[gid]))
+
+def print_grouped_list(md, only_online = 0, include_emails = 0):
+    db = {}
+    for gid in md.groups.keys():
+        db[gid] = []
+    for gid in md.groups.keys():
+        for e in md.users.keys():
+            if md.users[e].gid == gid:
+                db[gid].append(e)
+    gids = db.keys()
+    gids.sort()
+    for gid in gids:
+        printl(':: %s ::\n' % md.groups[gid], bold = 1)
+        ul = db[gid]
+        ul.sort()
+        for email in ul:
+            u = m.users[email]
+            if u.status != 'FLN':
+                hl = 1
+            else:
+                if only_online: continue
+                hl = 0
+            status = msnlib.reverse_status[u.status]
+            printl('\t%7.7s :: %s ' % (status, u.nick), bold = hl)
+            if include_emails: printl('(%s) ' % (email), bold = hl)
+            printl('\n')
+
+def print_user_info(email):
+    "Prints the user information, and pending messages"
+    u = m.users[email]
+    out = c.bold
+    out += c.bold + 'User info for ' + email + '\n'
+    out += c.bold + 'Nick:\t\t' + c.normal + u.nick + '\n'
+    out += c.bold + 'Status:\t\t' + c.normal + msnlib.reverse_status[u.status] + '\n'
+    if 'B' in u.lists:
+        out += c.bold + 'Mode:\t\t' + c.normal + 'blocked' + '\n'
+    if u.gid != None:
+        out += c.bold + 'Group:\t\t' + c.normal + m.groups[u.gid] + '\n'
+    if u.realnick:
+        out += c.bold + 'Real Nick:\t' + c.normal + u.realnick + '\n'
+    if u.homep:
+        out += c.bold + 'Home phone:\t' + c.normal + u.homep + '\n'
+    if u.workp:
+        out += c.bold + 'Work phone:\t' + c.normal + u.workp + '\n'
+    if u.mobilep:
+        out += c.bold + 'Mobile phone:\t' + c.normal + u.mobilep + '\n'
+    if u.priv.has_key('typing') and u.priv['typing']:
+        out += c.bold + 'Last typing at:\t' + c.normal
+        out += time.asctime(time.localtime(u.priv['typing'])) + '\n'
+    if u.sbd:
+        out += c.bold + 'Switchboard:\t' + c.normal + str(u.sbd) + '\n'
+        if u.sbd.msgqueue:
+            out += c.bold + 'Pending messages:' + '\n'
+            for msg in u.sbd.msgqueue:
+                out += c.bold + '\t>>> ' + c.normal + msg + '\n'
+    printl(out)
+
+def print_prompt():
+    "Prints the user prompt"
+    safe_write('\r' + c.red + c.bold + '[msn] ' + c.normal)
+    safe_flush()
+
+def print_inc_msg(email, lines, eoh = 0, quiet = 0, ptime = 1, recvtime = 0):
+    """Prints an incoming message from a list of lines and an optional
+    end-of-header pointer.  You can also pass the original received time as
+    a parameter, this is used for history printed."""
+    nick = email2nick(email)
+    if not nick: nick = email
+    if email in ignored:
+        return
+    if ptime:
+        if recvtime:
+            ctime = time.strftime('%I:%M:%S%p', time.localtime(recvtime))
+        else:
+            ctime = time.strftime('%I:%M:%S%p', now())
+        printl('%s ' % ctime, c.blue)
+    printl('%s' % nick, c.cyan, 1)
+    if len(lines[eoh:]) == 1:
+        printl(' <<< %s\n' % lines[eoh], c.yellow, 1)
+    else:
+        printl(' <<< \n\t', c.yellow, 1)
+        msg = string.join(lines[eoh:], '\n\t')
+        msg = msg.replace('\r', '')
+        printl(msg + '\n', c.bold)
+    beep(quiet)
+
+def print_out_msg(nick, msg):
+    "Prints an outgoing message"
+    ctime = time.strftime('%I:%M:%S%p', now())
+    printl('%s ' % ctime, c.blue)
+    printl('%s' % nick, c.cyan, 1)
+    printl(' >>> ', c.yellow, 1)
+    printl('%s' % msg)
+
+
+def beep(q = 0):
+    "Beeps unless it's told to be quiet"
+    if not q:
+        printl('\a')
+
+
+def safe_flush():
+    """Safely flushes stdout. It fixes a strange issue with flush and
+    nonblocking io, when flushing too fast."""
+    c = 0
+    while c < 100:
+        try:
+            sys.stdout.flush()
+            return
+        except IOError:
+            c +=1
+            time.sleep(0.01 * c)
+    raise Exception, 'flushed too many times, giving up. Please report!'
+
+def safe_write(text):
+    """Safely writes to stdout. It fixes the same issue that safe_flush,
+    that is, writing too fast raises errors due to nonblocking fd."""
+    c = 1
+    while c:
+        try:
+            sys.stdout.write(text)
+            return
+        except IOError:
+            c += 1
+            time.sleep(0.01 * c)
+    raise Exception, 'wrote too many times, giving up. Please report!'
+
+
+#
+# useful functions
+#
+
+def quit(code = 0):
+    "Exits"
+    printl('Closing\n', c.green, 1)
+    try:
+        m.disconnect()
+        global oldtermattr
+        termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, oldtermattr)
+    except:
+        pass
+    sys.exit(code)
+
+def nick2email(nick):
+    "Returns an email according to the given nick, or None if noone matches"
+    for email in m.users.keys():
+        if m.users[email].nick == nick:
+            return email
+    if nick in m.users.keys():
+        return nick
+    return None
+
+def email2nick(email):
+    "Returns a nick accoriding to the given email, or None if noone matches"
+    if email in m.users.keys():
+        return m.users[email].nick
+    else:
+        return None
+
+def matchemail(begin):
+    """"Returns a matching email/nick for the given beginning; it avoids
+    nicks with spaces"""
+    l = len(begin)
+    # first try the ones with sbd
+    for email in m.users.keys():
+        if not m.users[email].sbd:
+            continue
+        nick = m.users[email].nick
+        if ' ' in nick:
+            nick = email
+        if len(nick) >= l and nick[0:l] == begin:
+            return nick
+    # then the nicks
+    for email in m.users.keys():
+        nick = m.users[email].nick
+        if ' ' in nick:
+            continue
+        if len(nick) >= l and nick[0:l] == begin:
+            return nick
+    # and finally the emails
+    for email in m.users.keys():
+        if len(email) >= l and email[0:l] == begin:
+            return email
+    return None
+
+def gname2gid(gname):
+    "Returns a group name according to the given group id"
+    for gid in m.groups.keys():
+        if m.groups[gid] == gname:
+            return gid
+    return None
+
+def get_config(file):
+    "Parses a simple config file, and returns a var:value dict"
+    try:
+        fd = open(file)
+    except:
+        return None
+    lines = fd.readlines()
+    config = {}
+    for i in lines:
+        i = i.strip()
+        if i.find('=') < 0:
+            continue
+        if i[0] == '#':
+            continue
+        var, value = i.split('=', 1)
+        var = var.strip()
+        value = value.strip()
+        config[var] = value
+    return config
+
+def null(s):
+    "Null function, useful to void debug ones"
+    pass
+
+def log_msg(email, type, msg, mtime = 0, users = []):
+    """Logs the message or event of the 'type', related to 'email',
+    with the content 'msg', to a file in the specified directory.  See
+    documentation for more specific details, specially about
+    formatting."""
+
+    if not config['log history']:
+        return
+
+    if config['profile']:
+        prepend = config['profile'] + '::'
+    else:
+        prepend = ''
+    if users:
+        # copy and sort the user list, so we log always to the same
+        # file regarding the order the users were joined
+        usorted = users[:]
+        usorted.sort()
+        #file = os.path.join(config['ME'],'.msn','msnrc-' + profile)
+        file = config['history directory'] + '/' + prepend + 'M::'
+	print 'file is : ',file
+        for i in usorted:
+            file += i + ','
+        # strip the last ','
+        file = file[:-1]
+    else:
+        file = config['history directory'] + '/' + prepend + email
+    if not mtime:
+        mtime = time.time()
+    out = ''
+    out += time.strftime('%d/%b/%Y %H:%M:%S ', time.localtime(mtime))
+    out += email + ' '
+    if type == 'in':
+        out += '<<< '
+        msg = msg.replace('\r', '')
+        lines = msg.split('\n')
+        if len(lines) == 1:
+            out += msg + '\n'
+        else:
+            out += '\n\t'
+            out += string.join(lines[:], '\n\t')
+            out += '\n'
+    elif type == 'out':
+        out += '>>> ' + msg + '\n'
+    elif type == 'status':
+        out += '*** ' + msg + '\n'
+    elif type == 'multi':
+        out += '+++ ' + msg + '\n'
+
+    fd = open(file, 'a')
+    fd.write(out)
+    fd.close()
+    del(fd)
+    return
+
+def now():
+    "Returns the current time, in tuple format"
+    return time.localtime(time.time())
+
+
+#
+# terminal handling
+#
+
+# all this is _ugly_, a real mess; luckily it's pretty much self contained.
+# if you're trying to follow the code, i highly recommend you to skip this
+# section; you really don't need to know it, just think of redraw_cli() pretty
+# much as print_prompt(), stdin_read() as sys.stdin.readline(), and
+# clear_line() as printf('\r'). actually that's quite near true when we don't
+# use termios.
+# it has been written in a way that if termios is not available, we fall back
+# to the normal and old behaviour which is guaranteed to work.
+
+#try:
+    # all of this disables line-buffering on the terminal (thus allowing
+    # char-by-char reads) and echoing (so we output whatever we want); and
+    # finally sets the file nonblocking so we can read all that's
+    # available without complications.
+    # you should read termios and fcntl manpages to find out the details
+#       import termios
+#       stdinfd = sys.stdin.fileno()
+#       oldtermattr = termios.tcgetattr(stdinfd)
+#       newtermattr = termios.tcgetattr(stdinfd)
+#       newtermattr[3] = newtermattr[3] & ~termios.ICANON & ~termios.ECHO
+#       termios.tcsetattr(stdinfd, termios.TCSANOW, newtermattr)
+#       import fcntl, os
+#       fcntl.fcntl(stdinfd, fcntl.F_SETFL, os.O_NONBLOCK)
+#       del(newtermattr)
+#       use_termios = 1
+#except:
+#       use_termios = 0
+use_termios = 0
+
+# now we try to find out the console size; if we fail we fall back to
+# the good old 80x24.
+# note that the (' ' * 10) is just awful, but there is no sane way of
+# doing this without using a C module. it's based on 'struct winsize',
+# but as we only use the first 4 bytes, we don't ask for more; then we
+# unpack the two shorts into (lenght, width)
+#try:
+#       import struct
+#       winsize = fcntl.ioctl(stdinfd, termios.TIOCGWINSZ, ' ' * 10)
+#       winsize = struct.unpack('hh', winsize[:4])
+#except:
+#       winsize = (24, 80)
+
+winsize = (24, 80)
+screenwidth = winsize[1]
+
+# input buffer, where all the characters written by the user are stored in
+inbuf = ''
+
+# input history buffer, to store previous commands.
+# we use a list [buffer, pointer] to avoid namespace pollution
+inbuf_history = [[], -1]
+
+def stdin_read(input=None):
+    """Reads from stdin, and acts in consecuense. If you don't use
+    termios, it's almost the same as calling readline(); but otherwise it
+    handles all the input reading."""
+    global inbuf
+    if input==None and not use_termios:
+        inbuf = sys.stdin.readline()
+        tmpbuf = inbuf
+        inbuf = ''
+        out = parse_cmd(tmpbuf)
+        printl(out + '\n', c.green, 1)
+        redraw_cli()
+        return
+
+    in_esc = 0
+    if input==None:
+        input = sys.stdin.read()
+    
+    for char in input:
+        inbuf = inbuf + char
+        if char == '\n' or char == '\r':
+            # command history
+            if len(inbuf_history[0]) > config['input history size']:
+                del(inbuf_history[0][0])
+            inbuf_history[0].append(inbuf[:-1])
+            inbuf_history[1] = len(inbuf_history[0]) - 1 # moves the pointer
+
+            safe_write(char)
+            tmpbuf = inbuf
+            inbuf = ''
+            out = parse_cmd(tmpbuf)
+            printl(out + '\n', c.green, 1)
+            redraw_cli()
+
+        elif char == '\b' or ord(char) == 127:          # ^H / DEL
+            inbuf = inbuf[:-2]
+            redraw_cli()
+
+        elif char == '\t':                              # tab
+            # we do a basic cycling between the last received and
+            # last sent; first we build the two strings and then
+            # we see which one applies according to some messy
+            # logic
+            if email2nick(last_received):
+                nick = email2nick(last_received)
+                if ' ' in nick:
+                    nick = last_received
+                mtolrecv = 'm ' + nick + ' '
+            else:
+                mtolrecv = None
+
+            if email2nick(last_sent):
+                nick = email2nick(last_sent)
+                if ' ' in nick:
+                    nick = last_sent
+                mtolsent = 'm ' + nick + ' '
+            else:
+                mtolsent = None
+
+            if len(inbuf) == 1:
+                if mtolsent:
+                    inbuf = mtolsent
+                elif mtolrecv:
+                    inbuf = mtolrecv
+                else:
+                    inbuf = inbuf[:-1]
+                    beep()
+            else:
+                # if we have mtolsent, replace with mtolrecv
+                # (if possible, otherwise beep)
+                if mtolsent and inbuf.strip() == mtolsent.strip():
+                    if mtolrecv:
+                        inbuf = mtolrecv
+                    else:
+                        inbuf = inbuf[:-1]
+                        beep()
+                # the opposite case
+                elif mtolrecv and inbuf.strip() == mtolrecv.strip():
+                    if mtolsent:
+                        inbuf = mtolsent
+                    else:
+                        inbuf = inbuf[:-1]
+                        beep()
+                # we have something that is neither mtolsent or
+                # mtolrecv, we try to find a matching
+                # email/nick
+                else:
+                    p = inbuf.split()
+                    if p[0] != 'm' or len(p) != 2:
+                        inbuf = inbuf[:-1]
+                        beep()
+                    else:
+                        begin = p[1]
+                        email = matchemail(begin)
+                        if not email:
+                            inbuf = inbuf[:-1]
+                            beep()
+                        else:
+                            inbuf = 'm ' + email + ' '
+            redraw_cli()
+
+        elif ord(char) == 4:                            # EOT
+            safe_write('\n')
+            out = parse_cmd('')
+            printl(out + '\n', c.green, 1)
+
+        elif ord(char) == 27:                           # ESC
+            # we use in_esc for escape secuenses (composed of
+            # ESC + '[' + LETTER). 1 means got ESC, 2 means got
+            # '['. Here we set to 1, and the rest are in the
+            # generic handling
+            in_esc = 1
+            inbuf = inbuf[:-1]
+
+        elif ord(char) < 32:                            # unhandled control
+            print 'Got weird char: %d' % ord(char)
+            redraw_cli_cond(char)
+
+        else:                                           # normal
+            if not in_esc:
+                redraw_cli_cond(char)
+                continue
+
+            # comes from a escape code
+            elif in_esc == 1:
+                if char == '[':
+                    in_esc = 2
+                else:
+                    in_esc = 0
+                inbuf = inbuf[:-1]
+            elif in_esc == 2:
+                if char == 'A':                 # up
+                    if inbuf_history[1] == -1:
+                        # hit the top, or it's empty;
+                        # remove it from the buffer
+                        inbuf = inbuf[:-1]
+                    else:
+                        clear_line()
+                        pos = inbuf_history[1]
+                        inbuf = inbuf_history[0][pos]
+                        inbuf_history[1] -= 1
+                        redraw_cli()
+                elif char == 'B':               # down
+                    if not inbuf_history[0]:
+                        # it's empty, so we only
+                        # remove it from the buffer
+                        inbuf = inbuf[:-1]
+                    elif inbuf_history[1] == len(inbuf_history[0]) - 1:
+                        # hit the bottom, clear the buffer
+                        clear_line()
+                        inbuf = ''
+                        redraw_cli()
+                    else:
+                        inbuf_history[1] += 1
+                        clear_line()
+                        pos = inbuf_history[1]
+                        inbuf = inbuf_history[0][pos]
+                        redraw_cli()
+                else:                           # unhandled esc
+                    inbuf = inbuf[:-1]
+                in_esc = 0
+
+def redraw_cli():
+    """Redraws the current prompt line, including user input; it first
+    clears the line, either automatically or up to 'lenght' chars."""
+    global inbuf, screenwidth
+    clear_line()
+    print_prompt()
+    lenght = screenwidth - 7        # we subsctract the prompt lenght + 1
+    safe_write(inbuf[-lenght:])
+    safe_flush()
+
+def redraw_cli_cond(char):
+    """Same as redraw_cli, but conditional over the lenght of stdin. That
+    means that if inbuf is getting too big, we redraw; otherwise we just
+    write the character. It's used mostly to avoid innecesary redraw
+    overhead (it avoids 90% of cases)."""
+    global inbuf, screenwidth
+    if len(inbuf) >= (screenwidth - 7):
+        redraw_cli()
+    else:
+        safe_write(char)
+        safe_flush()
+
+def clear_line():
+    """Clears the current line by overwriting it with spaces."""
+    global inbuf, screenwidth
+    if use_termios:
+        safe_write('\r' + (screenwidth - 1) * ' ' + '\r')
+
+
+#
+# stdin command parser
+#
+
+def parse_cmd(cmd):
+    """Parses the commands introduced by the user. It's pretty long and
+    boring, as expected."""
+
+    global c, last_sent, last_received      # ugly but necesary
+
+    if len(cmd) == 0:
+        quit()
+    elif len(cmd) == 1:
+        return ''
+
+    # cut trailing newline and clean up
+    if cmd[-1] == '\n':
+        cmd = cmd[:-1]
+    cmd = cmd.lstrip()
+    orig_cmd = cmd
+    s = cmd.split()
+    if len(s) > 1:
+        cmd = s[0]
+        # recover original params to preserve whitespace
+        # use as index the first parameter to the command
+        params = orig_cmd[orig_cmd.find(s[1]):]
+    else:
+        if not cmd: return ''
+        cmd = s[0]
+        params = ''
+
+
+    # parse
+    if   cmd == 'status':           # change status
+        if not params:
+            return 'Your current status is %s' % msnlib.reverse_status[m.status]
+        if not m.change_status(params):
+            out = 'Status must be one of:\n'
+            out += '\tonline, away, busy, brb, phone, lunch, invisible or idle'
+            return out
+        return 'Status changed to: %s' % params
+
+    elif cmd == 'q':                # quit
+        quit()
+
+    elif cmd == 'reload':           # reload callbacks
+        reload(msncb)
+        m.cb = msncb.cb()
+
+    elif cmd == 'w':                # list
+        print_grouped_list(m)
+
+    elif cmd == 'ww':               # list, include emails
+        print_grouped_list(m, include_emails = 1)
+
+    elif cmd == 'wr':               # reverse list
+        print_list(m, userlist = m.reverse, include_emails = 1)
+
+    elif cmd == 'e':                # list (online only)
+        print_list(m, only_online = 1)
+
+    elif cmd == 'eg':
+        print_grouped_list(m, only_online = 1)
+
+    elif cmd == 'ee':
+        print_grouped_list(m, only_online = 1, include_emails = 1)
+
+    elif cmd == 'g':                # list groups
+        print_group_list(m)
+
+    elif cmd == 'raw':              # send a raw message
+        try:
+            cmd = params[0:3]
+            pars = params[4:]
+        except:
+            return 'Error parsing command'
+        m._send(cmd, pars)
+
+    elif cmd == 'debug':            # enable/disable debugging
+        p = params.split()
+        if len(p) != 1:
+            return 'Error parsing command'
+        if p[0] == 'off':
+            msnlib.debug = null
+            msncb.debug = null
+            return 'Debugging disabled'
+        elif p[0] == 'on':
+            reload(msnlib)
+            reload(msncb)
+            return 'Debugging enabled'
+        else:
+            return 'Unknown parameter - must be "on" or "off"'
+
+    elif cmd == 'config':           # show config variables
+        keys = config.keys()
+        keys.sort()
+        for var in keys:
+            value = str(config[var])
+            if var == 'password':
+                value = '<not displayed>'
+            printl(c.bold + var + ' = ' + c.normal + value + '\n')
+        printl(c.bold + 'use_termios = ' + str(use_termios) + '\n')
+        printl(c.bold + 'screensize = ' + str(winsize) + '\n')
+
+    elif cmd == 'color':            # configure/show colors
+        p = params.split()
+        if len(p) != 1:
+            printl(c.bold + "Currently using theme " + c.name + '\n')
+            printl(c.bold + "Available themes:\n")
+            for i in color_classes.keys():
+                printl(c.bold + "\t* " + i + '\n')
+        elif p[0] not in color_classes.keys():
+            return "The specified theme is not available"
+        else:
+            c = color_classes[p[0]]()
+            return "Changed theme to " + p[0]
+
+    elif cmd == 'close':            # close a connection
+        p = params.split()
+        if len(p) != 1:
+            return 'Error parsing command'
+        email = nick2email(p[0])
+        if not email:
+            return 'Unknown nick (%s)' % p[0]
+        if not m.users[email].sbd:
+            return 'No socket opened for %s' % p[0]
+        desc = str(m.users[email].sbd)
+        m.close(m.users[email].sbd)
+        return 'Closed socket %s' % desc
+
+    elif cmd == 'privacy':          # set privacy mode
+        p = params.split()
+        if len(p) != 2:
+            return 'Error parsing command'
+        try:
+            public = int(p[0])
+            auth = int(p[1])
+            if public not in (0, 1) or auth not in (0, 1):
+                return 'Error: both parameters must be 1 or 0'
+        except:
+            return 'Error: both parameters must be 1 or 0'
+        m.privacy(public, auth)
+
+    elif cmd == 'lignore':          # ignore a user locally
+        p = params.split()
+        if len(p) == 0:
+            printl(c.bold + 'Locally ignored users\n')
+            for e in ignored:
+                printl(email2nick(e) + ' (' + e + ')\n')
+            return ''
+        email = nick2email(p[0])
+        if email in ignored:
+            return 'User is already being locally ignored'
+        ignored.append(email)
+        return 'User is now being locally ignored'
+
+    elif cmd == 'lunignore':        # unignore a locally ignored user
+        p = params.split()
+        if len(p) == 0:
+            return 'Error parsing command'
+        email = nick2email(p[0])
+        if email not in ignored:
+            return 'User is not being locally ignored'
+        ignored.remove(email)
+        return 'User is no longer locally ignored'
+
+    elif cmd == 'block':
+        p = params.split()
+        if len(p) == 0:
+            return 'Error parsing command'
+        email = nick2email(p[0])
+        m.userblock(email)
+        return 'User %s blocked' % email
+
+    elif cmd == 'unblock':
+        p = params.split()
+        if len(p) == 0:
+            return 'Error parsing command'
+        email = nick2email(p[0])
+        m.userunblock(email)
+        return 'User %s unblocked' % email
+
+    elif cmd == 'add':              # add a user
+        p = params.split()
+        if   len(p) == 0:
+            return 'Error parsing command'
+        elif len(p) == 1:
+            email = nick = p[0]
+            gid = '0'
+        elif len(p) == 2:
+            email = p[0]
+            nick = p[1]
+            gid = '0'
+        else:
+            email = p[0]
+            nick = p[1]
+            group = p[2]
+            gid = gname2gid(group)
+            if not gid: gid = group
+            if gid not in m.groups.keys():
+                return 'Unknown group'
+        m.useradd(email, nick, gid)
+
+    elif cmd == 'del':              # delete a user
+        p = params.split()
+        if len(p) != 1: return 'Error parsing command'
+        email = nick2email(p[0])
+        if not email:
+            return 'Unknown nick (%s)' % p[0]
+        m.userdel(email)
+
+    elif cmd == 'gadd':             # add a group
+        p = params.split()
+        if len(p) != 1: return 'Error parsing command'
+        m.groupadd(p[0])
+
+    elif cmd == 'gdel':             # delete a group
+        p = params.split()
+        if len(p) != 1: return 'Error parsing command'
+        gname = p[0]
+        gid = gname2gid(gname)
+        if not gid: gid = gname
+        if gid not in m.groups.keys():
+            return 'Unknown group'
+        for e in m.users.keys():
+            u = m.users[e]
+            if u.gid == gid:
+                printl('User %s (%s) will be deleted\n' % \
+                        (u.nick, e), bold = 1)
+        m.groupdel(gid)
+
+    elif cmd == 'gren':             # rename a group
+        p = params.split()
+        if len(p) != 2: return 'Error parsing command'
+        newname = p[1]
+        origname = p[0]
+        gid = gname2gid(origname)
+        if not gid: gid = origname
+        if gid not in m.groups.keys():
+            return 'Unknown group'
+        m.groupren(gid, newname)
+
+    elif cmd == 'invite':           # invite a user to an existing sbd
+        p = params.split()
+        if len(p) != 3: return 'Error parsing command'
+        if p[1] != 'to': return 'Error parsing command'
+        email = nick2email(p[0])
+        if not email: email = p[0]
+        dst = nick2email(p[2])
+        if not dst: dst = p[2]
+        for i in (email, dst):
+            if i not in m.users.keys():
+                return 'User %s unknown' % i
+        dst_sbd = m.users[dst].sbd
+        if not dst_sbd:
+            return 'No current chat with user %s' % dst
+        m.invite(email, dst_sbd)
+
+    elif cmd == 'nick':             # change our nick
+        if len(params) < 1: return 'Error parsing command'
+        nick = params
+        m.change_nick(nick)
+
+    elif cmd == 'info':             # user info
+        p = params.split()
+        if len(p) != 1:
+            out = ''
+            out += c.bold + 'Info for ' + m.email + '\n'
+            out += c.bold + 'Nick:\t\t' + c.normal + m.nick + '\n'
+            out += c.bold + 'Status:\t\t' \
+                    + c.normal + msnlib.reverse_status[m.status] + '\n'
+            out += c.bold + 'Home phone:\t' + c.normal + str(m.homep) + '\n'
+            out += c.bold + 'Work phone:\t' + c.normal + str(m.workp) + '\n'
+            out += c.bold + 'Mobile phone:\t' + c.normal + str(m.mobilep) + '\n'
+            out += c.bold + 'Users in contact list: ' + str(len(m.users)) + '\n'
+            out += c.bold + 'Users in reverse list: ' + str(len(m.reverse)) + '\n'
+            out += c.bold + 'Notification server: ' + c.normal + str(m) + '\n'
+            if m.sb_fds:
+                out += c.bold + 'Switchboard connections:\n'
+                for i in m.sb_fds:
+                    out += c.bold + '\tSB: ' + c.normal + str(i) + '\n'
+            printl(out)
+        else:
+            email = nick2email(p[0])
+            if not email:
+                return 'Unknown nick (%s)' % str(p[0])
+            print_user_info(email)
+
+    elif cmd == 'sync':             # manual sync
+        m.sync()
+
+    elif cmd == 'h':                # show history
+        printl('Incoming Message History (last %d messages)\n' \
+                % config['history size'], c.green, 1)
+        for i in history_ring:
+            rtime = i[0]
+            email = i[1]
+            msg = i[2]
+            print_inc_msg(email, msg, quiet = 1, ptime = 1, recvtime = rtime)
+
+    # send a message
+    elif cmd == 'm' or cmd == 'msg' or cmd == 'r' or cmd == 'a':
+        if cmd == 'm' or cmd == 'msg':
+            p = params.split()
+            if len(p) < 1:
+                return 'Please enter a nick and a message'
+            nick = p[0]
+            email = nick2email(nick)
+            # begin the message content after the nick
+            begin = len(nick + ' ')
+            msg = params[begin:]
+        elif cmd == 'r':
+            email = last_received
+            nick = email2nick(email)
+            if not nick: nick = email
+            msg = params
+        elif cmd == 'a':
+            email = last_sent
+            nick = email2nick(email)
+            if not nick: nick = email
+            msg = params
+        if not email:
+            if cmd == 'a': return 'Please write a message first'
+            if cmd == 'r': return 'Please reply a message first'
+            else: return 'Unknown nick %s' % str(p[0])
+        if m.users[email].status == 'FLN' and not m.users[email].sbd:
+            return 'Unable to send message: User is offline'
+        if (m.status == 'FLN' or m.status == 'HDN') and not m.users[email].sbd:
+            return 'Unable to send message: Not allowed when offline'
+
+        r = m.sendmsg(email, msg)
+        last_sent = email
+        if r == 1:
+            return 'Message for %s queued for delivery' % nick
+        elif r == 2:
+            print_out_msg(nick, msg)
+            if len(m.users[email].sbd.emails) > 1:
+                log_msg(m.email, 'out', msg, \
+                        users = m.users[email].sbd.emails)
+            else:
+                log_msg(email, 'out', msg)
+        elif r == -2:
+            return 'Message too big'
+        else:
+            return 'Error %d sending message' % r
+
+    elif cmd == 'help' or cmd == '?':
+        return help
+    else:
+        return 'Unknown command, type "help" for help'
+
+    return ''
+
+
+
+#
+# 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]
+    nick = md.users[email].nick
+    ctime = time.strftime('%I:%M:%S%p', now())
+    printl('\r%s ' % ctime, c.blue)
+    printl(nick, c.blue, 1)
+    printl(' changed status to ', c.magenta)
+    printl('%s\n' % status, c.magenta, 1)
+    log_msg(email, 'status', 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 = params.split(' ')
+    email = t[0]
+    nick = md.users[email].nick
+    ctime = time.strftime('%I:%M:%S%p', now())
+    printl('\r%s ' % ctime, c.blue)
+    printl(nick, c.blue, 1)
+    printl(' changed status to ', c.magenta)
+    printl('%s\n' % status, c.magenta, 1)
+    log_msg(email, 'status', status)
+    msncb.cb_nln(md, type, tid, params)
+m.cb.nln = cb_nln
+
+def cb_fln(md, type, tid, params):
+    email = tid
+    nick = md.users[email].nick
+    ctime = time.strftime('%I:%M:%S%p', now())
+    printl('\r%s ' % ctime, c.blue)
+    printl(nick, c.blue, 1)
+    printl(' disconnected\n', c.magenta)
+    u = m.users[email]
+    if u.sbd and u.sbd.msgqueue:
+        printl(c.bold + "The following messages for " + nick + " will be discarded:\n")
+        for msg in u.sbd.msgqueue:
+            printl(c.bold + '\t>>> ' + c.normal + msg + '\n')
+    log_msg(email, 'status', 'disconnect')
+    msncb.cb_fln(md, type, tid, params)
+m.cb.fln = cb_fln
+
+# server disconnect
+def cb_out(md, type, tid, params):
+    printl('\rServer sent disconnect (probably you logged in somewhere else)\n', c.green, 1)
+    msncb.cb_out(md, type, tid, params)
+m.cb.out = cb_out
+
+def cb_bye(md, type, tid, params, sbd):
+    email = tid
+    if email != sbd.emails[0]:
+        nick = email2nick(email)
+        if not nick: nick = email
+        first_nick = email2nick(sbd.emails[0])
+        if not first_nick: first_nick = sbd.emails[0]
+        printl('\rUser %s left the chat with %s\n' % (nick, first_nick), c.green, 1)
+        log_msg(email, 'multi', 'left', users = sbd.emails)
+    msncb.cb_bye(md, type, tid, params, sbd)
+m.cb.bye = cb_bye
+
+
+# message
+def cb_msg(md, type, tid, params, sbd):
+    global last_received
+    t = tid.split(' ')
+    email = t[0]
+
+    # parse
+    lines = params.split('\n')
+    headers = {}
+    eoh = 0
+    for i in lines:
+        # end of headers
+        if i == '\r':
+            break
+        tv = i.split(':', 1)
+        type = tv[0]
+        value = tv[1].strip()
+        headers[type] = value
+        eoh += 1
+    eoh +=1
+
+    # handle special hotmail messages
+    if email == 'Hotmail':
+        if not headers.has_key('Content-Type'):
+            return
+        hotmail_info = {}
+
+        # parse the body
+        for i in lines:
+            i = i.strip()
+            if not i:
+                continue
+            tv = i.split(':', 1)
+            type = tv[0]
+            value = tv[1].strip()
+            hotmail_info[type] = value
+
+        msnlib.debug(params)
+        if headers['Content-Type'] == 'text/x-msmsgsinitialemailnotification; charset=UTF-8':
+            newmsgs = int(hotmail_info['Inbox-Unread'])
+            if not newmsgs:
+                return
+            printl('\rYou have %s unread email(s)' % str(newmsgs) \
+                    + ' in your Hotmail account\n', c.green, 1)
+        elif headers['Content-Type'] == 'text/x-msmsgsemailnotification; charset=UTF-8':
+            from_name = hotmail_info['From']
+            from_addr = hotmail_info['From-Addr']
+            subject = hotmail_info['Subject']
+            printl('\rYou have just received an email in your' + \
+                    ' Hotmail account:\n', c.green, 1)
+            printl('\r\tFrom: %s (%s)\n' % (from_name, from_addr),
+                    c.green, 1)
+            printl('\r\tSubject: %s\n' % subject, c.green, 1)
+        return
+
+    if headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-msmsgscontrol':
+        # the typing notices
+        nick = email2nick(email)
+        if not nick: nick = email
+        if not m.users[email].priv.has_key('typing'):
+            m.users[email].priv['typing'] = 0
+        if not m.users[email].priv['typing'] and email not in ignored:
+            printl('\r')
+            ctime = time.strftime('%I:%M:%S%p', now())
+            printl('%s ' % ctime, c.blue)
+            printl('%s' % nick, c.cyan, 1)
+            printl(' is typing\n', c.magenta)
+        m.users[email].priv['typing'] = time.time()
+    elif headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-clientcaps':
+        # ignore the x-clientcaps messages generated from gaim
+        pass
+    else:
+        # messages
+        m.users[email].priv['typing'] = 0
+        printl('\r')
+        print_inc_msg(email, lines, eoh)
+        if len(sbd.emails) > 1:
+            log_msg(email, 'in', string.join(lines[eoh:], '\n'), \
+                    users = sbd.emails)
+        else:
+            log_msg(email, 'in', string.join(lines[eoh:], '\n'))
+
+        # append the message to the history, keeping it below the configured limit
+        if len(history_ring) > config['history size']:
+            del(history_ring[0])
+        history_ring.append((time.time(), email, lines[eoh:]))
+
+    last_received = email
+    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
+    nick = email2nick(email)
+    if not nick: nick = email
+    if sbd.emails and email != sbd.emails[0]:
+        first_nick = email2nick(sbd.emails[0])
+        if not first_nick: first_nick = sbd.emails[0]
+        printl('\rUser %s has joined the chat with %s\n' % \
+                (nick, first_nick), c.green, 1)
+        log_msg(email, 'multi', 'join', \
+                users = sbd.emails + [email])
+    elif len(sbd.msgqueue) > 0:
+        printl('\rFlushing messages for %s:\n' % nick, c.green, 1)
+        for msg in sbd.msgqueue:
+            print_out_msg(nick, msg)
+            printl('\n')
+            log_msg(email, 'out', msg)
+    msncb.cb_joi(md, type, tid, params, sbd)
+m.cb.joi = cb_joi
+
+def cb_iro(md, type, tid, params, sbd):
+    p = params.split(' ')
+    uid, ucount, email, realnick = p
+    nick = email2nick(email)
+    if not nick: nick = email
+
+    if ucount == '1':
+        # do nothing if we only have one participant
+        pass
+    else:
+        first_nick = email2nick(sbd.emails[0])
+        if not first_nick: first_nick = sbd.emails[0]
+        # print a special message for the first user
+        if uid == '1':
+            printl('\rUser %s has invited us to a multi-user chat\n' % \
+                    first_nick, c.green, 1)
+        else:
+            printl('\rUser %s has joined the chat with %s\n' % \
+                    (nick, first_nick), c.green, 1)
+            log_msg(email, 'multi', 'join', \
+                    users = sbd.emails + [email])
+    msncb.cb_iro(md, type, tid, params, sbd)
+m.cb.iro = cb_iro
+
+# server errors
+def cb_err(md, errno, params):
+    if not msncb.error_table.has_key(errno):
+        desc = 'Unknown'
+    else:
+        desc = msncb.error_table[errno]
+    desc = '\rServer sent error %d: %s\n' % (errno, desc)
+    perror(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]
+        nick = urllib.unquote(t[3])
+    if type == 'RL':
+        out = '\r' + c.blue + c.bold + ('%s (%s) ' % (email, nick)) \
+                + c.magenta + 'has added you to his contact list\n'
+        printl(out)
+        beep()
+    elif type == 'FL':
+        out = '\r' + c.blue + c.bold + ('%s (%s) ' % (email, nick)) \
+                + c.magenta + 'has been added to your contact list\n'
+        printl(out)
+    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':
+        out = '\r' + c.blue + c.bold + email + ' ' + c.magenta \
+                + 'has removed you from his contact list\n'
+        printl(out)
+        beep()
+    elif type == 'FL':
+        out = '\r' + c.blue + c.bold + email + ' ' + c.magenta \
+                + 'has been removed from your contact list\n'
+        printl(out)
+    msncb.cb_rem(md, type, tid, params)
+m.cb.rem = cb_rem
+
+def cb_adg(md, type, tid, params):
+    t = params.split(' ')
+    lver, name, gid = t[0:3]
+    name = urllib.unquote(name)
+    out = '\r' + c.magenta + 'Group '
+    out += c.blue + c.bold + '%s (%s)' % (name, gid) + c.clear
+    out += c.magenta + ' has been added\n'
+    printl(out)
+    msncb.cb_adg(md, type, tid, params)
+m.cb.adg = cb_adg
+
+def cb_rmg(md, type, tid, params):
+    t = params.split(' ')
+    lver, gid = t[0:2]
+    name = md.groups[gid]
+    out = '\r' + c.magenta + 'Group '
+    out += c.blue + c.bold + '%s (%s)' % (name, gid) + c.clear
+    out += c.magenta + ' has been removed\n'
+    printl(out)
+    msncb.cb_rmg(md, type, tid, params)
+m.cb.rmg = cb_rmg
+
+def cb_reg(md, type, tid, params):
+    t = params.split(' ')
+    gid = t[1]
+    origname = md.groups[gid]
+    origname = urllib.unquote(origname)
+    newname = t[2]
+    newname = urllib.unquote(newname)
+    out = '\r' + c.magenta + 'Group '
+    out += c.blue + c.bold + '%s (%s)' % (origname, gid) + c.clear
+    out += c.magenta + ' has been renamed to '
+    out += c.blue + c.bold + '%s' % newname + '\n'
+    printl(out)
+    msncb.cb_reg(md, type, tid, params)
+m.cb.reg = cb_reg
+
+
+
+#
+# now the real thing
+#
+printl('* MSN Client (3.2) *\n', c.yellow, 1)
+
+# first, the configuration
+printl('Loading config... ', c.green, 1)
+if len(sys.argv) > 1:
+    # first, try the arg as file
+    config = get_config(sys.argv[1])
+    if not config:
+        # then, as the profile
+        profile = sys.argv[1]
+        file = os.path.join(os.environ['HOME'],'.msn','msnrc-' + profile)
+        config = get_config(file)
+else:
+    profile = None
+    configfilename = os.path.join(os.environ['HOME'],'.msn','msnrc')
+    print configfilename
+    config = get_config(configfilename)
+
+if not config:
+    perror('Error opening config file (%s), try running "msnsetup"\n' % file)
+    quit(1)
+
+config['profile'] = profile
+
+# set the mandatory values
+if config.has_key('email'):
+    m.email = config['email']
+else:
+    perror('Error: email not specified in config file\n')
+    quit(1)
+
+if config.has_key('password'):
+    m.pwd = config['password']
+else:
+    # we ask for the password, setting, if necesary, blocking IO over
+    # stdin (which was disabled by the terminal handling stuff)
+    import getpass
+    try: fcntl.fcntl(stdinfd, fcntl.F_SETFL, os.O_SYNC)
+    except: pass
+    m.pwd = getpass.getpass(c.green + c.bold + "\nPassword: ")
+    try: fcntl.fcntl(stdinfd, fcntl.F_SETFL, os.O_NONBLOCK)
+    except: pass
+
+# and the optional ones, setting the defaults if not present
+# history size
+if not config.has_key('history size'):
+    config['history size'] = 10
+else:
+    try:
+        config['history size'] = int(config['history size'])
+    except:
+        perror('history size must be integer, using default\n')
+        config['history size'] = 10
+
+# input history size
+if not config.has_key('input history size'):
+    config['input history size'] = 10
+else:
+    try:
+        config['history size'] = int(config['history size'])
+    except:
+        error('input history size must be integer, using default\n')
+        config['input history size'] = 10
+
+# initial status
+###if not config.has_key('initial status'):
+###    config['initial status'] = 'online'
+##elif config['initial status'] not in msnlib.status_table.keys():
+##    perror('unknown initial status, using default\n')
+##    config['initial status'] = 'online'
+config['initial status'] = 'online'
+
+# debug
+if not config.has_key('debug'):
+    config['debug'] = 0
+elif config['debug'] != 'yes':
+    config['debug'] = 0
+
+# colors
+if not config.has_key('color theme'):
+    config['color theme'] = 'default'
+try:
+    c = color_classes[config['color theme']]()
+except:
+    perror("Unknown color theme, type 'color' for help\n")
+
+# log history
+if not config.has_key('log history'):
+    config['log history'] = 1
+elif config['log history'] != 'yes':
+    config['log history'] = 0
+
+# history directory
+if not config.has_key('history directory'):
+    config['history directory'] = os.path.join(os.environ['HOME'],'.msn','history')
+
+# auto away time
+if not config.has_key('auto away'):
+    config['auto away'] = 0
+else:
+    try:
+        config['auto away'] = int(config['auto away'])
+    except:
+        perror('auto away must be integer, using default\n')
+        config['auto away'] = 0
+if config['auto away'] and config['auto away'] < 60:    # sanity check
+    perror('Warning: auto away time was set to less than a minute!\n')
+
+# encoding
+if not config.has_key('encoding'):
+    # we use posix standard way of defining standard locale, or just fall
+    # back to iso-8859-1; see locale(7) for more details
+    if os.environ.has_key('LC_ALL') and os.environ['LC_ALL']:
+        config['encoding'] = os.environ['LC_ALL']
+    elif os.environ.has_key('LANG') and os.environ['LANG']:
+        config['encoding'] = os.environ['LANG']
+    else:
+        config['encoding'] = 'iso-8859-1'
+m.encoding = config['encoding']
+
+printl('done\n', c.green, 1)
+
+
+# set or void the debug
+if not config['debug']:
+    msnlib.debug = null
+    msncb.debug = null
+
+# debug some internal variables
+msnlib.debug("Terminal Handling: %d" % use_termios)
+msnlib.debug("Terminal Size: %s" % str(winsize))
+
+# login to msn
+printl('Logging in... ', c.green, 1)
+try:
+    m.login()
+    printl('done\n', c.green, 1)
+except 'AuthError', info:
+    print info
+    errno = int(info[0])
+    if not msncb.error_table.has_key(errno):
+        desc = 'Unknown'
+    else:
+        desc = msncb.error_table[errno]
+    perror('Error: %s (%s)\n' % (desc, errno))
+    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)
+
+
+# call sync to get the lists and refresh
+printl('Syncing... ', c.green, 1)
+if m.sync():
+    printl('done\n', c.green, 1)
+else:
+    perror('Error syncing users\n')
+
+
+if m.change_status(config['initial status']):
+    printl('Status set to %s\n' % config['initial status'], c.green, 1)
+else:
+    perror('Error setting status: unknown status %s\n' % config['initial status'])
+
+
+# global variables
+history_ring = []       # history buffer
+last_sent = ''          # email of the last person we sent a message to
+last_received = ''      # email of the last person we received a message from
+ignored = []            # people being locally ignored
+
+# auto-away
+timeout = config['auto away']
+if not timeout:
+    timeout = None          # must be None, not 0 because of select() semantics
+auto_away = 0
+
+# loop
+redraw_cli()
+while 1:
+    if win32:
+        if msvcrt.kbhit():
+            stdin_read(msvcrt.getch())
+    else:
+        fds,_,_ = select.select([sys.stdin], [], [], 0.2)
+        if fds<>[]:
+            stdin_read()
+    if not m.poll():
+        quit(1)
+    redraw_cli()
+
+"""
+        fds = m.pollable()
+        infd = fds[0]
+        outfd = fds[1]
+        infd.append(sys.stdin)
+        try:
+                fds = select.select(infd, outfd, [], timeout)
+        except KeyboardInterrupt:
+                quit()
+
+        if timeout and len(fds[0] + fds[1]) == 0:
+                # timeout, set auto away
+                if m.status == 'NLN':
+                        m.change_status('away')
+                        auto_away = 1
+                        printl('\rAutomatically changing status to away\n', c.green, 1)
+        for i in fds[0] + fds[1]:               # see msnlib.msnd.pollable.__doc__
+                if i == sys.stdin:
+                        # auto away revival
+                        if auto_away:
+                                auto_away = 0
+                                m.change_status('online')
+                                printl('\rAutomatically changing status back to online\n', c.green, 1)
+                        # read from stdin
+                        stdin_read()
+                else:
+                        try:
+                                m.read(i)
+                        except ('SocketError', socket.error), err:
+                                if i != m:
+                                        if i.msgqueue:
+                                                nick = email2nick(i.emails[0])
+                                                printl("\rConnection with %s closed - the following messages couldn't be sent:\n" % (nick), c.green, 1)
+                                                for msg in i.msgqueue:
+                                                        printl(c.bold + '\t>>> ' + c.normal + msg + '\n')
+                                        m.close(i)
+                                else:
+                                        printl('\nMain socket closed (%s)\n' % str(err), c.red)
+                                        quit(1)
+                        # always redraw after network event
+                        redraw_cli()
+"""
diff -pruN msnlib-3.3/msn_s.py aw/msnlib-3.3/msn_s.py
--- msnlib-3.3/msn_s.py	1969-12-31 21:00:00.000000000 -0300
+++ aw/msnlib-3.3/msn_s.py	2004-06-18 00:23:37.000000000 -0300
@@ -0,0 +1,542 @@
+#                                                           #
+# $Id: msn_s.py,v 1.28 2004/03/23 02:22:07 aweil Exp $      #
+#                                                           #
+#
+#   2004.02.17   23hs. first 'working' version can send and
+#                       and receive messages.
+#   2004 03 14  23hs. ssl login works now using ahttplib, now
+#                       we are free!
+#   2004 03 15  01hs. raw_transport works again!
+#
+#
+import socket
+import select
+import errno        #for check of connect_ex return's value
+import urllib       #for making POST parameters
+import time         #for our poll timers
+import ahttplib     #async http..
+import async2
+
+extra_compat = True #setting to false should result in minimun required
+					#information transfers
+
+ahttplib.enable_log = True
+
+class transport:
+	""" Abstract transport class """
+	def fileno( self ):
+		raise "Must overwrite"
+
+	def isclosed( self ):
+		raise "Must overwrite"
+
+	def close( self ):
+		raise "Must overwrite"
+
+	def readline( self ):
+		"""Blocking until we have a complete line or a socket error"""
+		raise "Must overwrite"
+
+	def recv( self, what ):
+		"""Read 'what' amount of data from socket"""
+		raise "Must overwrite"
+
+	def send( self, what ):
+		raise "Must overwrite"
+
+	def setblocking( self, newstate ):
+		raise "Must overwrite"
+
+	def connect ( self, host, servertype='NS' ):
+		raise "Must overwrite"
+
+	def connect_ex ( self, host ):
+		raise "Must overwrite"
+
+	def process ( self, timeout=0 ):
+		"""
+			Check for changes
+			Timeout is the 'select' timeout and framework is supposed to poll
+			this function
+
+			Returns:
+				True if we have new data having to receive a recv()/readline()
+					soon.
+		"""
+		raise "Must overwrite"
+
+	## Util functions
+	def sanitize( self, somestr ):
+		return somestr.strip().split(' ')
+
+
+class raw_transport(transport):
+	def __init__( self ):
+		# socket fd
+		self.fd = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
+		self.connecting = False
+		self.isclosed = True
+
+	def isclosed( self ):
+		# XXX Fix this. Seems to be fixed now.
+		#return False
+		return self.isclosed
+
+	def connect ( self, host, servertype='NS' ):
+		#simply ignore servertype..
+		self.fd.connect( host )
+
+	def connect_ex ( self, host ):
+		self.setblocking(0)
+		e =self.fd.connect_ex( host )
+		#print 'connect ex returns : ', errno.errorcode[e]
+		if not (e in [0, errno.EISCONN]):
+			self.connecting = True
+			self.isclosed = False
+
+	def setblocking( self, newstate ):
+		self.fd.setblocking(newstate)
+
+	def __repr__( self ):
+		return '(raw '+str(self.fd)+')'
+
+	def fileno( self ):
+		return self.fd.fileno()
+
+	def readline( self ):
+		# cheap and dirty readline, FIXME
+		buf = ''
+		c = self.fd.recv(1)
+		while c != '\n' and c != '':
+			buf = buf + c
+			c = self.fd.recv(1)
+		if c == '':
+			raise 'SocketError'
+		return self.sanitize(buf)
+
+	def recv ( self, what ):
+		buf = self.fd.recv( what )
+		return buf
+
+	def send ( self, what ):
+		return self.fd.send( what )
+
+	def close( self ):
+		self.fd.close()
+		self.isclosed = True
+
+	def process ( self ):
+		## TODO check this.
+		if self.connecting:
+			wfds = [self.fd]
+		else:
+			wfds = []
+		a, b, c = select.select([self.fd],wfds,wfds,0)
+		if self.connecting and len(b)+len(c)>0:
+			self.connecting = False
+			return True
+		if self.connecting and len(a)>0:
+			self.connecting = False
+		return len(a)>0
+
+
+class _http_session:
+	"""
+	internal http transport class
+
+	has knowledge about our connection, proxy, http-gateway, session-id and
+	http-status
+	"""
+	IDLE = 0
+	CONNECTING = 1
+	CONNECTED = 2
+
+	MIN_POLL_IVAL = 0.5         #for our syncronic poll
+
+	def __init__( self, gateway=('http','gateway.messenger.hotmail.com', 80), \
+						proxyserver=None):
+		""" Use a connection using this http-gateway """
+		#self.gw = ('http','gateway.messenger.hotmail.com', 80)
+		self.gw = gateway
+		self.proxy = proxyserver
+		self.sessid = None
+		self._status = _http_session.IDLE
+		self.buf=''
+
+	def _get_surl(self, poll=False):
+		"""build our request line"""
+		if self.params==None:
+			if self.sessid==None:
+				raise 'Problemas aca'
+			self.params = { 'SessionID':self.sessid }
+			if poll:
+				self.params['Action']='poll'
+			self.sessid = None #clean transport id so we don't repeat it
+		params = urllib.urlencode(self.params)
+		url_base = '/gateway/gateway.dll?%s'%params
+		self.params = None
+		return url_base
+
+	def status( self ):
+		return self._status
+
+	def isconnected( self ):
+		return self.status()==_http_session.CONNECTED
+
+	def __connect( self, params=None ):
+		""" This is the internal connect function """
+		global extra_compat
+		#print '\t\t>>>>>CONNECTING<<<<<<'
+		if self.proxy==None:
+			con = ahttplib.AHttpConnection( self.gw, http_version='1.1')
+		else:
+			con = ahttplib.AProxyHttpConnection( self.gw, proxy=self.proxy, http_version='1.1')
+		sheaders = {'Accept-Encoding':'identity;q=1.0,*;q=0', \
+				'Pragma':'no-cache', \
+				'Accept':'*/*' } #'Connection':'close', #'Host':self.server,
+		if extra_compat:
+			sheaders['User-Agent']='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; TUCOWS; MSN Messenger 6.0.0268)'
+			sheaders['Accept-Language']='en-us'
+			sheaders['Accept']='*/*'
+		#save params for later use
+		self.params = params
+		self.setheaders = sheaders
+		self._status=_http_session.CONNECTED #we aren't connected but..
+		return con
+
+	def poll ( self, timeout=0, verbose=False ):
+		async2.looponce(timeout)
+		if self.con==None:
+			raise Exception('disconnected')
+		if self.isconnected():
+			response = self.con.get_response()
+			if response==None:
+				raise Exception('incomplete')
+			self.parse_response(response)
+			#print '\r\rresponse is : ',response
+			#print 'response buffer is : ',repr(response.buffer)
+			headers = response.headers
+			self.buf = response.buffer
+		else:
+			raise Exception('disconnected')
+
+	def read(self):
+		_buf = self.buf
+		self.buf = ''
+		return _buf
+
+	def connect( self, address=None, servertype='NS'):
+		"""
+			servertype is the kind of server to connect to:'NS' or 'SB'
+		"""
+		self.servertype = servertype
+		if address==None:
+			self.server=self.gw[1]
+			self.port = 80
+			self.con = self.__connect()
+		else:
+			self.server=address[1]
+			self.port=address[2]
+			self.con = self.__connect( { 'Action':'open', \
+									'Server':self.servertype, \
+									'IP':self.server }) #'messenger.hotmail.com'
+		# wait for connection..?
+		self.con.connect()
+
+	def close( self ):
+		if self.con <> None:
+			self.con.close()
+		self.con=None
+		self._status = _http_session.IDLE
+
+	def can_send(self):
+		return (self.sessid<>None) or (self.params<>None)
+
+	def send( self, req ):
+		"""Before we can send, we must check our state"""
+		if not self.can_send():
+			raise 'CANT SEND REQUEST NOW!'
+		return self._send(req)
+
+	def _send( self, req ):
+		"""Real send request function"""
+		global extra_compat
+		#print self.isconnected()
+		tpolling = req==''
+		#print '\r\r\tREQUEST: ',self.sessid,' -> ',repr(req)
+
+		headers = self.setheaders
+		headers['Content-Type']='application/x-msn-messenger'
+		headers['Content-Length']=str(len(req))
+		if extra_compat:
+			headers['Proxy-Connection']='Keep-Alive'
+			headers['Connection']='Keep-Alive'
+
+		url = self._get_surl(poll=tpolling)
+		self.con.request( url, method='POST', data=req, headers=headers, \
+							version='1.1')
+
+	def parse_response (self, response):
+		_session, _gw = response.headers['X-MSN-Messenger'].split(';')
+		self.sessid = _session.split('=')[1]
+		#print ' SESS ID: ',repr(self.sessid)
+		_server = _gw.split('=')[1]
+		self.gw = (self.gw[0], _server, self.gw[2])
+		if _server<>self.server:
+			## Switch server
+			self.server = _server
+			self.connect(servertype=self.servertype)
+		## we should check here if the connection
+		## is closed or something
+
+
+BUSY = 'busy'
+IDLE = 'idle'
+
+class http_transport(transport):
+	"""
+	http transport class
+
+	it uses http-transport instances to provide transport interface
+
+	Notes:
+
+	 1) It seems some request should be sent complete in one message, for
+		example, QRY, should be in one request's body:
+
+			QRY 7
+			45789....9346
+
+		send('') pushes diferent lines, but our internal _send() tries to send
+		all it can togheter.
+
+	 2) One thing that may be usefull is a minpoll time. So, we catch all the
+		request made in intervals of minpoll time, and after that, send all of
+		them togheter.
+
+	 3) Also it seems to be the place to accumulate requests instead of the
+		_http_session (the session) itself, because, has to deal with transport
+		id numbers, and is easy that _http_session can assign them directly.
+
+	"""
+	def __init__( self, proxyserver=None):
+		global IDLE
+		self.con = None
+		self.__nextclean = False
+		self.buf = ''
+		self.__lastpoll=time.time()
+		self.pollinterval = 6
+		self.proxyserver = proxyserver
+		self.status = BUSY
+		self.sendq = []
+		send.closing = False
+
+	def __wait(self, func, *args):
+		"""
+		Helper function.
+
+		Keeps looping until func does not raises an exception and then returns
+		its result.
+		"""
+		cont = True
+		while cont:
+			try:
+				self.process()
+				a = func(self, *args)
+				cont = False
+			except Exception, e:
+				pass
+		return a
+
+	def isclosed( self ):
+		return self.con==None
+
+	def __repr__( self ):
+		return '(http)'
+
+	def fileno(self):
+		"""Useful for select()"""
+		return
+		#return self.con.sock.fileno()
+
+	def setblocking( self, newstate ):
+		pass
+
+	def recv( self, rlen ):
+		""" Returns the next msg unread"""
+		#print '\r\r\t\tin msn_s transport recv()\r\r'
+		r = self.buf[:rlen]
+		self.buf = self.buf[rlen:]
+		return r
+
+	def readline( self ):
+		"""blocking version"""
+		def _readline(self):
+			"""
+			Retrives a line from our received buffer if we can, else, raise an
+			exception.
+			"""
+			if len(self.buf)==0:
+				raise Exception('SocketError')
+			idx = self.buf.find('\r\n')
+			if idx==-1:
+				idx = len(self.buf)
+				raise Exception('SocketError')
+			line = self.buf[:idx]
+			self.buf=self.buf[idx+2:]
+			return self.sanitize(line)
+
+		return self.__wait(_readline)
+
+	def send(self, msg):
+		""" buffered send """
+		self.sendq.append(msg)
+
+	def connect_ex ( self, host ):
+		raise 'not implemented yet'
+
+	def connect( self, hosttuple=None, gwport=80, servertype='NS' ):
+		""" syncronic connect """
+		global IDLE
+		#for simplicity no proxy now..
+		_host = ('http',hosttuple[0],hosttuple[1])
+		if self.con==None:
+			if self.proxyserver<>None:
+				self.con = _http_session ( proxyserver = self.proxyserver)
+			else:
+				self.con = _http_session ( )
+
+			self.con.connect( _host, servertype=servertype )
+			self.status = IDLE
+			self.__lastpoll=time.time()
+		else:
+			#print '\r\rcalling again to connect!!\r'
+			pass
+
+	def close( self ):
+		if self.con<>None:
+			self.closing = True
+
+	def _chkclose(self):
+		if self.closing and len(self.sendq)==0:
+			self.con.close()
+			self.con = None
+			self.closing=False
+
+	def __poll( self ):
+		"""
+		This function should emit a keepalive/poll/whatever at self.pollinterval
+		time from last request
+		"""
+		if time.time()-self.__lastpoll <= self.pollinterval:
+			return
+		self.pollinterval=3
+		if self.status==IDLE and len(self.sendq)==0:
+			#print 'POLLING!! sessid : ',self.con.sessid
+			self.send('')
+		self._chkclose()
+
+	def _send( self ):
+		"""
+		Internal send next request function.
+
+		Pops a request from our queue and send it.
+		Notes:
+			- Checks that we can make the request.
+			- Keeps __lastpoll time updated.
+			- Maybe usefull that we recheck keepaliveness here before sending a
+				keepalive request
+		"""
+		global BUSY, IDLE
+		#if len(self.sendq)>0 and not self.con.can_send():
+		#    print '\r -> REQUESTS WAITING.. :-( <-'
+		if len(self.sendq)>0 and self.con.can_send():
+			self.status = BUSY
+			# put all requests togheter, this, also, nullifies the keepalive
+			# requests, except if there are no other requests.
+			req = ''
+			#print self.sendq
+			cont = True
+			first = True
+			while len(self.sendq)>0 and cont:
+				new = self.sendq.pop(0)
+				if not new.startswith('CHG') or first:
+					#don't concatenate CHG messages!
+					req+=new
+				else:
+					cont = False
+					self.sendq = [new]+self.sendq
+				first = False
+
+			self.con.send(req)
+			self.__lastpoll = time.time()
+			self.status = IDLE
+
+
+	def process ( self ):
+		if self.con<>None:
+			try:
+				self.con.poll()
+				self.buf += self.con.read()
+			except:
+				pass
+			self._send()
+		self.__poll()
+		return len(self.buf)>0
+
+
+if __name__=='__main__':
+	def wait2(s):
+		cont = True
+		while cont:
+			try:
+				s.poll()
+				a = s.read()
+				cont = False
+			except Exception, e:
+				pass
+		return a
+
+	print '- connection test --------------------------------------------------'
+	#s = _http_session(proxyserver='192.168.254.254', proxyport=80)
+	s = _http_session()
+	s.connect( ('http','messenger.hotmail.com',80) )
+	s0='VER 1 MSNP8 CVR0'
+	#s0='VER 2 MSNP9 MSNP8 CVR0'
+	s.send(s0)
+	print 'readed:',wait2(s)
+	s.close()
+
+	s.connect()
+	s1='CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS aweil@speedy.com.ar'
+	#s1='CVR 3 0x0409 winnt 5.0 i386 MSNMSGR 6.0.0268 MSMSGS aweil@speedy.com.ar'
+	s.send(s1)
+	print 'readed: ',wait2(s)
+
+	print '- transport test -----------------------------------------------------'
+	s = http_transport()
+	s.connect( ('messenger.hotmail.com',80) )
+	s.send('VER 1 MSNP8 CVR0')
+	#print 'readed: ',wait(s)
+	print 'readed:',s.readline()
+
+	#s.connect( ('messenger.hotmail.com',1863) )
+	s.connect()
+	s.send('CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS aweil@speedy.com.ar')
+	#print 'readed: ',wait(s)
+	print 'readed:',s.readline()
+
+	raise 'preemp end'
+
+	print s.send('CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS aweil@speedy.com.ar\r\n')
+	#print s.send('CVR 3 0x0409 winnt 5.0 i386 MSNMSGR 6.0.0268 MSMSGS aweil@speedy.com.ar\r\n')
+	print s.send('USR 6 TWN I aweil@speedy.com.ar\r\n')
+	s.close ()
+	#print '- test with proxy@localhost ---------------------------------------'
+	#s = msn_s.http_transport('127.0.0.1',8888)
+	##s = msn_s.http_transport('127.0.0.1',3128)
+	#
+	#s.connect( ('messenger.hotmail.com',1863) )
+	#print s.send('VER 1 MSNP8 CVR0\r\n')
+	#print s.send('CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS aweil@speedy.com.ar')
+	#print s.send('USR 6 TWN I aweil@speedy.com.ar\r\n')
diff -pruN msnlib-3.3/SimpleChannel.py aw/msnlib-3.3/SimpleChannel.py
--- msnlib-3.3/SimpleChannel.py	1969-12-31 21:00:00.000000000 -0300
+++ aw/msnlib-3.3/SimpleChannel.py	2004-06-18 00:23:37.000000000 -0300
@@ -0,0 +1,106 @@
+import StringIO
+"""
+SimpleChannel. Kept simple and small..
+"""
+
+class SCAccesor:
+	"""An accesor over a SimpleChannel"""
+	def __init__(self, strtuple):
+		self.readfrom, self.writeto = strtuple
+		self.rpos = self.wpos = 0
+		
+	def _handle_rpos(self, func, args):
+		self.readfrom.seek(self.rpos)
+		ret = func(*args)
+		self.rpos = self.readfrom.tell()
+		return ret
+	
+	def _handle_wpos(self, func, args):
+		self.writeto.seek(self.wpos)
+		ret = func(*args)
+		self.wpos = self.writeto.tell()
+		return ret
+		
+	def read(self, *args):
+		return self._handle_rpos(self.readfrom.read, args)
+	
+	def readline(self, *args):
+		return self._handle_rpos(self.readfrom.readline, args)
+	
+	def write(self, *args):
+		return self._handle_wpos(self.writeto.write, args)
+	
+	def seek(self, *args):
+		#return self._handle_pos(self.file.seek, args)
+		raise 'must implement'
+	
+	def tell(self, *args):
+		#return self._keep_pos(self.file.tell, args)
+		raise 'must implement'
+	
+	def truncate(self, *args):
+		#return self._keep_pos(self.file.truncate, args)
+		raise 'must implement'
+	
+	def close(self):
+		#do something here
+		self.readfrom = self.writefrom = None
+		
+		
+class SimpleChannel:
+	"""
+	A Channel for accessing a file at two places.. as a bidireccional socket
+	"""
+	def __init__(self, twoway=False, strclass=StringIO.StringIO):
+		self.twoway = twoway
+		self.st1 = strclass()
+		if self.twoway:
+			self.st2 = strclass()
+		else:
+			self.st2 = None
+			
+	def get_reader(self):
+		return ( SCAccesor( (self.st1, self.st2) ))
+	def get_writer(self):
+		return ( SCAccesor( (self.st2, self.st1) ))
+	def likepopen(self):
+		return (self.get_reader(), self.get_writer())
+
+
+if __name__=='__main__':
+	import unittest
+	
+	class genTester(unittest.TestCase):
+		def _test_instances(self, reader, writer):
+			"""
+			This is used to check that any instance keep it's position pointer
+			"""
+			t = writer.write('hola carlos')
+			t = reader.read(5)
+			self.assertEqual(t, 'hola ')
+			t = writer.write(' -> chau')
+			t = reader.read()
+			self.assertEqual(t, 'carlos -> chau')
+	
+	
+	class testChannel(genTester):
+		"""Test Channel"""
+		def test_1(self):
+			"""Test that a channel reader/writer works as I want :-)"""
+			c = SimpleChannel()
+			r = c.get_reader()
+			w = c.get_writer()
+			self._test_instances(r,w)
+		def test_twosided(self):
+			"""Test bi-dir"""
+			c = SimpleChannel(True)
+			r = c.get_reader()
+			w = c.get_writer()
+			self._test_instances(r,w)
+			self._test_instances(w,r)
+	
+	suite = unittest.TestSuite()
+	suite.addTest(unittest.makeSuite(testChannel))
+	unittest.TextTestRunner(verbosity=2).run(suite)
+	
+	
\ No newline at end of file
