MIFARE Login - Add a blacklist of known-bad cards
[uccvend-vendserver.git] / VendServer / OpenDispense.py
1 """
2 Author: Mitchell Pomery (bobgeorge33)
3
4 System to connect to OpenDispense2.
5 Most of this code has been copied out of VendServer.py, then had variables updated so that it runs.
6 This is so VendServer can easily operate regardless of the current accounting backend.
7 Documentation for this code can be found inder Dispence.DispenceInterface
8 """
9
10 from DispenseInterface import DispenseInterface
11 import os
12 import logging
13 import re
14 import pwd
15 import base64
16 import socket
17 from subprocess import Popen, PIPE
18 from LDAPConnector import get_uid,get_uname, set_card_id
19
20 DISPENSE_ENDPOINT = ("localhost", 11020)
21 DISPSRV_MIFARE = True
22
23 # A list of cards that should never be registered, and should never log in
24 # - Some of these might have been registered before we knew they were duplicates
25 CARD_BLACKLIST = [
26         'AAAAAA==',     # All zeroes, don't allow that.
27         'ISIjJA==', # CommBank credit cards
28         ]
29
30 class OpenDispense(DispenseInterface):
31         _username = ""
32         _disabled = True
33         _loggedIn = False
34         _userId = None
35
36         def __init__(self, username=None, secret=False):
37                 pass
38
39         def authUserIdPin(self, userId, pin):
40                 return self.authUserIdPin_db(userId, pin)
41                 #return self.authUserIdPin_file(userId, pin)
42         
43         def authUserIdPin_db(self, userId, pin):
44                 userId = int(userId)
45
46                 try:
47                         # Get username (TODO: Store the user ID in the dispense database too)
48                         info = pwd.getpwuid(userId)
49                 except KeyError:
50                         logging.info('getting pin for uid %d: user not in password file'%userId)
51                         return False
52                 
53                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
54                 sock.connect(DISPENSE_ENDPOINT)
55                 logging.debug('connected to dispsrv')
56                 sockf = sock.makefile()
57                 sockf.write("AUTHIDENT\n"); sockf.flush()
58                 rsp = sockf.readline()
59                 assert "200" in rsp
60                 logging.debug('authenticated')
61                 sockf.write("PIN_CHECK %s %s\n" % (info.pw_name, pin)); sockf.flush()
62                 rsp = sockf.readline()
63                 if not "200" in rsp:
64                         logging.info('checking pin for uid %d: Server said no - %r' % (userId, rsp))
65                         return False
66                 #Login Successful
67                 logging.info('accepted pin for uid %d \'%s\'' % (userId, info.pw_name))
68                 self._userid = userId
69                 self._loggedIn = True
70                 self._disabled = False
71                 self._username = info.pw_name
72                 return True
73
74         def authUserIdPin_file(self, userId, pin):
75                 userId = int(userId)
76
77                 try:
78                         # Get info from 
79                         info = pwd.getpwuid(userId)
80                 except KeyError:
81                         logging.info('getting pin for uid %d: user not in password file'%userId)
82                         return False
83
84                 if info.pw_dir == None: return False
85                 pinfile = os.path.join(info.pw_dir, '.pin')
86                 try:
87                         s = os.stat(pinfile)
88                 except OSError:
89                         logging.info('getting pin for uid %d: .pin not found in home directory'%userId)
90                         return False
91                 if s.st_mode & 077:
92                         logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%userId)
93                         os.chmod(pinfile, 0600)
94                 try:
95                         f = file(pinfile)
96                 except IOError:
97                         logging.info('getting pin for uid %d: I cannot read pin file'%userId)
98                         return False
99                 pinstr = f.readline().strip()
100                 f.close()
101                 if not re.search('^[0-9]{4}$', pinstr):
102                         logging.info('getting pin for uid %d: %s not a good pin'%(userId,repr(pinstr)))
103                         return False
104
105                 if pinstr == str(pin):
106                         #Login Successful
107                         self._userid = userId
108                         self._loggedIn = True
109                         self._disabled = False
110                         self._username = info.pw_name
111                         return True
112                 
113                 # Login Unsuccessful
114                 return False
115
116         def authMifareCard(self, cardId):
117                 self._loggedIn = False
118                 self._username = None
119                 if DISPSRV_MIFARE:
120                         card_base64 = base64.b64encode(cardId)
121
122                         if card_base64 in CARD_BLACKLIST:
123                                 logging.info("Blacklisted card base64:%s" % (card_base64,))
124                                 return False
125                         
126                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
127                         sock.connect(DISPENSE_ENDPOINT)
128                         logging.debug('connected to dispsrv')
129                         sockf = sock.makefile()
130                         sockf.write("AUTHIDENT\n"); sockf.flush()
131                         rsp = sockf.readline()
132                         assert "200" in rsp
133                         logging.debug('authenticated')
134                         sockf.write("AUTHCARD %s\n" % (card_base64,)); sockf.flush()
135                         rsp = sockf.readline()
136                         if not "200" in rsp:
137                                 logging.info("Rejected card base64:%s" % (card_base64,))
138                                 return False
139                         username = rsp.split('=')[1].strip()
140                         logging.info("Accepted card base64:%s for %s" % (card_base64,username,))
141
142                         ## Check for thier username
143                         #try:
144                         #       # Get info from the system (by username)
145                         #       info = pwd.getpwnam(username)
146                         #except KeyError:
147                         #       logging.info('getting info for user \'%s\': user not in password file' % (username,))
148                         #       return False
149                         #self._userid = info.pw_uid
150                         self._userid = None
151                         self._username = username
152                 else:
153                         # Get the users ID
154                         self._userid = get_uid(cardId)
155
156                         # Check for thier username
157                         try:
158                                 # Get info from the system (by UID)
159                                 info = pwd.getpwuid(self._userid)
160                         except KeyError:
161                                 logging.info('getting info for uid %d: user not in password file' % (self._userid,))
162                                 return False
163                         self._username = info.pw_name
164
165                 # If we get this far all is good
166                 self._loggedIn = True
167                 self._disabled = False
168                 return True
169
170         def logOut(self):
171             self._loggedIn = False
172             self._disabled = False
173             self._userId = None
174             self._username = None
175
176         def addCard(self, cardId):
177                 if not self.isLoggedIn():
178                         return False
179                 if DISPSRV_MIFARE:
180                         card_base64 = base64.b64encode(cardId)
181                         if card_base64 in CARD_BLACKLIST:
182                                 logging.info("Blacklisted card base64:%s" % (card_base64,))
183                                 return False
184                         logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username))
185                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
186                         sock.connect(DISPENSE_ENDPOINT)
187                         sockf = sock.makefile()
188                         sockf.write("AUTHIDENT\n")
189                         sockf.flush(); rsp = sockf.readline()
190                         assert "200" in rsp
191                         sockf.write("SETEUSER %s\n" % (self._username,))
192                         sockf.flush(); rsp = sockf.readline()
193                         assert "200" in rsp
194                         sockf.write("CARD_ADD %s\n" % (card_base64,))
195                         sockf.flush(); rsp = sockf.readline()
196                         if "200" in rsp:
197                                 return True
198                         else:
199                                 return False
200                 else:
201                         if get_uid(cardId) != None:
202                                 return False
203                         else:
204                                 logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username))
205                                 set_card_id(self._userId, cardId)
206                                 return True
207
208         def isLoggedIn(self):
209                 return self._loggedIn
210
211         def getUsername(self):
212                 return self._username
213
214         def getBalance(self):
215                 # Balance checking
216                 if self.isLoggedIn():
217                         acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
218                 else:
219                         return None
220                 balance = acct[acct.find("$")+1:acct.find("(")].strip()
221                 return balance
222
223         def getItemInfo(self, itemId):
224                 logging.debug("getItemInfo(%s)" % (itemId,))
225                 itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId)
226                 args = ('dispense', 'iteminfo', itemId)
227                 info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
228                 m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
229                 if m == None:
230                         return("dead", 0)
231                 cents = int(m.group(1))*100 + int(m.group(2))
232                 # return (name, price in cents)
233                 return (m.group(3), cents)
234
235         def isDisabled(self):
236                 return self._disabled
237
238         def dispenseItem(self, itemId):
239                 if not self.isLoggedIn() or self.getItemInfo(itemId)[0] == "dead":
240                         return False
241                 else:
242                         print('dispense -u "%s" %s'%(self._username, itemId))
243                         #os.system('dispense -u "%s" %s'%(self._username, itemId))
244                         return True
245
246         def logOut(self):
247                 self._username = ""
248                 self._disabled = True
249                 self._loggedIn = False
250                 self._userId = None
251
252 """
253 This class abstracts the idea of item numbers.
254 It removes the need for VendServer to know the mappings between inputted numbers
255 and the equivalent itemId in OpenDispense.
256 """
257 class OpenDispenseMapping():
258
259         @staticmethod
260         def vendingMachineToOpenDispense(itemId):
261                 logging.debug("vendingMachineToOpenDispense(%s)" % (itemId,))
262                 _mappingFile = "OpenDispenseMappings.conf"
263                 try:
264                         fh = open(_mappingFile)
265                 except IOError:
266                         if itemId[1] == '8':
267                                 return 'coke:' + itemId[0]
268                         elif itemId[1] == '5':
269                                 if itemId[0] == '5':
270                                         return 'door'
271                                 else:
272                                         return None
273                         else:
274                                 return 'snack:' + itemId
275                 map = ""
276                 for line in fh:
277                         #line = line.strip()
278                         if line.startswith(str(itemId) + " "):
279                                 map = line[len(str(itemId)) + 1:].strip()
280                                 print(map)
281                 return map
282
283
284 # vim: noexpandtab ts=4 sw=4

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