diff -ruN msnlib-d4/doc/Changelog msnlib-1.0/doc/Changelog
--- msnlib-d4/doc/Changelog	2003-01-20 13:51:24.000000000 -0300
+++ msnlib-1.0/doc/Changelog	2003-05-01 19:20:50.000000000 -0300
@@ -1,3 +1,36 @@
+01 May 03 19.20.41 - Alberto <albertogli@telpin.com.ar>
+ * tag: 1.0 tag
+
+01 May 03 19.16.39 - Alberto <albertogli@telpin.com.ar>
+ * utils: add msnbot and msncd to the utilities
+ * doc: small documentation updates
+
+23 Apr 03 18.08.25 - Alberto <albertogli@telpin.com.ar>
+ * msn: fix a very odd but important bug that could cause a protocol break by
+	miscalculating the lenght of an encoded message. Thanks to Ahilan
+	Sinnarajah for the report
+
+10 Apr 03 01.30.06 - Alberto <albertogli@telpin.com.ar>
+ * msn: implement color themes
+
+09 Apr 03 00.55.43 - Alberto <albertogli@telpin.com.ar>
+ * msn, msnsetup: implement profile support
+
+26 Mar 03 13.06.55 - Alberto <albertogli@telpin.com.ar>
+ * msn: when logging in, print the error code along with description
+
+18 Mar 03 15.45.37 - Alberto <albertogli@telpin.com.ar>
+ * msn: ask for password if not given in the configuration
+
+05 Mar 03 14.01.34 - Alberto <albertogli@telpin.com.ar>
+ * msnlib: don't send an extra new line in sendmsg
+
+16 Feb 03 16.48.08 - Alberto <albertogli@telpin.com.ar>
+ * msn: fix an exception in socket error handling
+
+20 Jan 03 14.31.43 - Alberto <albertogli@telpin.com.ar>
+ * tag: d4 tag (changelog entry not included in d4's tarballs)
+
 20 Jan 03 10.39.06 - Alberto <albertogli@telpin.com.ar>
  * doc: update url and add mailing list information
  * install: allow the user to specify an installation root directory
diff -ruN msnlib-d4/doc/commands msnlib-1.0/doc/commands
--- msnlib-d4/doc/commands	2003-01-15 11:29:46.000000000 -0300
+++ msnlib-1.0/doc/commands	2003-04-23 10:53:00.000000000 -0300
@@ -2,6 +2,8 @@
 This is a more detailed explanation of the commands provided in the client.
 A brief list is available at runtime with the 'help' command.
 
+Note that the runtime help gets updated more frequently than this one.
+
 
 status [mode]	
 	Shows the current status, or changes it to "mode", which can be one of:
@@ -52,6 +54,9 @@
 lunignore nick
 	Removes a user from the locally ignored users list.
 
+color [theme]
+	Shows the available color themes, or set the color theme to "theme".
+
 close nick
 	Closes the switchboard connection with "nick". There is no need to use
 	this command and is included just for advanced users and debugging.
diff -ruN msnlib-d4/doc/msncd_protocol msnlib-1.0/doc/msncd_protocol
--- msnlib-d4/doc/msncd_protocol	1969-12-31 21:00:00.000000000 -0300
+++ msnlib-1.0/doc/msncd_protocol	2003-04-23 09:50:33.000000000 -0300
@@ -0,0 +1,128 @@
+This is a description of the protocol used by msncd.
+
+It is still in an experimental stage as only few people dared to try it; if
+you do please let me know by sending a message to the mailing list.
+
+
+Add user:
+->	ADD nick email\n
+
+Delete user:
+->	DEL email\n
+
+Change our nick:
+->	NICK newnick\n
+
+Change the privacy behaviour:
+->	PRIV p a\n
+
+Change our status:
+->	STATUS newstatus\n
+
+Log in:
+->	LOGIN email password\n
+
+Log off:
+->	LOGOFF\n
+
+Info requests:
+->	INFO email\n
+<-	AttributeA = ValueA\n
+<-	AttributeB = ValueB\n
+<-	...
+<-	\n
+
+Send message:
+->	SENDMSG number_of_lines email\n
+->	line1\n
+->	line2\n
+->	...
+->	lineN\n
+
+Poll for events:
+->	POLL\n
+	(begin to send first message)
+<-	MSG number_of_lines end_of_header src_email\n
+<-	line1\n
+<-	line2\n
+<-	...
+<-	lineN\n
+	(and repeat for number_of_messages)
+	(then send status changes)
+<-	STCH email newstatus [number_of_messages_discarded]\n
+	(a user has added us)
+<-	UADD email\n
+	(a user has deleted us)
+<-	UDEL email\n
+	(a user was added)
+<-	ADDFL email\n
+	(a user was deleted)
+<-	DELFL email\n
+	(flushed messages)
+	MFLUSH email\n
+	(typing notifications)
+	TYPING email\n
+	(a socket was closed, type is either 'MAIN' or 'USER')
+	SCLOSE type [email number_of_messages_discarded]\n
+	(unexpected errors)
+<-	ERR code description\n
+	(finally, end the poll)
+<-	POLLEND\n
+
+Get contact list:
+->	GETCL\n
+<-	CL number_of_contacts\n
+<-	status1 email1 nick1\n
+<-	status2 email2 nick2\n
+<-	...
+<-	statusN emailN nickN\n
+
+Get reverse contact list:
+->	GETRCL\n
+	(same behaviour as GETCL)
+
+
+And I think that'd be pretty much it. Anyway, it's easily extensible.
+
+Note that:
+ * In most places, email can be exchanged with the url-encoded nick. The
+	server _always_ replies using the email
+ * The 'server' (that is, the real client, that reads from the pipe) responses
+	an 'OK\n' for most commands, or 'ERR description\n'; here they are
+	ommited for brevity.
+ * If considered necesary, a timestamp could be sent before some responses to
+	indicate time. I'm not sure about this, because polling often (like
+	250ms) has enough granularity and it doesn't represent any load, so we
+	could just avoid the overhead. Even with 1s poll time, there are no
+	problems regarding times.
+ * New pollable stuff could also be added later (files, for instance). Poll
+	responses can come in any order.
+ * This is syncronous and events get queued on the server side. Server _never_
+	issues a pipe write without a request, that's what we have POLL for.
+	This avoid a huge load of races, and the client code to avoid them.
+ * Unexpected errors also come out from POLL, specially the network ones that
+	tend to be quite async to everything else.
+
+
+So now, we start offline and then, on 'LOGIN' we connect and after we log in,
+the 'OK' response is sent. Then the client changes the status to whatever he
+wants, for instance with 'STATUS away'.
+
+The client will mostly like do a GETCL and GETRCL after that, and then start
+polling for events, which now becomes the common path. This is very efficient
+when no new events are pending (again, the common case), just:
+->	POLL\n
+<-	POLLEND\n
+
+Disconnect is as simple as 'LOGOFF'. Note that this is not the same as
+'STATUS offline', as the former closes everything and returns to the initial
+state.
+
+Much simpler than everything else (remember that most other stablished
+protocols require to have a library, a python binding and so on), it can be
+implemented quite fast in any language, and it's simple and efficient.
+
+No connection or user tracking is required on the client, no state machines,
+nothing. Just a frontend for a text protocol.
+
+
diff -ruN msnlib-d4/doc/profiles msnlib-1.0/doc/profiles
--- msnlib-d4/doc/profiles	1969-12-31 21:00:00.000000000 -0300
+++ msnlib-1.0/doc/profiles	2003-04-23 09:56:25.000000000 -0300
@@ -0,0 +1,20 @@
+
+The msn client, from release D5, supports different profiles.
+
+What this means is that now you can have several configurations under the same
+unix user in a very simple way.
+
+Just create a profile like you normally create your configuration, telling the
+setup the profile name, like:
+
+msnsetup profilename
+
+
+Then, to run msn under an specific profile, do:
+
+msn profilename
+
+and you're done.
+
+
+
diff -ruN msnlib-d4/msn msnlib-1.0/msn
--- msnlib-d4/msn	2003-01-20 13:53:10.000000000 -0300
+++ msnlib-1.0/msn	2003-05-01 19:18:47.000000000 -0300
@@ -18,16 +18,20 @@
 MSN Client
 
 This is a fully usable msn client, which also serves as reference
-implementation for msnlib.
+implementation for msnlib-based code.
 For further information refer to the documentation or the source (which is
-always preferred).
+always preferred). 
+Please direct any comments to the msnlib mailing list,
+msnlib-devel@auriga.wearlab.de.
 """
 
 #
 # colors, for nice output
 #
-class color:
+
+class color_default:
 	def __init__(self):
+		self.name =	'default'
 		self.black = 	'\x1b[0;30m'
 		self.red =	'\x1b[0;31m'
 		self.green =	'\x1b[0;32m'
@@ -40,7 +44,26 @@
 		self.bold =	'\x1b[1m'
 		self.clear =	'\x1b[J'
 
-c = color()
+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'
+
+color_classes = {
+	'default':	color_default,
+	'bw':		color_bw
+}
+c = color_classes['default']()
 
 
 #
@@ -254,7 +277,11 @@
 	if not config['log history']:
 		return
 	
-	file = config['history directory'] + '/' + email
+	if config['profile']:
+		prepend = config['profile'] + '::'
+	else:
+		prepend = ''
+	file = config['history directory'] + '/' + prepend + email
 	if not mtime:
 		mtime = time.time()
 	out = ''
@@ -603,6 +630,19 @@
 		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: 
@@ -766,6 +806,7 @@
 		r += 'info [nick]\t Shows the user information and pending messages (if any), or our personal info\n'
 		r += 'lignore [nick]\t Locally ignores the user, or display the locally ignored users list\n'
 		r += 'lunignore nick\t Removes a user from the locally ignored users list\n'
+		r += 'color [theme]\t Shows or set the color theme to "theme"\n'
 		r += 'close nick\t Closes the switchboard connection with "nick"\n'
 		r += 'config\t\t Shows the configuration\n'
 		r += 'nick newnick\t Changes your nick to "newnick"\n'
@@ -967,20 +1008,28 @@
 #
 # now the real thing
 #
-printl('* MSN Client (release D4) *\n', c.yellow, 1)
+printl('* MSN Client (1.0) *\n', c.yellow, 1)
 
 # first, the configuration
 printl('Loading config... ', c.green, 1)
 if len(sys.argv) > 1:
-	file = 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.environ['HOME'] + '/.msn/msnrc-' + profile
+		config = get_config(file)
 else:
-	file = os.environ['HOME'] + '/.msn/msnrc'
+	profile = None
+	config = get_config(os.environ['HOME'] + '/.msn/msnrc')
 
-config = get_config(file)
 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']
@@ -991,8 +1040,14 @@
 if config.has_key('password'):
 	m.pwd = config['password']
 else:
-	perror('Error: password not specified in config file\n')
-	quit(1)
+	# 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
@@ -1028,6 +1083,14 @@
 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
@@ -1085,13 +1148,12 @@
 		desc = 'Unknown'
 	else:
 		desc = msncb.error_table[errno]
-	perror('Error: %s\n' % desc)
+	perror('Error: %s (%s)\n' % (desc, errno))
 	quit(1)
 except KeyboardInterrupt:
 	quit()
 except ('SocketError', socket.error), info:
-	desc = info[1]
-	perror('Network error: ' + desc + '\n')
+	perror('Network error: ' + str(info) + '\n')
 	quit(1)
 except:
 	pexc('Exception logging in\n')
diff -ruN msnlib-d4/msnlib.py msnlib-1.0/msnlib.py
--- msnlib-d4/msnlib.py	2003-01-20 13:52:59.000000000 -0300
+++ msnlib-1.0/msnlib.py	2003-05-01 19:07:28.000000000 -0300
@@ -14,7 +14,7 @@
 """
 
 # constants
-VERSION = 0xD4
+VERSION = 0x100
 LOGIN_HOST = 'messenger.hotmail.com'
 LOGIN_PORT = 1863
 
@@ -236,7 +236,7 @@
 		return str(self.tid - 1)
 	
 	
-	def _send(self, cmd, params = '', nd = None):
+	def _send(self, cmd, params = '', nd = None, raw = 0):
 		"""Sends a command to the server, building it first as a
 		string; uses, if specified, the pseudo fd (it can be either
 		msnd or sbd).""" 
@@ -247,7 +247,8 @@
 		c = cmd + ' ' + tid
 		if params: c = c + ' ' + params
 		debug(str(fd.fileno()) + ' >>> ' + c)
-		c = c + '\r\n'
+		if not raw:
+			c = c + '\r\n'
 		c = self.encode(c)
 		return fd.send(c)
 	
@@ -592,9 +593,9 @@
 					"Content-Type: text/plain; " + \
 					"charset=UTF-8\r\n\r\n"
 				m = header + m
-				msize = len(m) + 2	# count the \r\n added by _send
+				msize = len(self.encode(m))
 				params = 'A ' + str(msize) + '\r\n' + m
-				self._send('MSG', params, sb)
+				self._send('MSG', params, sb, raw = 1)
 				del(pend[0])
 		
 			return 2
diff -ruN msnlib-d4/msnrc.sample msnlib-1.0/msnrc.sample
--- msnlib-d4/msnrc.sample	2003-01-16 10:55:56.000000000 -0300
+++ msnlib-1.0/msnrc.sample	2003-04-23 09:52:36.000000000 -0300
@@ -10,13 +10,13 @@
 # email, mandatory
 email = myself@mydomain.whatever
 
-# password, mandatory
-password = XXXX
-
 
 # all the following are optional parameters and can be omited, in which case
 # the default will be used
 
+# password, if you avoid it, the client will ask you for it at runtime
+password = XXXX
+
 # number of message history to keep in memory
 # defaults to 10
 history size = 10
@@ -56,4 +56,10 @@
 # the default is no
 debug = no
 
+# color theme
+# this configures which color theme to use; but you can still change it at
+# runtime with the 'color' command (use it to see a list of available themes
+# too)
+color theme = default
+
 
diff -ruN msnlib-d4/msnsetup msnlib-1.0/msnsetup
--- msnlib-d4/msnsetup	2003-01-20 13:51:58.000000000 -0300
+++ msnlib-1.0/msnsetup	2003-04-09 01:04:30.000000000 -0300
@@ -24,10 +24,8 @@
 function get_pass() {
 	echo "* Password"
 	echo "Your email address' password. Note that the characters won't be displayed for security issues."
-	while [ -z "$PASS" ]; do
-		read -s -p "Please insert your password: " PASS
-		echo
-	done
+	echo "If you press ENTER, it won't be written, and you'll be asked for it at login time."
+	read -s -p "Please insert your password: " PASS
 	echo
 	export PASS
 }
@@ -39,29 +37,46 @@
 }
 
 function create_rc() {
-	if [ -s "$HOME/.msn/msnrc" ]; then
-		echo "Error: file $HOME/.msn/msnrc already exists!"
+	# first parameter is the rc file to create
+	if [ -s "$1" ]; then
+		echo "Error: file $1 already exists!"
 		exit
 	fi
-	touch "$HOME/.msn/msnrc"
-	chmod -R 0600 "$HOME/.msn/msnrc"
-	echo "# msn client configuration file" >> "$HOME/.msn/msnrc"
-	echo "# created automatically by the msnsetup script" >> "$HOME/.msn/msnrc"
-	echo >> "$HOME/.msn/msnrc"
-	echo "email = $EMAIL" >> "$HOME/.msn/msnrc"
-	echo "password = $PASS" >> "$HOME/.msn/msnrc"
-	echo >> "$HOME/.msn/msnrc"
+	touch "$1"
+	chmod -R 0600 "$1"
+	echo "# msn client configuration file" >> "$1"
+	echo "# created automatically by the msnsetup script" >> "$1"
+	echo >> "$1"
+	echo "email = $EMAIL" >> "$1"
+	if [ -z "$PASS" ]; then
+		echo "# password not configured" >> "$1"
+	else
+		echo "password = $PASS" >> "$1"
+	fi
+	echo >> "$1"
 }	
 
 
 # main
+
+# we take only one optional parameter, the profile to create the rc for
+
 intro
 get_email
 get_pass
+
 echo "Creating the directory hierachy ($HOME/.msn)"
 create_dirs
-echo "Creating the configuration file ($HOME/.msn/msnrc)"
-create_rc
+
+RC="$HOME/.msn/msnrc"
+# if we have the profile, use it
+if [ "$1" ]; then
+	echo "Configuring for profile $1"
+	RC="$HOME/.msn/msnrc-$1"
+fi
+echo "Creating the configuration file ($RC)"
+create_rc "$RC"
+
 echo "Done! run 'msn' to start the client"
 
 
diff -ruN msnlib-d4/setup.py msnlib-1.0/setup.py
--- msnlib-d4/setup.py	2003-01-20 13:52:11.000000000 -0300
+++ msnlib-1.0/setup.py	2003-05-01 19:07:41.000000000 -0300
@@ -2,7 +2,7 @@
 from distutils.core import setup
 
 setup(name="msnlib",
-	version="D4",
+	version="1.0",
 	description="MSN Messenger Library and Client",
 	author="Alberto Bertogli",
 	author_email="albertogli@telpin.com.ar",
diff -ruN msnlib-d4/utils/msnbot msnlib-1.0/utils/msnbot
--- msnlib-d4/utils/msnbot	1969-12-31 21:00:00.000000000 -0300
+++ msnlib-1.0/utils/msnbot	2003-05-01 19:16:34.000000000 -0300
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+"""
+This is a very simple bot to show how automation using msnlib could be done.
+It's not quite useful as-is, but provides a good example.
+
+If you play with it, please let me know.
+"""
+
+
+# sys, for getting the parameters
+import sys
+
+# time, for sleeping
+import time
+
+# select to wait for events
+import select
+
+# socket, to catch errors
+import socket
+
+# thread, for creating the worker thread
+import thread
+
+# and, of course, msnlib
+import msnlib
+import msncb
+
+
+m = msnlib.msnd()
+m.cb = msncb.cb()
+
+
+def do_work():
+	"""
+	Here you do your stuff and send messages using m.sendmsg()
+	This is the only place your code lives
+	"""
+	
+	# wait a bit for everything to settle down (sync taking efect
+	# basically)
+	time.sleep(15)
+	
+	print '-' * 20 + 'SEND 1'
+	print m.sendmsg("xx@me.com", "Message One")
+
+	print '-' * 20 + 'SEND 2'
+	print m.sendmsg("xx@me.com", "Message Two")
+
+	# give time to send the messages
+	time.sleep(30)
+
+	# and then quit
+	quit()
+	
+
+# you shouldn't need to touch anything past here
+
+
+# get the login email and password from the parameters
+try:
+	m.email = sys.argv[1]
+	m.pwd = sys.argv[2]
+except:
+	print "Use: msnbot email password"
+	sys.exit(1)
+
+
+print "Logging In"
+m.login()
+
+print "Sync"
+# this makes the server send you the contact list, and it's recommended that
+# you do it because you can get in trouble when getting certain events from
+# people that are not on your list; and it's not that expensive anyway
+m.sync()
+
+print "Changing Status"
+# any non-offline status will do, otherwise we'll get an error from msn when
+# sending a message
+m.change_status("away")
+
+def quit():
+	try:
+		m.disconnect()
+	except:
+		pass
+	print "Exit"
+	sys.exit(0)
+
+# we start a thread to do the work. it's a thread because we want to share
+# everything, and fork cow semantics cause problems here
+thread.start_new_thread(do_work, ())
+
+
+# we loop over the network socket to get events
+print "Loop"
+while 1:
+	# we get pollable fds
+	t = m.pollable()
+	infd = t[0]
+	outfd = t[1]
+
+	# we select, waiting for events
+	try:
+		fds = select.select(infd, outfd, [], 0)
+	except:
+		quit()
+	
+	for i in fds[0] + fds[1]:       # see msnlib.msnd.pollable.__doc__
+		try:
+			m.read(i)
+		except ('SocketError', socket.error), err:
+			if i != m:
+				# user closed a connection
+				# note that messages can be lost here
+				m.close(i)
+			else:
+				# main socket closed
+				quit()
+
+	# sleep a bit so we don't take over the cpu
+	time.sleep(0.01)
+
+
+
diff -ruN msnlib-d4/utils/msncd msnlib-1.0/utils/msncd
--- msnlib-d4/utils/msncd	1969-12-31 21:00:00.000000000 -0300
+++ msnlib-1.0/utils/msncd	2003-04-23 18:23:02.000000000 -0300
@@ -0,0 +1,417 @@
+#!/usr/bin/env python 
+
+
+import sys
+import os
+import socket
+import select
+import string
+
+import msnlib
+import msncb
+
+
+"""
+MSN Client Daemon
+
+This is a MSN client that reads commands from a named pipe, using
+a little text-only protocol. It's main use is to serve as a 'glue'
+to implement clients in other languages.
+
+This is yet experimental because lack of testing, please let me know if you
+try it out.
+"""
+
+
+def null(s):
+	"Null function, useful to void debug ones"
+	pass
+		
+
+#
+# This are the callback replacements, which only handle the output and then
+# call the original callbacks to do the lower level stuff
+#
+
+# basic classes
+m = msnlib.msnd()
+m.cb = msncb.cb()
+
+# status change
+def cb_iln(md, type, tid, params):
+	t = params.split()
+	status = msnlib.reverse_status[t[0]]
+	email = t[1]
+	equeue.append('STCH %s %s\n' % (email, status))
+	msncb.cb_iln(md, type, tid, params)
+m.cb.iln = cb_iln
+
+def cb_nln(md, type, tid, params):
+	status = msnlib.reverse_status[tid]
+	t = string.split(params)
+	email = t[0]
+	equeue.append('STCH %s %s\n' % (email, status))
+	msncb.cb_nln(md, type, tid, params)
+m.cb.nln = cb_nln
+
+def cb_fln(md, type, tid, params):
+	email = tid
+	u = m.users[email]
+	discarded = 0
+	if u.sbd and u.sbd.msgqueue:
+		discarded = len(u.sbd.msgqueue)
+	equeue.append('STCH %s offline %d\n' % (email, discarded))
+	msncb.cb_fln(md, type, tid, params)
+m.cb.fln = cb_fln
+
+# server disconnect
+def cb_out(md, type, tid, params):
+	equeue.append('ERR SERV_DISC Server sent disconnect\n')
+	msncb.cb_out(md, type, tid, params)
+m.cb.out = cb_out
+
+
+# message
+def cb_msg(md, type, tid, params, sbd):
+	t = string.split(tid)
+	email = t[0]
+	
+	# messages from hotmail are only when we connect, and send things
+	# regarding, aparently, hotmail issues. we ignore them (basically
+	# because i couldn't care less; however if somebody has intrest in
+	# these and provides some debug output i'll be happy to implement
+	# parsing).
+	if email == 'Hotmail':
+		return
+
+	# parse
+	lines = string.split(params, '\n')
+	headers = {}
+	eoh = 1
+	for i in lines:
+		# end of headers
+		if i == '\r':
+			break
+		tv = string.split(i, ':')
+		type = tv[0]
+		value = string.join(tv[1:], ':')
+		value = string.strip(value)
+		headers[type] = value
+		eoh += 1
+	
+	if headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-msmsgscontrol':
+		# the typing notices
+		equeue.append('TYPING %s\n' % email)
+	else:
+		# messages
+		equeue.append('MSG %d %d %s\n%s\n' % \
+			(len(lines), eoh, email, string.join(lines, '\n')) )
+	
+	msncb.cb_msg(md, type, tid, params, sbd)
+m.cb.msg = cb_msg
+
+
+# join a conversation and send pending messages
+def cb_joi(md, type, tid, params, sbd):
+	email = tid
+	if len(sbd.msgqueue) > 0:
+		equeue.append('MFLUSH %s\n' % email)
+	msncb.cb_joi(md, type, tid, params, sbd)
+m.cb.joi = cb_joi
+
+# server errors
+def cb_err(md, errno, params):
+	if not msncb.error_table.has_key(errno):
+		desc = 'Unknown'
+	else:
+		desc = msncb.error_table[errno]
+	equeue.append('ERR %s %s\n' % (errno, desc))
+	msncb.cb_err(md, errno, params)
+m.cb.err = cb_err
+	
+# users add, delete and modify
+def cb_add(md, type, tid, params):
+	t = params.split()
+	type = t[0]
+	if type == 'RL' or type == 'FL':
+		email = t[2]
+	if type == 'RL':
+		equeue.append('UADD %s\n' % email)
+	elif type == 'FL':
+		equeue.append('ADDFL %s\n' % email)
+	msncb.cb_add(md, type, tid, params)
+m.cb.add = cb_add
+
+def cb_rem(md, type, tid, params):
+	t = params.split()
+	type = t[0]
+	if type == 'RL' or type == 'FL':
+		email = t[2]
+	if type == 'RL':
+		equeue.append('UDEL %s\n' % email)
+	elif type == 'FL':
+		equeue.append('DELFL %s\n' % email)
+	msncb.cb_rem(md, type, tid, params)
+m.cb.rem = cb_rem
+
+
+def login(email, password):
+	# login to msn
+	printl('Logging in... ', c.green, 1)
+	try:
+		m.login()
+		printl('done\n', c.green, 1)
+	except 'AuthError', info:
+		errno = int(info[0])
+		if not msncb.error_table.has_key(errno):
+			desc = 'Unknown'
+		else:
+			desc = msncb.error_table[errno]
+		perror('Error: %s\n' % desc)
+		quit(1)
+	except KeyboardInterrupt:
+		quit()
+	except ('SocketError', socket.error), info:
+		perror('Network error: ' + str(info) + '\n')
+		quit(1)
+	except:
+		pexc('Exception logging in\n')
+		quit(1)
+
+
+#
+# the pipe read
+#
+
+# first, a small send wrapper to avoid repeating 'addr' all over the place
+# note that as they are implemented using udp, if more than one client
+# connects it can get quite quite messy
+addr = ()
+def psend(pipe, s):
+	print '-->', s,
+	return pipe.sendto(s, addr)
+
+# read from the pipe, c being the pipe socket passed from the caller
+def pipe_read(c):
+	global m
+	global addr
+	global equeue	
+	
+	# we don't worry about lines too much in this implementation because
+	# we use datagrams. however, when using stream sockets you should
+	s, addr = c.recvfrom(4 * 1024)	# input buffer, should be enough
+	print '<--', s,
+	try:
+		s = s.split(' ', 1)
+		if len(s) == 2:
+			cmd, params = s
+		else:
+			cmd = s[0]
+			params = ''
+
+		cmd = cmd.strip()
+		if params:
+			params = params.strip()
+			params = params.split(' ')
+	except:
+		psend(c, 'ERR EINVAL\n')
+		return
+	
+	if cmd == 'LOGIN':
+		if len(params) != 2:
+			psend(c, 'ERR PARAMS\n')
+			return
+		try:
+			email, pwd = params
+			m.email = email
+			m.pwd = pwd
+			m.login()
+			m.sync()
+		except 'AuthError', info:
+			errno = int(info[0])
+			if not msncb.error_table.has_key(errno):
+				desc = 'Unknown'
+			else:
+				desc = msncb.error_table[errno]
+			psend(c, 'ERR MSN %d %s\n' % (errno, desc))
+			return
+		except ('SocketError', socket.error), info:
+			psend(c, 'ERR SOCK %s\n' % str(info))
+			return
+		psend(c, 'OK\n')
+		return
+		
+	elif cmd == 'LOGOFF':
+		m.disconnect()
+		psend(c, 'OK\n')
+		return
+		
+	# if we are not connected, the following commands are not available
+	if not m.fd:
+		psend(c, 'ERR ENOTCONN\n')
+		return
+	
+	
+	if cmd == 'STATUS':
+		status = string.join(params, ' ')
+		if not m.change_status(status):
+			psend(c, 'ERR UNK STATUS\n')
+		else:
+			psend(c, 'OK\n')
+		return
+		
+	if cmd == 'POLL':
+		equeue.append('POLLEND\n')
+		for evt in equeue:
+			psend(c, evt)
+		equeue = []
+		return
+	
+	if cmd == 'GETCL':
+		psend(c, 'CL %d\n' % len(m.users.keys()) )
+		for email in m.users.keys():
+			u = m.users[email]
+			status = msnlib.reverse_status[u.status]
+			psend(c, '%s %s %s\n' % (status, email, u.nick))
+		return
+	
+	if cmd == 'GETRCL':
+		psend(c, 'CL %d\n' % len(m.reverse.keys()) )
+		for email in m.reverse.keys():
+			u = m.reverse[email]
+			status = msnlib.reverse_status[u.status]
+			psend(c, '%s %s %s\n' % (status, email, u.nick))
+		return
+	
+	if cmd == 'INFO':
+		if len(params) != 1:
+			psend(c, 'ERR PARAMS\n')
+			return
+		if not m.users.has_key(email):
+			psend(c, 'ERR UNK USER\n')
+		u = m.users[email]
+		psend(c, 'email = %s\n' % email)
+		psend(c, 'nick = %s\n' % u.nick)
+		psend(c, 'homep = %s\n' % u.homep)
+		psend(c, 'workp = %s\n' % u.workp)
+		psend(c, 'mobilep = %s\n' % u.mobilep)
+		psend(c, '\n')
+		return
+	
+	if cmd == 'ADD':
+		if len(params) != 2:
+			psend(c, 'ERR PARAMS\n')
+			return
+		nick, email = params
+		m.useradd(email, nick)
+		psend(c, 'OK\n')
+		return
+	
+	if cmd == 'DEL':
+		if len(params) != 1:
+			psend(c, 'ERR PARAMS\n')
+			return
+		m.userdel(params)
+		psend(c, 'OK\n')
+		return
+	
+	if cmd == 'NICK':
+		if len(params) != 1:
+			psend(c, 'ERR PARAMS\n')
+			return
+		m.change_nick(params)
+		psend(c, 'OK\n')
+		return
+	
+	if cmd == 'PRIV':
+		if len(params) != 2:
+			psend(c, 'ERR PARAMS\n')
+			return
+		try:
+			public = int(p[0])
+			auth = int(p[1])
+			if public not in (0, 1) or auth not in (0, 1):
+				raise
+		except:
+			psend(c, 'ERR EINVAL\n')
+			return
+		m.privacy(public, auth)
+		psend(c, 'OK\n')
+		return
+	
+	if cmd == 'SENDMSG':
+		params = string.join(params, ' ')
+		params = string.split(params, '\n', 2)
+		params, msg = params
+		params = string.split(params, ' ')
+		if len(params) < 2:
+			psend(c, 'ERR PARAMS\n')
+			return
+		lines = params[0]
+		email = params[1]
+		msg = msg
+		m.sendmsg(email, msg)
+		psend(c, 'OK\n')
+		return
+		
+	# if we got here is because the command is unknown		
+	psend(c, 'ERR UNK\n')
+	return
+	
+		
+
+#
+# now the real thing
+#
+
+# void the debug
+msnlib.debug = null
+msncb.debug = null
+
+# POLL event queue
+# We implement it in a very, very efficient way: text =)
+# Yes, it's actually a list, but just because .append() is readable
+# and allow us to keep track of the number of pending events
+equeue = []
+
+# open the socket for local communication
+# we use datagram sockets to avoid complex reads and writes for now, but the
+# protocol is line-oriented and perfectly capable of working over a stream
+# socket.
+pipe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+pipe.bind(('127.0.0.1', 3030))
+
+
+# loop, waiting for connections
+while 1:
+	infd = outfd = []
+	# if we are connected, poll from msn
+	if m.fd != None:
+		t = m.pollable()
+		infd = t[0]
+		outfd = t[1]
+	infd.append(pipe)
+
+	fds = select.select(infd, outfd, [], 0)
+	
+	for i in fds[0] + fds[1]:	# see msnlib.msnd.pollable.__doc__
+		if i == pipe:
+			# read from the pipe
+			pipe_read(pipe)
+		else:
+			try:
+				m.read(i)
+			except ('SocketError', socket.error), err:
+				if i != m:
+					# user closed a connection
+					# note that messages can be
+					# lost here
+					equeue.append('SCLOSE USER %s %d\n' % (i.emails[0], len(i.msgqueue)) )
+					m.close(i)
+				else:
+					# main socket closed
+					# report
+					equeue.append('SCLOSE MAIN\n')
+					quit(1)
+
+
