0ce822341e5a886561769e0e7e029c38bfd0f3c7
[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                 self._loggedIn = False
76                 self._username = None
77                 if DISPSRV_MIFARE:
78                         card_base64 = base64.b64encode(cardId)
79                         
80                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
81                         sock.connect(DISPENSE_ENDPOINT)
82                         logging.debug('connected to dispsrv')
83                         sockf = sock.makefile()
84                         sockf.write("AUTHIDENT\n"); sockf.flush()
85                         rsp = sockf.readline()
86                         assert "200" in rsp
87                         logging.debug('authenticated')
88                         sockf.write("AUTHCARD %s\n" % (card_base64,)); sockf.flush()
89                         rsp = sockf.readline()
90                         if not "200" in rsp:
91                                 return False
92                         username = rsp.split('=')[1].strip()
93
94                         # Check for thier username
95                         try:
96                                 # Get info from the system (by username)
97                                 info = pwd.getpwnam(username)
98                         except KeyError:
99                                 logging.info('getting info for user \'%s\': user not in password file' % (username,))
100                                 return False
101                 else:
102                         # Get the users ID
103                         self._userid = get_uid(cardId)
104
105                         # Check for thier username
106                         try:
107                                 # Get info from the system (by UID)
108                                 info = pwd.getpwuid(self._userid)
109                         except KeyError:
110                                 logging.info('getting info for uid %d: user not in password file' % (self._userid,))
111                                 return False
112
113                 # If we get this far all is good
114                 self._loggedIn = True
115                 self._disabled = False
116                 self._userid = info.pw_uid
117                 self._username = info.pw_name
118                 return True
119
120         def addCard(self, cardId):
121                 if not self.isLoggedIn():
122                         return False
123                 if DISPSRV_MIFARE:
124                         card_base64 = base64.b64encode(cardId)
125                         logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username))
126                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
127                         sock.connect(DISPENSE_ENDPOINT)
128                         sockf = sock.makefile()
129                         sockf.write("AUTHIDENT\n")
130                         sockf.flush(); rsp = sockf.readline()
131                         assert "200" in rsp
132                         sockf.write("SETEUSER %s\n" % (self._username,))
133                         sockf.flush(); rsp = sockf.readline()
134                         assert "200" in rsp
135                         sockf.write("CARD_ADD %s\n" % (card_base64,))
136                         sockf.flush(); rsp = sockf.readline()
137                         if "200" in rsp:
138                                 return True
139                         else:
140                                 return False
141                 else:
142                         if get_uid(cardId) != None:
143                                 return False
144                         else:
145                                 logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username))
146                                 set_card_id(self._userId, cardId)
147                                 return True
148
149         def isLoggedIn(self):
150                 return self._loggedIn
151
152         def getUsername(self):
153                 return self._username
154
155         def getBalance(self):
156                 # Balance checking
157                 if self.isLoggedIn():
158                         acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
159                 else:
160                         return None
161                 balance = acct[acct.find("$")+1:acct.find("(")].strip()
162                 return balance
163
164         def getItemInfo(self, itemId):
165                 logging.debug("getItemInfo(%s)" % (itemId,))
166                 itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId)
167                 args = ('dispense', 'iteminfo', itemId)
168                 info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
169                 m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
170                 if m == None:
171                         return("dead", 0)
172                 cents = int(m.group(1))*100 + int(m.group(2))
173                 # return (name, price in cents)
174                 return (m.group(3), cents)
175
176         def isDisabled(self):
177                 return self._disabled
178
179         def dispenseItem(self, itemId):
180                 if not self.isLoggedIn() or self.getItemInfo(itemId)[0] == "dead":
181                         return False
182                 else:
183                         print('dispense -u "%s" %s'%(self._username, itemId))
184                         #os.system('dispense -u "%s" %s'%(self._username, itemId))
185                         return True
186
187         def logOut(self):
188                 self._username = ""
189                 self._disabled = True
190                 self._loggedIn = False
191                 self._userId = None
192
193 """
194 This class abstracts the idea of item numbers.
195 It removes the need for VendServer to know the mappings between inputted numbers
196 and the equivalent itemId in OpenDispense.
197 """
198 class OpenDispenseMapping():
199
200         @staticmethod
201         def vendingMachineToOpenDispense(itemId):
202                 logging.debug("vendingMachineToOpenDispense(%s)" % (itemId,))
203                 _mappingFile = "OpenDispenseMappings.conf"
204                 try:
205                         fh = open(_mappingFile)
206                 except IOError:
207                         if itemId[1] == '8':
208                                 return 'coke:' + itemId[0]
209                         elif itemId[1] == '5':
210                                 if itemId[0] == '5':
211                                         return 'door'
212                                 else:
213                                         return None
214                         else:
215                                 return 'snack:' + itemId
216                 map = ""
217                 for line in fh:
218                         #line = line.strip()
219                         if line.startswith(str(itemId) + " "):
220                                 map = line[len(str(itemId)) + 1:].strip()
221                                 print(map)
222                 return map
223

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