diff -ruN msnlib-2.1/README msnlib-3.0/README
--- msnlib-2.1/README	2003-01-20 14:26:15.000000000 -0300
+++ msnlib-3.0/README	2003-09-24 14:27:58.000000000 -0300
@@ -3,15 +3,23 @@
 Alberto Bertogli (albertogli@telpin.com.ar)
 ----------------------------------------------
 
-This is a python implementation for the msn messenger protocol, it's pretty
-simple and straightforward; but it works well.
+This is a python implementation for the msn messenger protocol (version 8),
+it's pretty simple and straightforward; but it works well.
 
 Please read the 'INSTALL' file to see how to install and use both the client
 and the library.
 
 The client is really simple, uses a text-mode interface with commands similar
-to 'micq' (http://micq.ukeer.de/), which is a really great icq client; if
-you're looking for a good messaging system, forget about messenger and try it.
+to 'micq' (http://micq.ukeer.de/), which is a great ICQ client.
+
+If you're looking for a good messaging system, forget about messenger and try
+Jabber (http://www.jabber.org), it's the only one which is run in the open
+based on public standards, it's safe, fast, has a lot of features and it's
+quite scalable. 
+
+But if you're stucked with messenger for whatever reason (your friends,
+family, the dog, all use it =), I hope you find this useful.
+
 
 The basic idea for the library is a main class which represents a connection
 with the server and holds all the relevant information, it has only the a few
diff -ruN msnlib-2.1/doc/Changelog msnlib-3.0/doc/Changelog
--- msnlib-2.1/doc/Changelog	2003-08-25 23:14:35.000000000 -0300
+++ msnlib-3.0/doc/Changelog	2003-09-24 14:29:48.000000000 -0300
@@ -1,3 +1,19 @@
+24 Sep 03 14.29.17 - Alberto <albertogli@telpin.com.ar>
+ * tag: 3.0
+ * doc: small updates
+
+22 Sep 03 23.55.26 - Alberto <albertogli@telpin.com.ar>
+ * msn: preserve whitespace in messages
+
+22 Sep 03 02.10.15 - Alberto <albertogli@telpin.com.ar>
+ * msn, msnlib: implement user blocking and unblocking
+ * msncb: fix MSNP8 SYN so it doesn't step over existing users
+ * msncb: use the same user object for both reverse and forward lists
+ * msncb: fix MSNP8 ADD and BPR so both work well with the new SYN
+
+22 Sep 03 00.14.15 - Alberto <albertogli@telpin.com.ar>
+ * msnlib, msncb: implement MSNP8
+
 25 Aug 03 23.14.08 - Alberto <albertogli@telpin.com.ar>
  * tag: 2.1
 
diff -ruN msnlib-2.1/doc/TODO msnlib-3.0/doc/TODO
--- msnlib-2.1/doc/TODO	2003-06-07 15:40:55.000000000 -0300
+++ msnlib-3.0/doc/TODO	2003-09-22 02:18:07.000000000 -0300
@@ -8,13 +8,8 @@
 msn client TODO
 * be able to use nick with spaces
 	this can cause a lot of damage, is it worthy?
-* full-featured tab completion
 * line editing
 
-msn lib TODO
-* blocked and allowed lists
-	this isn't hard and it might be handy for some people
-
 
 Future / In doubt
 -----------------
@@ -31,10 +26,6 @@
 
 
 msn lib 
-* make login async (maybe using something like continuations?)
-	continuations would have worked nicely, but are only available in
-	python >= 2.2 (which many people don't have), so it will have to wait
-	for a bit
 * file transfer
 	this is waaaaay below in my priority lists. there are thousand of
 	better ways to do file transfer between two hosts; plus the protocol
diff -ruN msnlib-2.1/doc/dependencies msnlib-3.0/doc/dependencies
--- msnlib-2.1/doc/dependencies	2003-01-20 14:26:15.000000000 -0300
+++ msnlib-3.0/doc/dependencies	2003-09-22 00:45:55.000000000 -0300
@@ -1,5 +1,6 @@
 
-The only thing you need is a working python installation.
+The only thing you need is a working python installation with SSL support
+(most of them have it by default).
 
 Now, i've tried with python 2.2 but it will probably work with lower versions
 too, it doesn't do weird things with the language so i don't see many possible
diff -ruN msnlib-2.1/msn msnlib-3.0/msn
--- msnlib-2.1/msn	2003-08-25 23:13:00.000000000 -0300
+++ msnlib-3.0/msn	2003-09-24 14:28:44.000000000 -0300
@@ -48,6 +48,8 @@
 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
@@ -207,6 +209,8 @@
 	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:
@@ -693,14 +697,19 @@
 	if cmd[-1] == '\n':
 		cmd = cmd[:-1]
 	cmd = cmd.lstrip()
-	cmd = cmd.split()
-	if len(cmd) > 1:
-		cmd, params = cmd[0], string.join(cmd[1:])
+	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 = cmd[0]
+		cmd = s[0]
 		params = ''
 	
+
 	# parse
 	if   cmd == 'status': 		# change status
 		if not params:
@@ -835,6 +844,22 @@
 		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: 
@@ -962,7 +987,12 @@
 				return 'Please enter a nick and a message'
 			nick = p[0]
 			email = nick2email(nick)
-			msg = string.join(p[1:])
+			# get the beginning, that is the position of the first
+			# word behind the nick
+			begin = params.find(p[1])
+			# and use it to build the message; all this to
+			# preserve whitespace properly
+			msg = params[begin:]
 		elif cmd == 'r':
 			email = last_received
 			nick = email2nick(email)
@@ -1265,7 +1295,7 @@
 #
 # now the real thing
 #
-printl('* MSN Client (2.1) *\n', c.yellow, 1)
+printl('* MSN Client (3.0) *\n', c.yellow, 1)
 
 # first, the configuration
 printl('Loading config... ', c.green, 1)
diff -ruN msnlib-2.1/msncb.py msnlib-3.0/msncb.py
--- msnlib-2.1/msncb.py	2003-08-25 00:38:14.000000000 -0300
+++ msnlib-3.0/msncb.py	2003-09-22 02:27:52.000000000 -0300
@@ -140,9 +140,9 @@
 def cb_chl(md, type, tid, params):
 	"Handles the challenges"
 	if type != 'CHL': raise 'CallbackMess', (md, type, params)
-	hash = params + 'Q1P7W2E4J9R8U3S5' # magic from www.hypothetic.org
+	hash = params + 'VT6PX?UQTM4WM%YR' # magic from www.hypothetic.org
 	hash = md5.md5(hash).hexdigest()
-	md._send('QRY', 'msmsgs@msnmsgr.com 32')
+	md._send('QRY', 'PROD0038W!61ZTF9 32')
 	md.fd.send(hash)
 
 
@@ -191,11 +191,23 @@
 
 def cb_bpr(md, type, tid, params):
 	"Update friend info"
-	t = params.split(' ')
-	email = t[0]
-	type = t[1]
-	if len(t) > 2: param = urllib.unquote(t[2])
-	else: param = ''
+	# the email is deduced from the last lst we got; if it's None it means
+	# that we come from an add (the protocol behaves different if coming
+	# from SYN or ADD)
+	email = md._last_lst
+	if email:
+		# we come from SYN
+		type = tid
+		param = urllib.unquote(params)
+	else:
+		# we come from ADD
+		t = params.split(' ')
+		email = t[0]
+		type = t[1]
+		if len(t) >= 3:
+			param = urllib.unquote(t[2])
+		else:
+			param = ''
 
 	if not md.users.has_key(email): return
 
@@ -207,41 +219,54 @@
 
 def cb_lst(md, type, tid, params):
 	p = params.split(' ')
-	reqtype = p[0]
-
-	# forward list
-	if reqtype == 'FL':
-		# empty list
-		if p[3] == '0':
-			return
-			
-		lver, uid, ucount, email, nick, gid = p[1:]
-		nick = urllib.unquote(nick)
-		gid = gid.split(',')[0]
-		if email not in md.users.keys():
-			md.users[email] = msnlib.user(email, nick, gid) 
-		else:
-			md.users[email].nick = nick
-			md.users[email].gid = gid
+	email = tid
+	nick = urllib.unquote(p[0])
+	listmask = int(p[1])
+	if len(p) == 3:
+		groups = p[2]
+	else:
+		groups = '0'
 	
-	elif reqtype == 'RL':
-		if p[3] == '0':
-			return
-		lver, uid, ucount, email, nick = p[1:]
-		nick = urllib.unquote(nick)
-		md.reverse[email] = msnlib.user(email, nick)
+	# we only use one main group id
+	gid = groups.split(',')[0]
 	
-	# blocked and allowed lists, TODO?
-	elif reqtype == 'BL':
-		pass
-	elif reqtype == 'AL':
-		pass
-
+	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
+	# BL: 4
+	# RL: 8
+
+	# in forward
+	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
 
 def cb_lsg(md, type, tid, params):
 	"Handles group list"
 	p = params.split(' ')
-	lver, gnum, gcount, gid, name, unk = p[0:]
+	gid = tid
+	name, unk = p[0:]
 	# if we get the group 0, start from scratch
 	if gid == '0':
 		md.groups = {}
@@ -263,8 +288,7 @@
 
 
 def cb_add(md, type, tid, params):
-	"""Handles a user add. 
-	Only make something in the case of 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':
@@ -276,11 +300,12 @@
 		nick = urllib.unquote(t[3])
 		gid = t[4]
 		md.users[email] = msnlib.user(email, nick, gid)
+		# put None in last_lst so BPRs know it's not coming from sync
+		md._last_lst = None
 		debug('ADD: adding %s (%s)' % (email, nick))
 	else:
 		pass
 
-
 def cb_rem(md, type, tid, params):
 	"""Handles a user del.
 	Only make something in the case of a user removing you"""
diff -ruN msnlib-2.1/msnlib.py msnlib-3.0/msnlib.py
--- msnlib-2.1/msnlib.py	2003-08-25 23:13:09.000000000 -0300
+++ msnlib-3.0/msnlib.py	2003-09-24 14:29:14.000000000 -0300
@@ -14,7 +14,7 @@
 """
 
 # constants
-VERSION = 0x0201
+VERSION = 0x0300
 LOGIN_HOST = 'messenger.hotmail.com'
 LOGIN_PORT = 1863
 
@@ -62,6 +62,7 @@
 		self.mobilep = None
 		self.sbd = None
 		self.priv = {}
+		self.lists = []
 	
 	def __repr__(self):
 		return '<user email:%s nick:"%s" gid:%s>' % (email, nick, gid)
@@ -378,6 +379,18 @@
 		self._send('REM', 'FL ' + email)
 		return 1
 	
+	def userblock(self, email):
+		self._send('REM', 'AL ' + email)
+		self._send('ADD', 'BL ' + email + ' ' + email)
+		if 'B' not in self.users[email].lists:
+			self.users[email].lists.append('B')
+	
+	def userunblock(self, email):
+		self._send('REM', 'BL ' + email)
+		self._send('ADD', 'AL ' + email + ' ' + email)
+		if 'B' in self.users[email].lists:
+			self.users[email].lists.remove('B')
+	
 	def groupadd(self, name):
 		"Adds a group"
 		name = urllib.quote(name)
@@ -424,21 +437,18 @@
 		self.fd.connect((self.lhost, self.lport))
 		
 		# version information
-		self._send('VER', 'MSNP7 MSNP6 MSNP5 MSNP4 CVR0')
+		self._send('VER', 'MSNP8 CVR0')
 		
 		r = self._recv()
-		if r[0] != 'VER' and r[2][0:4] != 'MSNP7':
+		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
 			raise 'VersionError', r
-	
-		# authentication method (always md5)
-		self._send('INF')
-		
-		r = self._recv()
-		if r[0] != 'INF' and r[2] != 'MD5':
-			raise 'CapError', 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', 'MD5 I ' + self.email)
+		self._send('USR', 'TWN I ' + self.email)
 
 		r = self._recv()
 		if r[0] != 'XFR' and r[2][0:2] != 'NS':
@@ -456,23 +466,24 @@
 		self.fd.connect(self.ns)
 
 		# version, same as before
-		self._send('VER', 'MSNP7 MSNP6 MSNP5 MSNP4 CVR0')
+		self._send('VER', 'MSNP8 CVR0')
 		r = self._recv()
-		if r[0] != 'VER' and r[2][0:4] != 'MSNP7':
+		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
 			raise 'VersionError', r
-
-		self._send('INF')
-		r = self._recv()
-		if r[0] != 'INF' and r[2] != 'MD5':
-			raise 'CapError', 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
 	
-		# auth: send user, get hash and send md5
-		self._send('USR', 'MD5 I ' + self.email)
+		# auth: send user, get hash
+		self._send('USR', 'TWN I ' + self.email)
 		
 		r = self._recv()
 		hash = string.split(r[2])[2]
-		sum = md5.md5(hash + self.pwd).hexdigest()
-		self._send('USR', 'MD5 S ' + sum)
+		
+		# get and use the passport id
+		passportid = self.passport_auth(hash)
+		self._send('USR', 'TWN S ' + passportid)
 
 		r = self._recv()
 		if r[0] != 'USR' and r[2][0:2] != 'OK':
@@ -483,6 +494,59 @@
 		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
+		
+		nexus = urllib.urlopen('https://nexus.passport.com/rdr/pprdr.asp')
+		h = nexus.headers
+		purl = h['PassportURLs']
+
+		d = {}
+		for i in purl.split(','):
+		        key, val = i.split('=', 1)
+			d[key] = val
+		
+		login_server = 'https://' + d['DALogin']
+		login_host = d['DALogin'].split('/')[0]
+
+		ls = httplib.HTTPSConnection(login_host)
+
+		
+		ahead =  'Passport1.4 OrgVerb=GET'
+		ahead += ',OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom'
+		ahead += ',sign-in=' + urllib.quote(self.email)
+		ahead += ',pwd=' + urllib.quote(self.pwd)
+		ahead += ',lc=1033,id=507,tw=40,fs=1,'
+		ahead += 'ru=http%3A%2F%2Fmessenger%2Emsn%2Ecom,ct=1062764229,'
+		ahead += 'kpp=1,kv=5,ver=2.1.0173.1,tpf=' + hash
+		
+		headers = { 'Authorization': ahead }
+		ls.request('GET', login_server, '', headers)
+		resp = ls.getresponse()
+		
+		if resp.status != 200:
+			# for now we raise 911, which means authentication
+			# failed; but maybe we can get more detailed
+			# information
+			raise 'AuthError', 911
+		try:
+			ainfo = resp.getheader('Authentication-Info')
+		except:
+			ainfo = resp.getheader('WWW-Authenticate')
+		
+		d = {}
+		for i in ainfo.split(','):
+			key, val = i.split('=', 1)
+			d[key] = val
+			
+		passportid = d['from-PP']
+		passportid = passportid[1:-1]		# remove the "'"
+		return passportid
+
+
 	def read(self, nd = None):
 		"""Reads from the specified nd and run the callback. The nd
 		can be either a msnd or a sbd (that's why it's called 'nd'
diff -ruN msnlib-2.1/setup.py msnlib-3.0/setup.py
--- msnlib-2.1/setup.py	2003-08-25 23:13:18.000000000 -0300
+++ msnlib-3.0/setup.py	2003-09-24 14:29:05.000000000 -0300
@@ -2,7 +2,7 @@
 from distutils.core import setup
 
 setup(name="msnlib",
-	version="2.1",
+	version="3.0",
 	description="MSN Messenger Library and Client",
 	author="Alberto Bertogli",
 	author_email="albertogli@telpin.com.ar",
