diff --git a/sql-edition/servers/LDAPConnector.py b/sql-edition/servers/LDAPConnector.py
index 60408d023a06236680d194310160fdc26b4217a8..8699fd2ba6f536d39efd434ca534c685bdad87e7 100644
--- a/sql-edition/servers/LDAPConnector.py
+++ b/sql-edition/servers/LDAPConnector.py
@@ -36,6 +36,22 @@ def get_uid(card_id):
         
         return results[0][1]['uidNumber'][0]
 
+def get_uname(uid):
+        ldapconn = get_ldap_connection()
+        
+        basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au'
+        filter = ldap.filter.filter_format('(uidNumber=%s)', (uid, ))
+        attrs = ('uid',)
+        
+        results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT)
+        
+        ldapconn.unbind()
+        
+        if len(results) != 1:
+                raise ValueError, "no username found for user id"
+        
+        return results[0][1]['uid'][0]
+
 def set_card_id(uidNumber, card_id):
         ldapconn = get_ldap_connection()
         
diff --git a/sql-edition/servers/MIFAREDriver.py b/sql-edition/servers/MIFAREDriver.py
index 46a073192d48ae97a53c5ee3f658136f987bc9c5..6248f60c2934b2e2135c8a97899561116dd2c5bc 100644
--- a/sql-edition/servers/MIFAREDriver.py
+++ b/sql-edition/servers/MIFAREDriver.py
@@ -96,7 +96,7 @@ class MIFAREReader:
         
         card_type_response = self.send_packet(command)
         
-        if card_type_response[2] == '\x14':
+        if card_type_response == None or card_type_response[2] == '\x14':
             raise MIFAREException, "select_card: no card available"
         card_type = card_type_response[3:5]
         
diff --git a/sql-edition/servers/VendServer.py b/sql-edition/servers/VendServer.py
index ce7583721416ca3f8b788103165cc1d5bfbd6862..2a9a55af613028cd85d9b3a0e32aa0fc9a056b6f 100755
--- a/sql-edition/servers/VendServer.py
+++ b/sql-edition/servers/VendServer.py
@@ -10,7 +10,7 @@ import logging, logging.handlers
 from traceback import format_tb
 if USE_DB: import pg
 from time import time, sleep, mktime, localtime
-from popen2 import popen2
+from subprocess import Popen, PIPE
 from LATClient import LATClient, LATClientException
 from SerialClient import SerialClient, SerialClientException
 from VendingMachine import VendingMachine, VendingException
@@ -21,7 +21,7 @@ from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,Fort
 from SnackConfig import get_snack#, get_snacks
 import socket
 from posix import geteuid
-from LDAPConnector import get_uid, set_card_id
+from LDAPConnector import get_uid,get_uname, set_card_id
 
 CREDITS="""
 This vending machine software brought to you by:
@@ -94,13 +94,10 @@ class DispenseDatabase:
 
 def scroll_options(username, mk, welcome = False):
 	if welcome:
-		# Balance checking: crap code, [DAA]'s fault
-		# Updated 2011 to handle new dispense [MRD]
-		raw_acct = os.popen('dispense acct %s' % username)
-		acct = raw_acct.read()
+		# Balance checking
+		acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
 		# this is fucking appalling
 		balance = acct[acct.find("$")+1:acct.find("(")].strip()
-		raw_acct.close()
         
 		msg = [(center('WELCOME'), False, TEXT_SPEED),
 			   (center(username), False, TEXT_SPEED),
@@ -112,10 +109,8 @@ def scroll_options(username, mk, welcome = False):
 	# Get coke contents
 	cokes = []
 	for i in range(0, 7):
-		cmd = 'dispense iteminfo coke:%i' % i
-		raw = os.popen(cmd)
-		info = raw.read()
-		raw.close()
+		args = ('dispense', 'iteminfo', 'coke:%i' % i)
+		info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
 		m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
 		cents = int(m.group(1))*100 + int(m.group(2))
 		cokes.append('%i %i %s' % (i, cents, m.group(3)));
@@ -143,30 +138,82 @@ def scroll_options(username, mk, welcome = False):
 	msg.append((choices, False, None))
 	mk.set_messages(msg)
 
-def get_acct_state(uid):
-	try:
-		info = pwd.getpwuid(uid)
-	except KeyError:
-		logging.info('getting pin for uid %d: user not in password file'%uid)
-		return 'invalid'
-	ret = os.system('dispense acct %s' % (info.pw_name))
-	if ret != 0:
-		return 'invalid'
-
-	# TODO: Disabled account check (done in server pin check now)	
+_pin_uid = 0
+_pin_uname = 'root'
+_pin_pin = '----'
 
-	return 'good'
+def _check_pin(uid, pin):
+	global _pin_uid
+	global _pin_uname
+	global _pin_pin
+	print "_check_pin('",uid,"',---)"
+	if uid != _pin_uid:
+		try:
+			info = pwd.getpwuid(uid)
+		except KeyError:
+			logging.info('getting pin for uid %d: user not in password file'%uid)
+			return None
+		if info.pw_dir == None: return False
+		pinfile = os.path.join(info.pw_dir, '.pin')
+		try:
+			s = os.stat(pinfile)
+		except OSError:
+			logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
+			return None
+		if s.st_mode & 077:
+			logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
+			os.chmod(pinfile, 0600)
+		try:
+			f = file(pinfile)
+		except IOError:
+			logging.info('getting pin for uid %d: I cannot read pin file'%uid)
+			return None
+		pinstr = f.readline()
+		f.close()
+		if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
+			logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
+			return None
+		_pin_uid = uid
+		_pin_pin = pinstr
+		_pin_uname = info.pw_name
+	else:
+		pinstr = _pin_pin
+	if pin == int(pinstr):
+		logging.info("Pin correct for %d",uid)
+	else:
+		logging.info("Pin incorrect for %d",uid)
+	return pin == int(pinstr)
+
+def acct_is_disabled(name=None):
+	global _pin_uname
+	if name == None:
+		name = _pin_uname
+	acct, unused = Popen(['dispense', 'acct', _pin_uname], close_fds=True, stdout=PIPE).communicate()
+	# this is fucking appalling
+	flags = acct[acct.find("(")+1:acct.find(")")].strip()
+	if 'disabled' in flags:
+		return True
+	if 'internal' in flags:
+		return True
+	return False
+
+def has_good_pin(uid):
+	return _check_pin(uid, None) != None
 
 def verify_user_pin(uid, pin, skip_pin_check=False):
-	info = pwd.getpwuid(uid)
-	if skip_pin_check:
-		logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
-	elif os.system('dispense pincheck %04i %s' % (pin, info.pw_name)) != 0:
+	if skip_pin_check or _check_pin(uid, pin) == True:
+		info = pwd.getpwuid(uid)
+		if skip_pin_check:
+			if acct_is_disabled(info.pw_name):
+				logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name))
+				return '-disabled-'
+			logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
+		else:
+			logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
+		return info.pw_name
+	else:
 		logging.info('refused pin for uid %d'%(uid))
 		return None
-	else:
-		logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
-	return info.pw_name
 
 
 def cookie(v):
@@ -225,6 +272,8 @@ def setup_idlers(v):
 		ClockIdler(v),
 		StringIdler(v),
 		TrainIdler(v),
+		# "Hello World" in brainfuck
+		StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
 		]
 	disabled = [
 		]
@@ -420,7 +469,6 @@ def make_selection(v, vstatus):
 			v.display('SEEMS NOT')
 		else:
 			v.display('GOT DRINK!')
-			#v.display('SEE FRIDGE')
 	else:
 		# first see if it's a named slot
 		try:
@@ -429,14 +477,18 @@ def make_selection(v, vstatus):
 			price, shortname, name = get_snack( '--' )
 		dollarprice = "$%.2f" % ( price / 100.0 )
 		v.display(vstatus.cur_selection+' - %s'%dollarprice)
+#		exitcode = os.system('dispense -u "%s" give \>snacksales %d "%s"'%(vstatus.username, price, name)) >> 8
 #		exitcode = os.system('dispense -u "%s" give \>sales\:snack %d "%s"'%(vstatus.username, price, name)) >> 8
-		# For some reason, this causes the machine and this code to desync
 		exitcode = os.system('dispense -u "%s" snack:%s'%(vstatus.username, vstatus.cur_selection)) >> 8
 		if (exitcode == 0):
 			# magic dispense syslog service
 			syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, vstatus.cur_selection, vstatus.username))
-			v.vend(vstatus.cur_selection)
-			v.display('THANK YOU')
+			(worked, code, string) = v.vend(vstatus.cur_selection)
+			if worked:
+				v.display('THANK YOU')
+			else:
+				print "Vend Failed:", code, string
+				v.display('VEND FAIL')
 		elif (exitcode == 5):	# RV_BALANCE
 			v.display('NO MONEY?')
 		elif (exitcode == 4):	# RV_ARGUMENTS (zero give causes arguments)
@@ -451,7 +503,9 @@ def make_selection(v, vstatus):
 
 def price_check(v, vstatus):
 	if vstatus.cur_selection[1] == '8':
-		v.display(center('SEE COKE'))
+		args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0])
+		info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
+		dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1)
 	else:
 		# first see if it's a named slot
 		try:
@@ -459,7 +513,7 @@ def price_check(v, vstatus):
 		except:
 			price, shortname, name = get_snack( '--' )
 		dollarprice = "$%.2f" % ( price / 100.0 )
-		v.display(vstatus.cur_selection+' - %s'%dollarprice)
+	v.display(vstatus.cur_selection+' - %s'%dollarprice)
 
 
 def handle_getting_pin_key(state, event, params, v, vstatus):
@@ -569,35 +623,21 @@ Wouldn't you prefer a nice game of chess?
 
 			return
 
-		acct_state = get_acct_state(uid)
-		if acct_state == 'invalid':
-			logging.info('user '+vstatus.cur_user+' is not in the database')
-			vstatus.mk.set_messages(
-				[(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
-			vstatus.cur_user = ''
-			vstatus.cur_pin = ''
-			
-			reset_idler(v, vstatus, 3)
-			return
-		elif acct_state == 'locked':
-			logging.info('user '+vstatus.cur_user+' is locked')
+		if not has_good_pin(uid):
+			logging.info('user '+vstatus.cur_user+' has a bad PIN')
 			vstatus.mk.set_messages(
 				[(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
 			vstatus.cur_user = ''
 			vstatus.cur_pin = ''
 			
 			reset_idler(v, vstatus, 3)
+
 			return
-		elif acct_state == 'good':
-			vstatus.cur_pin = ''
-			vstatus.mk.set_message('PIN: ')
-			logging.info('need pin for user %s'%vstatus.cur_user)
-			vstatus.change_state(STATE_GETTING_PIN)
-			return
-		else:
-			logging.error('user '+vstatus.cur_user+' has an unknown account state'+acct_state)
+		
+		if acct_is_disabled():
+			logging.info('user '+vstatus.cur_user+' is disabled')
 			vstatus.mk.set_messages(
-				[(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
+				[(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
 			vstatus.cur_user = ''
 			vstatus.cur_pin = ''
 			
@@ -605,6 +645,13 @@ Wouldn't you prefer a nice game of chess?
 			return
 
 
+		vstatus.cur_pin = ''
+		vstatus.mk.set_message('PIN: ')
+		logging.info('need pin for user %s'%vstatus.cur_user)
+		vstatus.change_state(STATE_GETTING_PIN)
+		return
+
+
 def handle_idle_key(state, event, params, v, vstatus):
 	#print "handle_idle_key (s,e,p)", state, " ", event, " ", params
 
@@ -784,11 +831,23 @@ def handle_mifare_event(state, event, params, v, vstatus):
 	try:
 		vstatus.cur_user = get_uid(card_id)
 		logging.info('Mapped card id to uid %s'%vstatus.cur_user)
-		vstatus.username = verify_user_pin(int(vstatus.cur_user), None, True)
+		vstatus.username = get_uname(vstatus.cur_user)
+		if acct_is_disabled(vstatus.username):
+			vstatus.username = '-disabled-'
 	except ValueError:
 		vstatus.username = None
+	if vstatus.username == '-disabled-':
+		v.beep(40, False)
+		vstatus.mk.set_messages(
+			[(center('ACCT DISABLED'), False, 1.0),
+			 (center('SORRY'), False, 0.5)])
+		vstatus.cur_user = ''
+		vstatus.cur_pin = ''
+		vstatus.username = None
 	
-	if vstatus.username:
+		reset_idler(v, vstatus, 2)
+		return
+	elif vstatus.username:
 		v.beep(0, False)
 		vstatus.cur_selection = ''
 		vstatus.change_state(STATE_GET_SELECTION)
@@ -1091,9 +1150,8 @@ def do_vend_server(options, config_opts):
 		
 		try:
 			run_forever(rfh, wfh, options, config_opts)
-		except VendingException as e:
+		except VendingException:
 			logging.error("Connection died, trying again...")
-			logging.info("Exception: "+e.__str__())
 			logging.info("Trying again in 5 seconds.")
 			sleep(5)