Cleaned up cokebank_basic a bit
[tpg/opendispense2.git] / src / cokebank_basic / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * cokebank.c - Coke-Bank management
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file COPYING
8  * for full details.
9  */
10 #include <stdlib.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <string.h>
15 #include <limits.h>
16 #include <pwd.h>
17 #include <grp.h>
18 #include <openssl/sha.h>
19 #include "common.h"
20 #if USE_LDAP
21 # include <ldap.h>
22 #endif
23
24 /*
25  * NOTES:
26  * 
27  * http://linuxdevcenter.com/pub/a/linux/2003/08/14/libldap.html
28  * - Using libldap, the LDAP Client Library 
29  * 
30  */
31
32 #define USE_UNIX_GROUPS 1
33 #define HACK_TPG_NOAUTH 1
34 #define HACK_ROOT_NOAUTH        1
35
36 // === PROTOTYPES ===
37 void    Init_Cokebank(const char *Argument);
38 static int Bank_int_ReadDatabase(void);
39 static int Bank_int_WriteEntry(int ID);
40  int    Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
41  int    Bank_CreateUser(const char *Username);
42  int    Bank_GetMaxID(void);
43  int    Bank_GetUserID(const char *Username);
44  int    Bank_GetBalance(int User);
45  int    Bank_GetFlags(int User);
46  int    Bank_SetFlags(int User, int Mask, int Value);
47  int    Bank_int_AlterUserBalance(int ID, int Delta);
48  int    Bank_int_GetMinAllowedBalance(int ID);
49  int    Bank_int_AddUser(const char *Username);
50 char    *Bank_GetUserName(int User);
51  int    Bank_int_GetUnixID(const char *Username);
52  int    Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString);
53 #if USE_LDAP
54 char    *ReadLDAPValue(const char *Filter, char *Value);
55 #endif
56 void    HexBin(uint8_t *Dest, int BufSize, const char *Src);
57
58 // === GLOBALS ===
59 FILE    *gBank_LogFile;
60 #if USE_LDAP
61 char    *gsLDAPPath = "ldapi:///";
62 LDAP    *gpLDAP;
63 #endif
64 tUser   *gaBank_Users;
65  int    giBank_NumUsers;
66 FILE    *gBank_File;
67
68 // === CODE ===
69 /**
70  * \brief Load the cokebank database
71  */
72 void Init_Cokebank(const char *Argument)
73 {
74         #if USE_LDAP
75          int    rv;
76         #endif
77         
78         // Open Cokebank
79         gBank_File = fopen(Argument, "rb+");
80         if( !gBank_File )       gBank_File = fopen(Argument, "wb+");
81         if( !gBank_File )       perror("Opening coke bank");
82         Bank_int_ReadDatabase();
83
84         // Open log file
85         // TODO: Do I need this?
86         gBank_LogFile = fopen("cokebank.log", "a");
87         if( !gBank_LogFile )    gBank_LogFile = stdout;
88         
89         
90         #if USE_LDAP
91         // Connect to LDAP
92         rv = ldap_create(&gpLDAP);
93         if(rv) {
94                 fprintf(stderr, "ldap_create: %s\n", ldap_err2string(rv));
95                 exit(1);
96         }
97         rv = ldap_initialize(&gpLDAP, gsLDAPPath);
98         if(rv) {
99                 fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rv));
100                 exit(1);
101         }
102         { int ver = LDAP_VERSION3; ldap_set_option(gpLDAP, LDAP_OPT_PROTOCOL_VERSION, &ver); }
103         # if 0
104         rv = ldap_start_tls_s(gpLDAP, NULL, NULL);
105         if(rv) {
106                 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
107                 exit(1);
108         }
109         # endif
110         {
111                 struct berval   cred;
112                 struct berval   *servcred;
113                 cred.bv_val = "secret";
114                 cred.bv_len = 6;
115                 rv = ldap_sasl_bind_s(gpLDAP, "cn=admin,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
116                         "", &cred, NULL, NULL, &servcred);
117                 if(rv) {
118                         fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
119                         exit(1);
120                 }
121         }
122         #endif
123 }
124
125 #if 1
126 static int Bank_int_ReadDatabase(void)
127 {
128         if( gaBank_Users )      return 1;
129         
130         // Get size
131         fseek(gBank_File, 0, SEEK_END);
132         giBank_NumUsers = ftell(gBank_File) / sizeof(gaBank_Users[0]);
133         fseek(gBank_File, 0, SEEK_SET);
134         // Read data
135         gaBank_Users = malloc( giBank_NumUsers * sizeof(gaBank_Users[0]) );
136         fread(gaBank_Users, sizeof(gaBank_Users[0]), giBank_NumUsers, gBank_File);
137         
138         return 0;
139 }
140 #else
141 static int Bank_int_ReadDatabase(void)
142 {
143         char    buf[BUFSIZ];
144         // Alternate data format
145         // > Plain Text
146         // <username>,<uid>,<pin>,<lastused>,<balance>,<flags>,<altlogins...>
147         fseek(gBank_File, 0, SEEK_SET);
148         
149         while(1)
150         {
151                 fgets(buf, BUFSIZ-1, gBank_File);
152         }
153         #endif
154 }
155 #endif
156
157 static int Bank_int_WriteEntry(int ID)
158 {
159         if( ID < 0 || ID >= giBank_NumUsers ) {
160                 return -1;
161         }
162         
163         // Commit to file
164         fseek(gBank_File, ID*sizeof(gaBank_Users[0]), SEEK_SET);
165         fwrite(&gaBank_Users[ID], sizeof(gaBank_Users[0]), 1, gBank_File);
166         
167         return 0;
168 }
169
170 /**
171  * \brief Transfers money from one user to another
172  * \param SourceUser    Source user
173  * \param DestUser      Destination user
174  * \param Ammount       Ammount of cents to move from \a SourceUser to \a DestUser
175  * \param Reason        Reason for the transfer (essentially a comment)
176  * \return Boolean failure
177  */
178 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
179 {
180          int    srcBal = Bank_GetBalance(SourceUser);
181          int    dstBal = Bank_GetBalance(DestUser);
182         
183         if( srcBal - Ammount < Bank_int_GetMinAllowedBalance(SourceUser) )
184                 return 1;
185         if( dstBal + Ammount < Bank_int_GetMinAllowedBalance(DestUser) )
186                 return 1;
187         Bank_int_AlterUserBalance(DestUser, Ammount);
188         Bank_int_AlterUserBalance(SourceUser, -Ammount);
189         fprintf(gBank_LogFile, "Transfer %ic #%i{%i} > #%i{%i} [%i, %i] (%s)\n",
190                 Ammount, SourceUser, srcBal, DestUser, dstBal,
191                 srcBal - Ammount, dstBal + Ammount, Reason);
192         return 0;
193 }
194
195 int Bank_CreateUser(const char *Username)
196 {
197          int    ret;
198         
199         ret = Bank_GetUserID(Username);
200         if( ret != -1 ) return -1;
201         
202         return Bank_int_AddUser(Username);
203 }
204
205 int Bank_GetMaxID(void)
206 {
207         return giBank_NumUsers;
208 }
209
210 /**
211  * \brief Get the User ID of the named user
212  */
213 int Bank_GetUserID(const char *Username)
214 {
215          int    i, uid;
216         
217         uid = Bank_int_GetUnixID(Username);
218         
219         // Expensive search :(
220         for( i = 0; i < giBank_NumUsers; i ++ )
221         {
222                 if( gaBank_Users[i].UnixID == uid )
223                         return i;
224         }
225
226         return -1;
227 }
228
229 int Bank_GetBalance(int ID)
230 {
231         if( ID < 0 || ID >= giBank_NumUsers )
232                 return INT_MIN;
233
234         return gaBank_Users[ID].Balance;
235 }
236
237 int Bank_GetFlags(int ID)
238 {
239         if( ID < 0 || ID >= giBank_NumUsers )
240                 return -1;
241
242         // root
243         if( gaBank_Users[ID].UnixID == 0 ) {
244                 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL|USER_FLAG_COKE;
245         }
246
247         #if USE_UNIX_GROUPS
248         // TODO: Implement checking the PAM groups and status instead, then
249         // fall back on the database. (and update if there is a difference)
250         if( gaBank_Users[ID].UnixID > 0 )
251         {
252                 struct passwd   *pwd;
253                 struct group    *grp;
254                  int    i;
255                 
256                 // Get username
257                 pwd = getpwuid( gaBank_Users[ID].UnixID );
258                 
259                 // Check for additions to the "coke" group
260                 grp = getgrnam("coke");
261                 if( grp ) {
262                         for( i = 0; grp->gr_mem[i]; i ++ )
263                         {
264                                 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
265                                         gaBank_Users[ID].Flags |= USER_FLAG_COKE;
266                                         break ;
267                                 }
268                         }
269                 }
270                 
271                 // Check for additions to the "wheel" group
272                 grp = getgrnam("wheel");
273                 if( grp ) {
274                         for( i = 0; grp->gr_mem[i]; i ++ )
275                         {
276                                 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
277                                         gaBank_Users[ID].Flags |= USER_FLAG_WHEEL;
278                                         break ;
279                                 }
280                         }
281                 }
282         }
283         #endif
284
285         return gaBank_Users[ID].Flags;
286 }
287
288 int Bank_SetFlags(int ID, int Mask, int Value)
289 {
290         // Sanity
291         if( ID < 0 || ID >= giBank_NumUsers )
292                 return -1;
293         
294         // Silently ignore changes to root and meta accounts
295         if( gaBank_Users[ID].UnixID <= 0 )      return 0;
296         
297         gaBank_Users[ID].Flags &= ~Mask;
298         gaBank_Users[ID].Flags |= Value;
299
300         Bank_int_WriteEntry(ID);
301         
302         return 0;
303 }
304
305 int Bank_int_AlterUserBalance(int ID, int Delta)
306 {
307         // Sanity
308         if( ID < 0 || ID >= giBank_NumUsers )
309                 return -1;
310
311         // Update
312         gaBank_Users[ID].Balance += Delta;
313
314         Bank_int_WriteEntry(ID);
315         
316         return 0;
317 }
318
319 int Bank_int_GetMinAllowedBalance(int ID)
320 {
321          int    flags;
322         if( ID < 0 || ID >= giBank_NumUsers )
323                 return 0;
324
325         flags = Bank_GetFlags(ID);
326
327         // Internal accounts have no limit
328         if( (flags & USER_FLAG_INTERNAL) )
329                 return INT_MIN;
330
331         // Wheel is allowed to go to -$100
332         if( (flags & USER_FLAG_WHEEL) )
333                 return -10000;
334         
335         // Coke is allowed to go to -$20
336         if( (flags & USER_FLAG_COKE) )
337                 return -2000;
338
339         // For everyone else, no negative
340         return 0;
341 }
342
343 /**
344  * \brief Create a new user in our database
345  */
346 int Bank_int_AddUser(const char *Username)
347 {
348         void    *tmp;
349          int    uid = Bank_int_GetUnixID(Username);
350
351         // Can has moar space plz?
352         tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
353         if( !tmp )      return -1;
354         gaBank_Users = tmp;
355
356         // Crete new user
357         gaBank_Users[giBank_NumUsers].UnixID = uid;
358         gaBank_Users[giBank_NumUsers].Balance = 0;
359         gaBank_Users[giBank_NumUsers].Flags = 0;
360         
361         if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
362                 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
363         }
364         else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
365                 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
366         }
367         else if( strcmp(Username, "root") == 0 ) {
368                 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_WHEEL|USER_FLAG_COKE;
369         }
370
371         // Increment count
372         giBank_NumUsers ++;
373         
374         Bank_int_WriteEntry(giBank_NumUsers - 1);
375
376         return 0;
377 }
378
379 // ---
380 // Unix user dependent code
381 // TODO: Modify to keep its own list of usernames
382 // ---
383 /**
384  * \brief Return the name the passed user
385  */
386 char *Bank_GetUserName(int ID)
387 {
388         struct passwd   *pwd;
389         
390         if( ID < 0 || ID >= giBank_NumUsers )
391                 return NULL;
392         
393         if( gaBank_Users[ID].UnixID == -1 )
394                 return strdup(COKEBANK_SALES_ACCT);
395
396         if( gaBank_Users[ID].UnixID == -2 )
397                 return strdup(COKEBANK_DEBT_ACCT);
398
399         pwd = getpwuid(gaBank_Users[ID].UnixID);
400         if( !pwd )      return NULL;
401
402         return strdup(pwd->pw_name);
403 }
404
405 int Bank_int_GetUnixID(const char *Username)
406 {
407          int    uid;
408
409         if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {      // Pseudo account that sales are made into
410                 uid = -1;
411         }
412         else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {  // Pseudo acount that money is added from
413                 uid = -2;
414         }
415         else {
416                 struct passwd   *pwd;
417                 // Get user ID
418                 pwd = getpwnam(Username);
419                 if( !pwd )      return -1;
420                 uid = pwd->pw_uid;
421         }
422         return uid;
423 }
424
425
426 /**
427  * \brief Authenticate a user
428  * \return User ID, or -1 if authentication failed
429  */
430 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString)
431 {
432         #if USE_LDAP
433         uint8_t hash[20];
434         uint8_t h[20];
435          int    ofs = strlen(Username) + strlen(Salt);
436         char    input[ ofs + 40 + 1];
437         char    tmp[4 + strlen(Username) + 1];  // uid=%s
438         char    *passhash;
439         #endif
440         
441         #if 1
442         // Only here to shut GCC up (until password auth is implemented
443         if( Salt == NULL )
444                 return -1;
445         if( PasswordString == NULL )
446                 return -1;
447         #endif
448         
449         #if HACK_TPG_NOAUTH
450         if( strcmp(Username, "tpg") == 0 )
451                 return Bank_GetUserID("tpg");
452         #endif
453         #if HACK_ROOT_NOAUTH
454         if( strcmp(Username, "root") == 0 ) {
455                 int ret = Bank_GetUserID("root");
456                 if( ret == -1 )
457                         return Bank_CreateUser("root");
458                 return ret;
459         }
460         #endif
461         
462         #if USE_LDAP
463         HexBin(hash, 20, PasswordString);
464         
465         // Build string to hash
466         strcpy(input, Username);
467         strcpy(input, Salt);
468         
469         // TODO: Get user's SHA-1 hash
470         sprintf(tmp, "uid=%s", Username);
471         printf("tmp = '%s'\n", tmp);
472         passhash = ReadLDAPValue(tmp, "userPassword");
473         if( !passhash ) {
474                 return -1;
475         }
476         printf("LDAP hash '%s'\n", passhash);
477         
478         sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
479                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
480                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
481                 );
482         // Then create the hash from the provided salt
483         // Compare that with the provided hash
484
485         # if 1
486         {
487                  int    i;
488                 printf("Password hash ");
489                 for(i=0;i<20;i++)
490                         printf("%02x", hash[i]&0xFF);
491                 printf("\n");
492         }
493         # endif
494         
495         #endif
496         
497         return -1;
498 }
499
500 #if USE_LDAP
501 char *ReadLDAPValue(const char *Filter, char *Value)
502 {
503         LDAPMessage     *res, *res2;
504         struct berval **attrValues;
505         char    *attrNames[] = {Value,NULL};
506         char    *ret;
507         struct timeval  timeout;
508          int    rv;
509         
510         timeout.tv_sec = 5;
511         timeout.tv_usec = 0;
512         
513         rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
514                 attrNames, 0, NULL, NULL, &timeout, 1, &res
515                 );
516         printf("ReadLDAPValue: rv = %i\n", rv);
517         if(rv) {
518                 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
519                         Value, Filter,
520                         ldap_err2string(rv)
521                         );
522                 return NULL;
523         }
524         
525         res2 = ldap_first_entry(gpLDAP, res);
526         attrValues = ldap_get_values_len(gpLDAP, res2, Value);
527         
528         ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
529         
530         ldap_value_free_len(attrValues);
531         
532         
533         return ret;
534 }
535 #endif
536
537 // TODO: Move to another file
538 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
539 {
540          int    i;
541         for( i = 0; i < BufSize; i ++ )
542         {
543                 uint8_t val = 0;
544                 
545                 if('0' <= *Src && *Src <= '9')
546                         val |= (*Src-'0') << 4;
547                 else if('A' <= *Src && *Src <= 'F')
548                         val |= (*Src-'A'+10) << 4;
549                 else if('a' <= *Src && *Src <= 'f')
550                         val |= (*Src-'a'+10) << 4;
551                 else
552                         break;
553                 Src ++;
554                 
555                 if('0' <= *Src && *Src <= '9')
556                         val |= (*Src-'0');
557                 else if('A' <= *Src && *Src <= 'F')
558                         val |= (*Src-'A'+10);
559                 else if('a' <= *Src && *Src <= 'f')
560                         val |= (*Src-'a'+10);
561                 else
562                         break;
563                 Src ++;
564                 
565                 Dest[i] = val;
566         }
567         for( ; i < BufSize; i++ )
568                 Dest[i] = 0;
569 }
570

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