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

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