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

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