MIFARE Login - Add a blacklist of known-bad cards
[uccvend-vendserver.git] / VendServer / OpenDispense.py
index f8cee0d..6b93058 100644 (file)
@@ -1,10 +1,3 @@
-from DispenseInterface import DispenseInterface
-import os
-import re
-import pwd
-from subprocess import Popen, PIPE
-from LDAPConnector import get_uid,get_uname, set_card_id
-
 """
 Author: Mitchell Pomery (bobgeorge33)
 
@@ -13,97 +6,248 @@ Most of this code has been copied out of VendServer.py, then had variables updat
 This is so VendServer can easily operate regardless of the current accounting backend.
 Documentation for this code can be found inder Dispence.DispenceInterface
 """
+
+from DispenseInterface import DispenseInterface
+import os
+import logging
+import re
+import pwd
+import base64
+import socket
+from subprocess import Popen, PIPE
+from LDAPConnector import get_uid,get_uname, set_card_id
+
+DISPENSE_ENDPOINT = ("localhost", 11020)
+DISPSRV_MIFARE = True
+
+# A list of cards that should never be registered, and should never log in
+# - Some of these might have been registered before we knew they were duplicates
+CARD_BLACKLIST = [
+       'AAAAAA==',     # All zeroes, don't allow that.
+       'ISIjJA==', # CommBank credit cards
+       ]
+
 class OpenDispense(DispenseInterface):
-        _username = None
-        _disabled = True
-        _loggedIn = False
+       _username = ""
+       _disabled = True
+       _loggedIn = False
        _userId = None
 
-        def __init__(self, userId=None, username=None, loggedIn=False):
-               self._username = username
-                self._loggedIn = loggedIn
-               self._userId = userId
-               
-               acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
-               # this is fucking appalling
-               flags = acct[acct.find("(")+1:acct.find(")")].strip()
-               if 'disabled' in flags:
-                       self._disabled = True
-               if 'internal' in flags:
-                       self._disabled = True
-                self._disabled = False
+       def __init__(self, username=None, secret=False):
+               pass
+
+       def authUserIdPin(self, userId, pin):
+               return self.authUserIdPin_db(userId, pin)
+               #return self.authUserIdPin_file(userId, pin)
+       
+       def authUserIdPin_db(self, userId, pin):
+               userId = int(userId)
+
+               try:
+                       # Get username (TODO: Store the user ID in the dispense database too)
+                       info = pwd.getpwuid(userId)
+               except KeyError:
+                       logging.info('getting pin for uid %d: user not in password file'%userId)
+                       return False
+               
+               sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+               sock.connect(DISPENSE_ENDPOINT)
+               logging.debug('connected to dispsrv')
+               sockf = sock.makefile()
+               sockf.write("AUTHIDENT\n"); sockf.flush()
+               rsp = sockf.readline()
+               assert "200" in rsp
+               logging.debug('authenticated')
+               sockf.write("PIN_CHECK %s %s\n" % (info.pw_name, pin)); sockf.flush()
+               rsp = sockf.readline()
+               if not "200" in rsp:
+                       logging.info('checking pin for uid %d: Server said no - %r' % (userId, rsp))
+                       return False
+               #Login Successful
+               logging.info('accepted pin for uid %d \'%s\'' % (userId, info.pw_name))
+               self._userid = userId
+               self._loggedIn = True
+               self._disabled = False
+               self._username = info.pw_name
+               return True
+
+       def authUserIdPin_file(self, userId, pin):
+               userId = int(userId)
 
-       @staticmethod
-       def authUserIdPin(userId, pin):
                try:
                        # Get info from 
-                        info = pwd.getpwuid(userId)
-                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().strip()
-                f.close()
-                if not re.search('^[0-9]{4}$', pinstr):
-                        logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
-                        return None
-               return OpenDispense(userId, info.pw_name, (int(pin)==int(pinstr)))
+                       info = pwd.getpwuid(userId)
+               except KeyError:
+                       logging.info('getting pin for uid %d: user not in password file'%userId)
+                       return False
 
-       @staticmethod
-       def authMifareCard(cardId):
-               return OpenDispense(get_uid(cardId), get_uname(get_uid(cardId)), True)
+               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'%userId)
+                       return False
+               if s.st_mode & 077:
+                       logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%userId)
+                       os.chmod(pinfile, 0600)
+               try:
+                       f = file(pinfile)
+               except IOError:
+                       logging.info('getting pin for uid %d: I cannot read pin file'%userId)
+                       return False
+               pinstr = f.readline().strip()
+               f.close()
+               if not re.search('^[0-9]{4}$', pinstr):
+                       logging.info('getting pin for uid %d: %s not a good pin'%(userId,repr(pinstr)))
+                       return False
+
+               if pinstr == str(pin):
+                       #Login Successful
+                       self._userid = userId
+                       self._loggedIn = True
+                       self._disabled = False
+                       self._username = info.pw_name
+                       return True
+               
+               # Login Unsuccessful
+               return False
+
+       def authMifareCard(self, cardId):
+               self._loggedIn = False
+               self._username = None
+               if DISPSRV_MIFARE:
+                       card_base64 = base64.b64encode(cardId)
+
+                       if card_base64 in CARD_BLACKLIST:
+                               logging.info("Blacklisted card base64:%s" % (card_base64,))
+                               return False
+                       
+                       sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+                       sock.connect(DISPENSE_ENDPOINT)
+                       logging.debug('connected to dispsrv')
+                       sockf = sock.makefile()
+                       sockf.write("AUTHIDENT\n"); sockf.flush()
+                       rsp = sockf.readline()
+                       assert "200" in rsp
+                       logging.debug('authenticated')
+                       sockf.write("AUTHCARD %s\n" % (card_base64,)); sockf.flush()
+                       rsp = sockf.readline()
+                       if not "200" in rsp:
+                               logging.info("Rejected card base64:%s" % (card_base64,))
+                               return False
+                       username = rsp.split('=')[1].strip()
+                       logging.info("Accepted card base64:%s for %s" % (card_base64,username,))
+
+                       ## Check for thier username
+                       #try:
+                       #       # Get info from the system (by username)
+                       #       info = pwd.getpwnam(username)
+                       #except KeyError:
+                       #       logging.info('getting info for user \'%s\': user not in password file' % (username,))
+                       #       return False
+                       #self._userid = info.pw_uid
+                       self._userid = None
+                       self._username = username
+               else:
+                       # Get the users ID
+                       self._userid = get_uid(cardId)
+
+                       # Check for thier username
+                       try:
+                               # Get info from the system (by UID)
+                               info = pwd.getpwuid(self._userid)
+                       except KeyError:
+                               logging.info('getting info for uid %d: user not in password file' % (self._userid,))
+                               return False
+                       self._username = info.pw_name
+
+               # If we get this far all is good
+               self._loggedIn = True
+               self._disabled = False
+               return True
+
+        def logOut(self):
+            self._loggedIn = False
+            self._disabled = False
+            self._userId = None
+            self._username = None
 
        def addCard(self, cardId):
-               set_card_id(self._userId, cardId)
+               if not self.isLoggedIn():
+                       return False
+               if DISPSRV_MIFARE:
+                       card_base64 = base64.b64encode(cardId)
+                       if card_base64 in CARD_BLACKLIST:
+                               logging.info("Blacklisted card base64:%s" % (card_base64,))
+                               return False
+                       logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username))
+                       sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+                       sock.connect(DISPENSE_ENDPOINT)
+                       sockf = sock.makefile()
+                       sockf.write("AUTHIDENT\n")
+                       sockf.flush(); rsp = sockf.readline()
+                       assert "200" in rsp
+                       sockf.write("SETEUSER %s\n" % (self._username,))
+                       sockf.flush(); rsp = sockf.readline()
+                       assert "200" in rsp
+                       sockf.write("CARD_ADD %s\n" % (card_base64,))
+                       sockf.flush(); rsp = sockf.readline()
+                       if "200" in rsp:
+                               return True
+                       else:
+                               return False
+               else:
+                       if get_uid(cardId) != None:
+                               return False
+                       else:
+                               logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username))
+                               set_card_id(self._userId, cardId)
+                               return True
 
-        def isLoggedIn(self):
-                return self._loggedIn
+       def isLoggedIn(self):
+               return self._loggedIn
 
-        def getUsername(self):
-                return self._username
+       def getUsername(self):
+               return self._username
 
-        def getBalance(self):
+       def getBalance(self):
                # Balance checking
-                acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
-                # this is fucking appalling
-                balance = acct[acct.find("$")+1:acct.find("(")].strip()
-                return balance
+               if self.isLoggedIn():
+                       acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
+               else:
+                       return None
+               balance = acct[acct.find("$")+1:acct.find("(")].strip()
+               return balance
 
-        def getItemInfo(itemId):
+       def getItemInfo(self, itemId):
+               logging.debug("getItemInfo(%s)" % (itemId,))
                itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId)
                args = ('dispense', 'iteminfo', itemId)
-                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))
+               info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
+               m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
+               if m == None:
+                       return("dead", 0)
+               cents = int(m.group(1))*100 + int(m.group(2))
                # return (name, price in cents)
-                return (m.group(3), cents)
+               return (m.group(3), cents)
 
-        def isDisabled(self):
-               return False
+       def isDisabled(self):
+               return self._disabled
 
-        def dispenseItem(self, itemId):
-               itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId)
-               if itemId == "":
+       def dispenseItem(self, itemId):
+               if not self.isLoggedIn() or self.getItemInfo(itemId)[0] == "dead":
                        return False
                else:
                        print('dispense -u "%s" %s'%(self._username, itemId))
-                       os.system('dispense -u "%s" %s'%(self._username, itemId))
-                       return True
+                       #os.system('dispense -u "%s" %s'%(self._username, itemId))
+                       return True
+
+       def logOut(self):
+               self._username = ""
+               self._disabled = True
+               self._loggedIn = False
+               self._userId = None
 
 """
 This class abstracts the idea of item numbers.
@@ -114,8 +258,20 @@ class OpenDispenseMapping():
 
        @staticmethod
        def vendingMachineToOpenDispense(itemId):
+               logging.debug("vendingMachineToOpenDispense(%s)" % (itemId,))
                _mappingFile = "OpenDispenseMappings.conf"
-               fh = open(_mappingFile)
+               try:
+                       fh = open(_mappingFile)
+               except IOError:
+                       if itemId[1] == '8':
+                               return 'coke:' + itemId[0]
+                       elif itemId[1] == '5':
+                               if itemId[0] == '5':
+                                       return 'door'
+                               else:
+                                       return None
+                       else:
+                               return 'snack:' + itemId
                map = ""
                for line in fh:
                        #line = line.strip()
@@ -124,3 +280,5 @@ class OpenDispenseMapping():
                                print(map)
                return map
 
+
+# vim: noexpandtab ts=4 sw=4

UCC git Repository :: git.ucc.asn.au