From 31c3aa4355f03930a68cf25a83c8160414249213 Mon Sep 17 00:00:00 2001 From: John Hodge Date: Sat, 24 Aug 2013 06:51:35 +0000 Subject: [PATCH] subprocess.Popen, new pin code, disabled users, bugfixes --- sql-edition/servers/LDAPConnector.py | 16 +++ sql-edition/servers/MIFAREDriver.py | 2 +- sql-edition/servers/VendServer.py | 180 ++++++++++++++++++--------- 3 files changed, 136 insertions(+), 62 deletions(-) diff --git a/sql-edition/servers/LDAPConnector.py b/sql-edition/servers/LDAPConnector.py index 60408d0..8699fd2 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 46a0731..6248f60 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 ce75837..2a9a55a 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) -- 2.20.1