3 * UCC (University [of WA] Computer Club) Electronic Accounting System
5 * cokebank.c - Coke-Bank management
6 * > Simple custom database format (uses PAM for usernames)
8 * This file is licenced under the 3-clause BSD Licence. See the file COPYING
19 #include <openssl/sha.h>
28 * http://linuxdevcenter.com/pub/a/linux/2003/08/14/libldap.html
29 * - Using libldap, the LDAP Client Library
33 #define USE_UNIX_GROUPS 1
34 #define HACK_TPG_NOAUTH 1
35 #define HACK_ROOT_NOAUTH 1
37 #define indexof(array, ent) (((intptr_t)(ent)-(intptr_t)(array))/sizeof((array)[0]))
54 static int Bank_int_ReadDatabase(void);
55 static int Bank_int_WriteEntry(int ID);
56 int Bank_int_AlterUserBalance(int ID, int Delta);
57 int Bank_int_GetMinAllowedBalance(int ID);
58 int Bank_int_AddUser(const char *Username);
59 int Bank_int_GetUnixID(const char *Username);
61 char *ReadLDAPValue(const char *Filter, char *Value);
63 void HexBin(uint8_t *Dest, int BufSize, const char *Src);
68 char *gsLDAPPath = "ldapi:///";
74 tUser **gaBank_UsersByName;
75 tUser **gaBank_UsersByBalance;
79 * \brief Load the cokebank database
81 int Bank_Initialise(const char *Argument)
88 gBank_File = fopen(Argument, "rb+");
89 if( !gBank_File ) gBank_File = fopen(Argument, "wb+");
91 perror("Opening coke bank");
94 Bank_int_ReadDatabase();
97 // TODO: Do I need this?
98 gBank_LogFile = fopen("cokebank.log", "a");
99 if( !gBank_LogFile ) gBank_LogFile = stdout;
104 rv = ldap_create(&gpLDAP);
106 fprintf(stderr, "ldap_create: %s\n", ldap_err2string(rv));
109 rv = ldap_initialize(&gpLDAP, gsLDAPPath);
111 fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rv));
114 { int ver = LDAP_VERSION3; ldap_set_option(gpLDAP, LDAP_OPT_PROTOCOL_VERSION, &ver); }
116 rv = ldap_start_tls_s(gpLDAP, NULL, NULL);
118 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
124 struct berval *servcred;
125 cred.bv_val = "secret";
127 rv = ldap_sasl_bind_s(gpLDAP, "cn=admin,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
128 "", &cred, NULL, NULL, &servcred);
130 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
140 * \brief Name compare function for qsort
142 static int Bank_int_CompareNames(const void *Ent1, const void *Ent2)
144 const tUser *user1 = *(const tUser**)Ent1;
145 const tUser *user2 = *(const tUser**)Ent2;
147 return strcmp(user1->Name, user2->Name);
151 * \brief Name compare function for qsort
153 static int Bank_int_CompareBalance(const void *Ent1, const void *Ent2)
155 const tUser *user1 = *(const tUser**)Ent1;
156 const tUser *user2 = *(const tUser**)Ent2;
158 return user1->Balance - user2->Balance;
162 static int Bank_int_ReadDatabase(void)
165 if( gaBank_Users ) return 1;
168 fseek(gBank_File, 0, SEEK_END);
169 giBank_NumUsers = ftell(gBank_File) / sizeof(tFileUser);
170 fseek(gBank_File, 0, SEEK_SET);
172 // Allocate structures
173 gaBank_Users = malloc( giBank_NumUsers * sizeof(tUser) );
174 gaBank_UsersByName = malloc( giBank_NumUsers * sizeof(tUser*) );
175 gaBank_UsersByBalance = malloc( giBank_NumUsers * sizeof(tUser*) );
177 for( i = 0; i < giBank_NumUsers; i ++ )
180 fread(&fu, sizeof(tFileUser), 1, gBank_File);
181 gaBank_Users[i].Name = NULL;
182 gaBank_Users[i].UnixID = fu.UnixID;
183 gaBank_Users[i].Balance = fu.Balance;
184 gaBank_Users[i].Flags = fu.Flags;
185 gaBank_Users[i].Name = Bank_GetAcctName(i);
186 gaBank_UsersByName[i] = &gaBank_Users[i]; // Add to name index
187 gaBank_UsersByBalance[i] = &gaBank_Users[i]; // Add to balance index
191 qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
192 qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
197 static int Bank_int_ReadDatabase(void)
200 // Alternate data format
202 // <username>,<uid>,<pin>,<lastused>,<balance>,<flags>,<altlogins...>
203 fseek(gBank_File, 0, SEEK_SET);
207 fgets(buf, BUFSIZ-1, gBank_File);
212 static int Bank_int_WriteEntry(int ID)
215 if( ID < 0 || ID >= giBank_NumUsers ) {
220 fseek(gBank_File, ID*sizeof(fu), SEEK_SET);
222 fu.UnixID = gaBank_Users[ID].UnixID;
223 fu.Balance = gaBank_Users[ID].Balance;
224 fu.Flags = gaBank_Users[ID].Flags;
226 fwrite(&fu, sizeof(fu), 1, gBank_File);
232 * Transfers money from one user to another
233 * \param SourceUser Source user
234 * \param DestUser Destination user
235 * \param Ammount Ammount of cents to move from \a SourceUser to \a DestUser
236 * \param Reason Reason for the transfer (essentially a comment)
237 * \return Boolean failure
239 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
241 int srcBal = Bank_GetBalance(SourceUser);
242 int dstBal = Bank_GetBalance(DestUser);
244 if( srcBal - Ammount < Bank_int_GetMinAllowedBalance(SourceUser) )
246 if( dstBal + Ammount < Bank_int_GetMinAllowedBalance(DestUser) )
248 Bank_int_AlterUserBalance(DestUser, Ammount);
249 Bank_int_AlterUserBalance(SourceUser, -Ammount);
250 fprintf(gBank_LogFile, "Transfer %ic #%i{%i} > #%i{%i} [%i, %i] (%s)\n",
251 Ammount, SourceUser, srcBal, DestUser, dstBal,
252 srcBal - Ammount, dstBal + Ammount, Reason);
256 int Bank_CreateAcct(const char *Name)
260 ret = Bank_GetAcctByName(Name);
261 if( ret != -1 ) return -1;
263 return Bank_int_AddUser(Name);
266 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
270 ret = calloc( 1, sizeof(tAcctIterator) );
273 ret->MinBalance = INT_MIN;
274 ret->MaxBalance = INT_MAX;
276 ret->FlagMask = FlagMask;
277 ret->FlagValue = FlagValues & FlagMask;
279 if(Flags & BANK_ITFLAG_MINBALANCE)
280 ret->MinBalance = MinMaxBalance;
281 if(Flags & BANK_ITFLAG_MAXBALANCE)
282 ret->MaxBalance = MinMaxBalance;
284 ret->Sort = Flags & (BANK_ITFLAG_SORTMASK|BANK_ITFLAG_REVSORT);
289 //if(Flags & BANK_ITFLAG_SEENBEFORE)
290 // ret->MinBalance = MinMaxBalance;
291 //if(Flags & BANK_ITFLAG_SEENAFTER)
292 // ret->MinBalance = MinMaxBalance;
297 int Bank_IteratorNext(tAcctIterator *It)
301 while(It->CurUser < giBank_NumUsers)
305 case BANK_ITFLAG_SORT_NONE:
306 case BANK_ITFLAG_SORT_NONE | BANK_ITFLAG_REVSORT:
309 case BANK_ITFLAG_SORT_NAME:
310 ret = indexof(gaBank_Users, gaBank_UsersByName[It->CurUser]);
312 case BANK_ITFLAG_SORT_NAME | BANK_ITFLAG_REVSORT:
313 ret = indexof(gaBank_Users, gaBank_UsersByName[giBank_NumUsers-It->CurUser]);
315 case BANK_ITFLAG_SORT_BAL:
316 ret = indexof(gaBank_Users, gaBank_UsersByBalance[It->CurUser]);
317 printf("Sort by balance (ret = %i)\n", ret);
319 case BANK_ITFLAG_SORT_BAL | BANK_ITFLAG_REVSORT:
320 ret = indexof(gaBank_Users, gaBank_UsersByBalance[giBank_NumUsers-It->CurUser]);
323 fprintf(stderr, "BUG: Unsupported sort in Bank_IteratorNext\n");
328 if( gaBank_Users[ret].Balance < It->MinBalance )
330 if( gaBank_Users[ret].Balance > It->MaxBalance )
332 if( (gaBank_Users[ret].Flags & It->FlagMask) != It->FlagValue )
340 void Bank_DelIterator(tAcctIterator *It)
346 * \brief Get the User ID of the named user
348 int Bank_GetAcctByName(const char *Username)
352 i = giBank_NumUsers / 2;
353 size = giBank_NumUsers;
356 cmp = strcmp(gaBank_UsersByName[i]->Name, Username);
358 return indexof(gaBank_Users, gaBank_UsersByName[i];
372 uid = Bank_int_GetUnixID(Username);
374 // Expensive search :(
375 for( i = 0; i < giBank_NumUsers; i ++ )
377 if( gaBank_Users[i].UnixID == uid )
385 int Bank_GetBalance(int ID)
387 if( ID < 0 || ID >= giBank_NumUsers )
390 return gaBank_Users[ID].Balance;
393 int Bank_GetFlags(int ID)
395 if( ID < 0 || ID >= giBank_NumUsers )
399 if( gaBank_Users[ID].UnixID == 0 ) {
400 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL|USER_FLAG_COKE;
404 // TODO: Implement checking the PAM groups and status instead, then
405 // fall back on the database. (and update if there is a difference)
406 if( gaBank_Users[ID].UnixID > 0 )
413 pwd = getpwuid( gaBank_Users[ID].UnixID );
415 // Check for additions to the "coke" group
416 grp = getgrnam("coke");
418 for( i = 0; grp->gr_mem[i]; i ++ )
420 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
421 gaBank_Users[ID].Flags |= USER_FLAG_COKE;
427 // Check for additions to the "wheel" group
428 grp = getgrnam("wheel");
430 for( i = 0; grp->gr_mem[i]; i ++ )
432 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
433 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL;
441 return gaBank_Users[ID].Flags;
444 int Bank_SetFlags(int ID, int Mask, int Value)
447 if( ID < 0 || ID >= giBank_NumUsers )
450 // Silently ignore changes to root and meta accounts
451 if( gaBank_Users[ID].UnixID <= 0 ) return 0;
453 gaBank_Users[ID].Flags &= ~Mask;
454 gaBank_Users[ID].Flags |= Value;
456 Bank_int_WriteEntry(ID);
461 int Bank_int_AlterUserBalance(int ID, int Delta)
464 if( ID < 0 || ID >= giBank_NumUsers )
468 gaBank_Users[ID].Balance += Delta;
470 Bank_int_WriteEntry(ID);
475 int Bank_int_GetMinAllowedBalance(int ID)
478 if( ID < 0 || ID >= giBank_NumUsers )
481 flags = Bank_GetFlags(ID);
483 // Internal accounts have no limit
484 if( (flags & USER_FLAG_INTERNAL) )
487 // Wheel is allowed to go to -$100
488 if( (flags & USER_FLAG_WHEEL) )
491 // Coke is allowed to go to -$20
492 if( (flags & USER_FLAG_COKE) )
495 // For everyone else, no negative
500 * Create a new user in our database
502 int Bank_int_AddUser(const char *Username)
505 int uid = Bank_int_GetUnixID(Username);
507 // Can has moar space plz?
509 tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
510 if( !tmp ) return -1;
513 tmp = realloc(gaBank_UsersByName, (giBank_NumUsers+1)*sizeof(tUser*));
514 if( !tmp ) return -1;
515 gaBank_UsersByName = tmp;
517 tmp = realloc(gaBank_UsersByBalance, (giBank_NumUsers+1)*sizeof(tUser*));
518 if( !tmp ) return -1;
519 gaBank_UsersByBalance = tmp;
522 gaBank_Users[giBank_NumUsers].Name = NULL;
523 gaBank_Users[giBank_NumUsers].UnixID = uid;
524 gaBank_Users[giBank_NumUsers].Balance = 0;
525 gaBank_Users[giBank_NumUsers].Flags = 0;
526 gaBank_UsersByName[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
527 gaBank_UsersByBalance[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
530 if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
531 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
533 else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
534 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
536 else if( strcmp(Username, "root") == 0 ) {
537 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_WHEEL|USER_FLAG_COKE;
544 gaBank_Users[giBank_NumUsers-1].Name = Bank_GetAcctName(giBank_NumUsers-1);
547 qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
548 qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
551 Bank_int_WriteEntry(giBank_NumUsers - 1);
557 // Unix user dependent code
558 // TODO: Modify to keep its own list of usernames
560 char *Bank_GetAcctName(int ID)
564 if( ID < 0 || ID >= giBank_NumUsers )
567 if( gaBank_Users[ID].Name ) {
568 return strdup(gaBank_Users[ID].Name);
571 if( gaBank_Users[ID].UnixID == -1 )
572 return strdup(COKEBANK_SALES_ACCT);
574 if( gaBank_Users[ID].UnixID == -2 )
575 return strdup(COKEBANK_DEBT_ACCT);
577 pwd = getpwuid(gaBank_Users[ID].UnixID);
578 if( !pwd ) return NULL;
580 return strdup(pwd->pw_name);
583 int Bank_int_GetUnixID(const char *Username)
587 if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) { // Pseudo account that sales are made into
590 else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) { // Pseudo acount that money is added from
596 pwd = getpwnam(Username);
597 if( !pwd ) return -1;
605 * Authenticate a user
607 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
612 int ofs = strlen(Username) + strlen(Salt);
613 char input[ ofs + 40 + 1];
614 char tmp[4 + strlen(Username) + 1]; // uid=%s
619 // Only here to shut GCC up (until password auth is implemented)
622 if( Password == NULL )
627 if( strcmp(Username, "tpg") == 0 )
628 return Bank_GetAcctByName("tpg");
631 if( strcmp(Username, "root") == 0 ) {
632 int ret = Bank_GetAcctByName("root");
634 return Bank_CreateAcct("root");
640 HexBin(hash, 20, Password);
642 // Build string to hash
643 strcpy(input, Username);
646 // TODO: Get user's SHA-1 hash
647 sprintf(tmp, "uid=%s", Username);
648 printf("tmp = '%s'\n", tmp);
649 passhash = ReadLDAPValue(tmp, "userPassword");
653 printf("LDAP hash '%s'\n", passhash);
655 sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
656 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
657 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
659 // Then create the hash from the provided salt
660 // Compare that with the provided hash
665 printf("Password hash ");
667 printf("%02x", hash[i]&0xFF);
678 char *ReadLDAPValue(const char *Filter, char *Value)
680 LDAPMessage *res, *res2;
681 struct berval **attrValues;
682 char *attrNames[] = {Value,NULL};
684 struct timeval timeout;
690 rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
691 attrNames, 0, NULL, NULL, &timeout, 1, &res
693 printf("ReadLDAPValue: rv = %i\n", rv);
695 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
702 res2 = ldap_first_entry(gpLDAP, res);
703 attrValues = ldap_get_values_len(gpLDAP, res2, Value);
705 ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
707 ldap_value_free_len(attrValues);
714 // TODO: Move to another file
715 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
718 for( i = 0; i < BufSize; i ++ )
722 if('0' <= *Src && *Src <= '9')
723 val |= (*Src-'0') << 4;
724 else if('A' <= *Src && *Src <= 'F')
725 val |= (*Src-'A'+10) << 4;
726 else if('a' <= *Src && *Src <= 'f')
727 val |= (*Src-'a'+10) << 4;
732 if('0' <= *Src && *Src <= '9')
734 else if('A' <= *Src && *Src <= 'F')
735 val |= (*Src-'A'+10);
736 else if('a' <= *Src && *Src <= 'f')
737 val |= (*Src-'a'+10);
744 for( ; i < BufSize; i++ )