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

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