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_ADMIN|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;
428 // Check for additions to the "wheel" group
429 grp = getgrnam("wheel");
431 for( i = 0; grp->gr_mem[i]; i ++ )
433 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
434 gaBank_Users[ID].Flags |= USER_FLAG_ADMIN;
443 return gaBank_Users[ID].Flags;
446 int Bank_SetFlags(int ID, int Mask, int Value)
449 if( ID < 0 || ID >= giBank_NumUsers )
452 // Silently ignore changes to root and meta accounts
453 if( gaBank_Users[ID].UnixID <= 0 ) return 0;
455 gaBank_Users[ID].Flags &= ~Mask;
456 gaBank_Users[ID].Flags |= Value;
458 Bank_int_WriteEntry(ID);
463 int Bank_int_AlterUserBalance(int ID, int Delta)
466 if( ID < 0 || ID >= giBank_NumUsers )
470 gaBank_Users[ID].Balance += Delta;
472 Bank_int_WriteEntry(ID);
477 int Bank_int_GetMinAllowedBalance(int ID)
480 if( ID < 0 || ID >= giBank_NumUsers )
483 flags = Bank_GetFlags(ID);
485 // Internal accounts have no limit
486 if( (flags & USER_FLAG_INTERNAL) )
489 // Wheel is allowed to go to -$100
490 if( (flags & USER_FLAG_ADMIN) )
493 // Coke is allowed to go to -$20
494 if( (flags & USER_FLAG_COKE) )
497 // For everyone else, no negative
502 * Create a new user in our database
504 int Bank_int_AddUser(const char *Username)
507 int uid = Bank_int_GetUnixID(Username);
509 // Can has moar space plz?
511 tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
512 if( !tmp ) return -1;
515 tmp = realloc(gaBank_UsersByName, (giBank_NumUsers+1)*sizeof(tUser*));
516 if( !tmp ) return -1;
517 gaBank_UsersByName = tmp;
519 tmp = realloc(gaBank_UsersByBalance, (giBank_NumUsers+1)*sizeof(tUser*));
520 if( !tmp ) return -1;
521 gaBank_UsersByBalance = tmp;
524 gaBank_Users[giBank_NumUsers].Name = NULL;
525 gaBank_Users[giBank_NumUsers].UnixID = uid;
526 gaBank_Users[giBank_NumUsers].Balance = 0;
527 gaBank_Users[giBank_NumUsers].Flags = 0;
528 gaBank_UsersByName[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
529 gaBank_UsersByBalance[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
532 if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
533 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
535 else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
536 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
538 else if( strcmp(Username, "root") == 0 ) {
539 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_ADMIN|USER_FLAG_COKE;
546 gaBank_Users[giBank_NumUsers-1].Name = Bank_GetAcctName(giBank_NumUsers-1);
549 qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
550 qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
553 Bank_int_WriteEntry(giBank_NumUsers - 1);
559 // Unix user dependent code
560 // TODO: Modify to keep its own list of usernames
562 char *Bank_GetAcctName(int ID)
566 if( ID < 0 || ID >= giBank_NumUsers )
569 if( gaBank_Users[ID].Name ) {
570 return strdup(gaBank_Users[ID].Name);
573 if( gaBank_Users[ID].UnixID == -1 )
574 return strdup(COKEBANK_SALES_ACCT);
576 if( gaBank_Users[ID].UnixID == -2 )
577 return strdup(COKEBANK_DEBT_ACCT);
579 pwd = getpwuid(gaBank_Users[ID].UnixID);
580 if( !pwd ) return NULL;
582 return strdup(pwd->pw_name);
585 int Bank_int_GetUnixID(const char *Username)
589 if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) { // Pseudo account that sales are made into
592 else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) { // Pseudo acount that money is added from
598 pwd = getpwnam(Username);
599 if( !pwd ) return -1;
607 * Authenticate a user
609 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
614 int ofs = strlen(Username) + strlen(Salt);
615 char input[ ofs + 40 + 1];
616 char tmp[4 + strlen(Username) + 1]; // uid=%s
621 // Only here to shut GCC up (until password auth is implemented)
624 if( Password == NULL )
629 if( strcmp(Username, "tpg") == 0 )
630 return Bank_GetAcctByName("tpg");
633 if( strcmp(Username, "root") == 0 ) {
634 int ret = Bank_GetAcctByName("root");
636 return Bank_CreateAcct("root");
642 HexBin(hash, 20, Password);
644 // Build string to hash
645 strcpy(input, Username);
648 // TODO: Get user's SHA-1 hash
649 sprintf(tmp, "uid=%s", Username);
650 printf("tmp = '%s'\n", tmp);
651 passhash = ReadLDAPValue(tmp, "userPassword");
655 printf("LDAP hash '%s'\n", passhash);
657 sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
658 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
659 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
661 // Then create the hash from the provided salt
662 // Compare that with the provided hash
667 printf("Password hash ");
669 printf("%02x", hash[i]&0xFF);
680 char *ReadLDAPValue(const char *Filter, char *Value)
682 LDAPMessage *res, *res2;
683 struct berval **attrValues;
684 char *attrNames[] = {Value,NULL};
686 struct timeval timeout;
692 rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
693 attrNames, 0, NULL, NULL, &timeout, 1, &res
695 printf("ReadLDAPValue: rv = %i\n", rv);
697 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
704 res2 = ldap_first_entry(gpLDAP, res);
705 attrValues = ldap_get_values_len(gpLDAP, res2, Value);
707 ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
709 ldap_value_free_len(attrValues);
716 // TODO: Move to another file
717 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
720 for( i = 0; i < BufSize; i ++ )
724 if('0' <= *Src && *Src <= '9')
725 val |= (*Src-'0') << 4;
726 else if('A' <= *Src && *Src <= 'F')
727 val |= (*Src-'A'+10) << 4;
728 else if('a' <= *Src && *Src <= 'f')
729 val |= (*Src-'a'+10) << 4;
734 if('0' <= *Src && *Src <= '9')
736 else if('A' <= *Src && *Src <= 'F')
737 val |= (*Src-'A'+10);
738 else if('a' <= *Src && *Src <= 'f')
739 val |= (*Src-'a'+10);
746 for( ; i < BufSize; i++ )