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 void Init_Cokebank(const char *Argument);
55 static int Bank_int_ReadDatabase(void);
56 static int Bank_int_WriteEntry(int ID);
57 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
58 int Bank_CreateUser(const char *Username);
59 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues,
60 int Flags, int MinMaxBalance, time_t LastSeen);
61 int Bank_GetUserID(const char *Username);
62 int Bank_GetBalance(int User);
63 int Bank_GetFlags(int User);
64 int Bank_SetFlags(int User, int Mask, int Value);
65 int Bank_int_AlterUserBalance(int ID, int Delta);
66 int Bank_int_GetMinAllowedBalance(int ID);
67 int Bank_int_AddUser(const char *Username);
68 char *Bank_GetUserName(int User);
69 int Bank_int_GetUnixID(const char *Username);
70 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password);
72 char *ReadLDAPValue(const char *Filter, char *Value);
74 void HexBin(uint8_t *Dest, int BufSize, const char *Src);
79 char *gsLDAPPath = "ldapi:///";
85 tUser **gaBank_UsersByName;
86 tUser **gaBank_UsersByBalance;
90 * \brief Load the cokebank database
92 void Init_Cokebank(const char *Argument)
99 gBank_File = fopen(Argument, "rb+");
100 if( !gBank_File ) gBank_File = fopen(Argument, "wb+");
101 if( !gBank_File ) perror("Opening coke bank");
102 Bank_int_ReadDatabase();
105 // TODO: Do I need this?
106 gBank_LogFile = fopen("cokebank.log", "a");
107 if( !gBank_LogFile ) gBank_LogFile = stdout;
112 rv = ldap_create(&gpLDAP);
114 fprintf(stderr, "ldap_create: %s\n", ldap_err2string(rv));
117 rv = ldap_initialize(&gpLDAP, gsLDAPPath);
119 fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rv));
122 { int ver = LDAP_VERSION3; ldap_set_option(gpLDAP, LDAP_OPT_PROTOCOL_VERSION, &ver); }
124 rv = ldap_start_tls_s(gpLDAP, NULL, NULL);
126 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
132 struct berval *servcred;
133 cred.bv_val = "secret";
135 rv = ldap_sasl_bind_s(gpLDAP, "cn=admin,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
136 "", &cred, NULL, NULL, &servcred);
138 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
146 * \brief Name compare function for qsort
148 static int Bank_int_CompareNames(const void *Ent1, const void *Ent2)
150 const tUser *user1 = *(const tUser**)Ent1;
151 const tUser *user2 = *(const tUser**)Ent2;
153 return strcmp(user1->Name, user2->Name);
157 * \brief Name compare function for qsort
159 static int Bank_int_CompareBalance(const void *Ent1, const void *Ent2)
161 const tUser *user1 = *(const tUser**)Ent1;
162 const tUser *user2 = *(const tUser**)Ent2;
164 return user1->Balance - user2->Balance;
168 static int Bank_int_ReadDatabase(void)
171 if( gaBank_Users ) return 1;
174 fseek(gBank_File, 0, SEEK_END);
175 giBank_NumUsers = ftell(gBank_File) / sizeof(tFileUser);
176 fseek(gBank_File, 0, SEEK_SET);
178 // Allocate structures
179 gaBank_Users = malloc( giBank_NumUsers * sizeof(tUser) );
180 gaBank_UsersByName = malloc( giBank_NumUsers * sizeof(tUser*) );
181 gaBank_UsersByBalance = malloc( giBank_NumUsers * sizeof(tUser*) );
183 for( i = 0; i < giBank_NumUsers; i ++ )
186 fread(&fu, sizeof(tFileUser), 1, gBank_File);
187 gaBank_Users[i].Name = NULL;
188 gaBank_Users[i].UnixID = fu.UnixID;
189 gaBank_Users[i].Balance = fu.Balance;
190 gaBank_Users[i].Flags = fu.Flags;
191 gaBank_Users[i].Name = Bank_GetUserName(i);
192 gaBank_UsersByName[i] = &gaBank_Users[i]; // Add to name index
193 gaBank_UsersByBalance[i] = &gaBank_Users[i]; // Add to balance index
197 qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
198 qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
203 static int Bank_int_ReadDatabase(void)
206 // Alternate data format
208 // <username>,<uid>,<pin>,<lastused>,<balance>,<flags>,<altlogins...>
209 fseek(gBank_File, 0, SEEK_SET);
213 fgets(buf, BUFSIZ-1, gBank_File);
218 static int Bank_int_WriteEntry(int ID)
221 if( ID < 0 || ID >= giBank_NumUsers ) {
226 fseek(gBank_File, ID*sizeof(fu), SEEK_SET);
228 fu.UnixID = gaBank_Users[ID].UnixID;
229 fu.Balance = gaBank_Users[ID].Balance;
230 fu.Flags = gaBank_Users[ID].Flags;
232 fwrite(&fu, sizeof(fu), 1, gBank_File);
238 * Transfers money from one user to another
239 * \param SourceUser Source user
240 * \param DestUser Destination user
241 * \param Ammount Ammount of cents to move from \a SourceUser to \a DestUser
242 * \param Reason Reason for the transfer (essentially a comment)
243 * \return Boolean failure
245 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
247 int srcBal = Bank_GetBalance(SourceUser);
248 int dstBal = Bank_GetBalance(DestUser);
250 if( srcBal - Ammount < Bank_int_GetMinAllowedBalance(SourceUser) )
252 if( dstBal + Ammount < Bank_int_GetMinAllowedBalance(DestUser) )
254 Bank_int_AlterUserBalance(DestUser, Ammount);
255 Bank_int_AlterUserBalance(SourceUser, -Ammount);
256 fprintf(gBank_LogFile, "Transfer %ic #%i{%i} > #%i{%i} [%i, %i] (%s)\n",
257 Ammount, SourceUser, srcBal, DestUser, dstBal,
258 srcBal - Ammount, dstBal + Ammount, Reason);
262 int Bank_CreateUser(const char *Username)
266 ret = Bank_GetUserID(Username);
267 if( ret != -1 ) return -1;
269 return Bank_int_AddUser(Username);
272 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
276 ret = calloc( 1, sizeof(tAcctIterator) );
279 ret->MinBalance = INT_MIN;
280 ret->MaxBalance = INT_MAX;
282 ret->FlagMask = FlagMask;
283 ret->FlagValue = FlagValues & FlagMask;
285 if(Flags & BANK_ITFLAG_MINBALANCE)
286 ret->MinBalance = MinMaxBalance;
287 if(Flags & BANK_ITFLAG_MAXBALANCE)
288 ret->MaxBalance = MinMaxBalance;
290 ret->Sort = Flags & (BANK_ITFLAG_SORTMASK|BANK_ITFLAG_REVSORT);
295 //if(Flags & BANK_ITFLAG_SEENBEFORE)
296 // ret->MinBalance = MinMaxBalance;
297 //if(Flags & BANK_ITFLAG_SEENAFTER)
298 // ret->MinBalance = MinMaxBalance;
303 int Bank_IteratorNext(tAcctIterator *It)
307 while(It->CurUser < giBank_NumUsers)
311 case BANK_ITFLAG_SORT_NONE:
312 case BANK_ITFLAG_SORT_NONE | BANK_ITFLAG_REVSORT:
315 case BANK_ITFLAG_SORT_NAME:
316 ret = indexof(gaBank_Users, gaBank_UsersByName[It->CurUser]);
318 case BANK_ITFLAG_SORT_NAME | BANK_ITFLAG_REVSORT:
319 ret = indexof(gaBank_Users, gaBank_UsersByName[giBank_NumUsers-It->CurUser]);
321 case BANK_ITFLAG_SORT_BAL:
322 ret = indexof(gaBank_Users, gaBank_UsersByBalance[It->CurUser]);
323 printf("Sort by balance (ret = %i)\n", ret);
325 case BANK_ITFLAG_SORT_BAL | BANK_ITFLAG_REVSORT:
326 ret = indexof(gaBank_Users, gaBank_UsersByBalance[giBank_NumUsers-It->CurUser]);
329 fprintf(stderr, "BUG: Unsupported sort in Bank_IteratorNext\n");
334 if( gaBank_Users[ret].Balance < It->MinBalance )
336 if( gaBank_Users[ret].Balance > It->MaxBalance )
338 if( (gaBank_Users[ret].Flags & It->FlagMask) != It->FlagValue )
346 void Bank_DelIterator(tAcctIterator *It)
352 * \brief Get the User ID of the named user
354 int Bank_GetUserID(const char *Username)
358 i = giBank_NumUsers / 2;
359 size = giBank_NumUsers;
362 cmp = strcmp(gaBank_UsersByName[i]->Name, Username);
364 return indexof(gaBank_Users, gaBank_UsersByName[i];
378 uid = Bank_int_GetUnixID(Username);
380 // Expensive search :(
381 for( i = 0; i < giBank_NumUsers; i ++ )
383 if( gaBank_Users[i].UnixID == uid )
391 int Bank_GetBalance(int ID)
393 if( ID < 0 || ID >= giBank_NumUsers )
396 return gaBank_Users[ID].Balance;
399 int Bank_GetFlags(int ID)
401 if( ID < 0 || ID >= giBank_NumUsers )
405 if( gaBank_Users[ID].UnixID == 0 ) {
406 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL|USER_FLAG_COKE;
410 // TODO: Implement checking the PAM groups and status instead, then
411 // fall back on the database. (and update if there is a difference)
412 if( gaBank_Users[ID].UnixID > 0 )
419 pwd = getpwuid( gaBank_Users[ID].UnixID );
421 // Check for additions to the "coke" group
422 grp = getgrnam("coke");
424 for( i = 0; grp->gr_mem[i]; i ++ )
426 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
427 gaBank_Users[ID].Flags |= USER_FLAG_COKE;
433 // Check for additions to the "wheel" group
434 grp = getgrnam("wheel");
436 for( i = 0; grp->gr_mem[i]; i ++ )
438 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
439 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL;
447 return gaBank_Users[ID].Flags;
450 int Bank_SetFlags(int ID, int Mask, int Value)
453 if( ID < 0 || ID >= giBank_NumUsers )
456 // Silently ignore changes to root and meta accounts
457 if( gaBank_Users[ID].UnixID <= 0 ) return 0;
459 gaBank_Users[ID].Flags &= ~Mask;
460 gaBank_Users[ID].Flags |= Value;
462 Bank_int_WriteEntry(ID);
467 int Bank_int_AlterUserBalance(int ID, int Delta)
470 if( ID < 0 || ID >= giBank_NumUsers )
474 gaBank_Users[ID].Balance += Delta;
476 Bank_int_WriteEntry(ID);
481 int Bank_int_GetMinAllowedBalance(int ID)
484 if( ID < 0 || ID >= giBank_NumUsers )
487 flags = Bank_GetFlags(ID);
489 // Internal accounts have no limit
490 if( (flags & USER_FLAG_INTERNAL) )
493 // Wheel is allowed to go to -$100
494 if( (flags & USER_FLAG_WHEEL) )
497 // Coke is allowed to go to -$20
498 if( (flags & USER_FLAG_COKE) )
501 // For everyone else, no negative
506 * Create a new user in our database
508 int Bank_int_AddUser(const char *Username)
511 int uid = Bank_int_GetUnixID(Username);
513 // Can has moar space plz?
515 tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
516 if( !tmp ) return -1;
519 tmp = realloc(gaBank_UsersByName, (giBank_NumUsers+1)*sizeof(tUser*));
520 if( !tmp ) return -1;
521 gaBank_UsersByName = tmp;
523 tmp = realloc(gaBank_UsersByBalance, (giBank_NumUsers+1)*sizeof(tUser*));
524 if( !tmp ) return -1;
525 gaBank_UsersByBalance = tmp;
528 gaBank_Users[giBank_NumUsers].Name = NULL;
529 gaBank_Users[giBank_NumUsers].UnixID = uid;
530 gaBank_Users[giBank_NumUsers].Balance = 0;
531 gaBank_Users[giBank_NumUsers].Flags = 0;
532 gaBank_UsersByName[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
533 gaBank_UsersByBalance[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
536 if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
537 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
539 else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
540 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
542 else if( strcmp(Username, "root") == 0 ) {
543 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_WHEEL|USER_FLAG_COKE;
550 gaBank_Users[giBank_NumUsers-1].Name = Bank_GetUserName(giBank_NumUsers-1);
553 qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
554 qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
557 Bank_int_WriteEntry(giBank_NumUsers - 1);
563 // Unix user dependent code
564 // TODO: Modify to keep its own list of usernames
566 char *Bank_GetUserName(int ID)
570 if( ID < 0 || ID >= giBank_NumUsers )
573 if( gaBank_Users[ID].Name ) {
574 return strdup(gaBank_Users[ID].Name);
577 if( gaBank_Users[ID].UnixID == -1 )
578 return strdup(COKEBANK_SALES_ACCT);
580 if( gaBank_Users[ID].UnixID == -2 )
581 return strdup(COKEBANK_DEBT_ACCT);
583 pwd = getpwuid(gaBank_Users[ID].UnixID);
584 if( !pwd ) return NULL;
586 return strdup(pwd->pw_name);
589 int Bank_int_GetUnixID(const char *Username)
593 if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) { // Pseudo account that sales are made into
596 else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) { // Pseudo acount that money is added from
602 pwd = getpwnam(Username);
603 if( !pwd ) return -1;
611 * Authenticate a user
613 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
618 int ofs = strlen(Username) + strlen(Salt);
619 char input[ ofs + 40 + 1];
620 char tmp[4 + strlen(Username) + 1]; // uid=%s
625 // Only here to shut GCC up (until password auth is implemented)
628 if( Password == NULL )
633 if( strcmp(Username, "tpg") == 0 )
634 return Bank_GetUserID("tpg");
637 if( strcmp(Username, "root") == 0 ) {
638 int ret = Bank_GetUserID("root");
640 return Bank_CreateUser("root");
646 HexBin(hash, 20, Password);
648 // Build string to hash
649 strcpy(input, Username);
652 // TODO: Get user's SHA-1 hash
653 sprintf(tmp, "uid=%s", Username);
654 printf("tmp = '%s'\n", tmp);
655 passhash = ReadLDAPValue(tmp, "userPassword");
659 printf("LDAP hash '%s'\n", passhash);
661 sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
662 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
663 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
665 // Then create the hash from the provided salt
666 // Compare that with the provided hash
671 printf("Password hash ");
673 printf("%02x", hash[i]&0xFF);
684 char *ReadLDAPValue(const char *Filter, char *Value)
686 LDAPMessage *res, *res2;
687 struct berval **attrValues;
688 char *attrNames[] = {Value,NULL};
690 struct timeval timeout;
696 rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
697 attrNames, 0, NULL, NULL, &timeout, 1, &res
699 printf("ReadLDAPValue: rv = %i\n", rv);
701 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
708 res2 = ldap_first_entry(gpLDAP, res);
709 attrValues = ldap_get_values_len(gpLDAP, res2, Value);
711 ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
713 ldap_value_free_len(attrValues);
720 // TODO: Move to another file
721 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
724 for( i = 0; i < BufSize; i ++ )
728 if('0' <= *Src && *Src <= '9')
729 val |= (*Src-'0') << 4;
730 else if('A' <= *Src && *Src <= 'F')
731 val |= (*Src-'A'+10) << 4;
732 else if('a' <= *Src && *Src <= 'f')
733 val |= (*Src-'a'+10) << 4;
738 if('0' <= *Src && *Src <= '9')
740 else if('A' <= *Src && *Src <= 'F')
741 val |= (*Src-'A'+10);
742 else if('a' <= *Src && *Src <= 'f')
743 val |= (*Src-'a'+10);
750 for( ; i < BufSize; i++ )