3 * UCC (University [of WA] Computer Club) Electronic Accounting System
5 * cokebank.c - Coke-Bank management
7 * This file is licenced under the 3-clause BSD Licence. See the file COPYING
18 #include <openssl/sha.h>
27 * http://linuxdevcenter.com/pub/a/linux/2003/08/14/libldap.html
28 * - Using libldap, the LDAP Client Library
32 #define USE_UNIX_GROUPS 1
33 #define HACK_TPG_NOAUTH 1
34 #define HACK_ROOT_NOAUTH 1
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);
54 char *ReadLDAPValue(const char *Filter, char *Value);
56 void HexBin(uint8_t *Dest, int BufSize, const char *Src);
61 char *gsLDAPPath = "ldapi:///";
70 * \brief Load the cokebank database
72 void Init_Cokebank(const char *Argument)
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();
85 // TODO: Do I need this?
86 gBank_LogFile = fopen("cokebank.log", "a");
87 if( !gBank_LogFile ) gBank_LogFile = stdout;
92 rv = ldap_create(&gpLDAP);
94 fprintf(stderr, "ldap_create: %s\n", ldap_err2string(rv));
97 rv = ldap_initialize(&gpLDAP, gsLDAPPath);
99 fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rv));
102 { int ver = LDAP_VERSION3; ldap_set_option(gpLDAP, LDAP_OPT_PROTOCOL_VERSION, &ver); }
104 rv = ldap_start_tls_s(gpLDAP, NULL, NULL);
106 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
112 struct berval *servcred;
113 cred.bv_val = "secret";
115 rv = ldap_sasl_bind_s(gpLDAP, "cn=admin,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
116 "", &cred, NULL, NULL, &servcred);
118 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
126 static int Bank_int_ReadDatabase(void)
128 if( gaBank_Users ) return 1;
131 fseek(gBank_File, 0, SEEK_END);
132 giBank_NumUsers = ftell(gBank_File) / sizeof(gaBank_Users[0]);
133 fseek(gBank_File, 0, SEEK_SET);
135 gaBank_Users = malloc( giBank_NumUsers * sizeof(gaBank_Users[0]) );
136 fread(gaBank_Users, sizeof(gaBank_Users[0]), giBank_NumUsers, gBank_File);
141 static int Bank_int_ReadDatabase(void)
144 // Alternate data format
146 // <username>,<uid>,<pin>,<lastused>,<balance>,<flags>,<altlogins...>
147 fseek(gBank_File, 0, SEEK_SET);
151 fgets(buf, BUFSIZ-1, gBank_File);
157 static int Bank_int_WriteEntry(int ID)
159 if( ID < 0 || ID >= giBank_NumUsers ) {
164 fseek(gBank_File, ID*sizeof(gaBank_Users[0]), SEEK_SET);
165 fwrite(&gaBank_Users[ID], sizeof(gaBank_Users[0]), 1, gBank_File);
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
178 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
180 int srcBal = Bank_GetBalance(SourceUser);
181 int dstBal = Bank_GetBalance(DestUser);
183 if( srcBal - Ammount < Bank_int_GetMinAllowedBalance(SourceUser) )
185 if( dstBal + Ammount < Bank_int_GetMinAllowedBalance(DestUser) )
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);
195 int Bank_CreateUser(const char *Username)
199 ret = Bank_GetUserID(Username);
200 if( ret != -1 ) return -1;
202 return Bank_int_AddUser(Username);
205 int Bank_GetMaxID(void)
207 return giBank_NumUsers;
211 * \brief Get the User ID of the named user
213 int Bank_GetUserID(const char *Username)
217 uid = Bank_int_GetUnixID(Username);
219 // Expensive search :(
220 for( i = 0; i < giBank_NumUsers; i ++ )
222 if( gaBank_Users[i].UnixID == uid )
229 int Bank_GetBalance(int ID)
231 if( ID < 0 || ID >= giBank_NumUsers )
234 return gaBank_Users[ID].Balance;
237 int Bank_GetFlags(int ID)
239 if( ID < 0 || ID >= giBank_NumUsers )
243 if( gaBank_Users[ID].UnixID == 0 ) {
244 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL|USER_FLAG_COKE;
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 )
257 pwd = getpwuid( gaBank_Users[ID].UnixID );
259 // Check for additions to the "coke" group
260 grp = getgrnam("coke");
262 for( i = 0; grp->gr_mem[i]; i ++ )
264 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
265 gaBank_Users[ID].Flags |= USER_FLAG_COKE;
271 // Check for additions to the "wheel" group
272 grp = getgrnam("wheel");
274 for( i = 0; grp->gr_mem[i]; i ++ )
276 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
277 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL;
285 return gaBank_Users[ID].Flags;
288 int Bank_SetFlags(int ID, int Mask, int Value)
291 if( ID < 0 || ID >= giBank_NumUsers )
294 // Silently ignore changes to root and meta accounts
295 if( gaBank_Users[ID].UnixID <= 0 ) return 0;
297 gaBank_Users[ID].Flags &= ~Mask;
298 gaBank_Users[ID].Flags |= Value;
300 Bank_int_WriteEntry(ID);
305 int Bank_int_AlterUserBalance(int ID, int Delta)
308 if( ID < 0 || ID >= giBank_NumUsers )
312 gaBank_Users[ID].Balance += Delta;
314 Bank_int_WriteEntry(ID);
319 int Bank_int_GetMinAllowedBalance(int ID)
322 if( ID < 0 || ID >= giBank_NumUsers )
325 flags = Bank_GetFlags(ID);
327 // Internal accounts have no limit
328 if( (flags & USER_FLAG_INTERNAL) )
331 // Wheel is allowed to go to -$100
332 if( (flags & USER_FLAG_WHEEL) )
335 // Coke is allowed to go to -$20
336 if( (flags & USER_FLAG_COKE) )
339 // For everyone else, no negative
344 * \brief Create a new user in our database
346 int Bank_int_AddUser(const char *Username)
349 int uid = Bank_int_GetUnixID(Username);
351 // Can has moar space plz?
352 tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
353 if( !tmp ) return -1;
357 gaBank_Users[giBank_NumUsers].UnixID = uid;
358 gaBank_Users[giBank_NumUsers].Balance = 0;
359 gaBank_Users[giBank_NumUsers].Flags = 0;
361 if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
362 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
364 else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
365 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
367 else if( strcmp(Username, "root") == 0 ) {
368 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_WHEEL|USER_FLAG_COKE;
374 Bank_int_WriteEntry(giBank_NumUsers - 1);
380 // Unix user dependent code
381 // TODO: Modify to keep its own list of usernames
384 * \brief Return the name the passed user
386 char *Bank_GetUserName(int ID)
390 if( ID < 0 || ID >= giBank_NumUsers )
393 if( gaBank_Users[ID].UnixID == -1 )
394 return strdup(COKEBANK_SALES_ACCT);
396 if( gaBank_Users[ID].UnixID == -2 )
397 return strdup(COKEBANK_DEBT_ACCT);
399 pwd = getpwuid(gaBank_Users[ID].UnixID);
400 if( !pwd ) return NULL;
402 return strdup(pwd->pw_name);
405 int Bank_int_GetUnixID(const char *Username)
409 if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) { // Pseudo account that sales are made into
412 else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) { // Pseudo acount that money is added from
418 pwd = getpwnam(Username);
419 if( !pwd ) return -1;
427 * \brief Authenticate a user
428 * \return User ID, or -1 if authentication failed
430 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString)
435 int ofs = strlen(Username) + strlen(Salt);
436 char input[ ofs + 40 + 1];
437 char tmp[4 + strlen(Username) + 1]; // uid=%s
442 // Only here to shut GCC up (until password auth is implemented
445 if( PasswordString == NULL )
450 if( strcmp(Username, "tpg") == 0 )
451 return Bank_GetUserID("tpg");
454 if( strcmp(Username, "root") == 0 ) {
455 int ret = Bank_GetUserID("root");
457 return Bank_CreateUser("root");
463 HexBin(hash, 20, PasswordString);
465 // Build string to hash
466 strcpy(input, Username);
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");
476 printf("LDAP hash '%s'\n", passhash);
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]
482 // Then create the hash from the provided salt
483 // Compare that with the provided hash
488 printf("Password hash ");
490 printf("%02x", hash[i]&0xFF);
501 char *ReadLDAPValue(const char *Filter, char *Value)
503 LDAPMessage *res, *res2;
504 struct berval **attrValues;
505 char *attrNames[] = {Value,NULL};
507 struct timeval timeout;
513 rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
514 attrNames, 0, NULL, NULL, &timeout, 1, &res
516 printf("ReadLDAPValue: rv = %i\n", rv);
518 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
525 res2 = ldap_first_entry(gpLDAP, res);
526 attrValues = ldap_get_values_len(gpLDAP, res2, Value);
528 ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
530 ldap_value_free_len(attrValues);
537 // TODO: Move to another file
538 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
541 for( i = 0; i < BufSize; i ++ )
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;
555 if('0' <= *Src && *Src <= '9')
557 else if('A' <= *Src && *Src <= 'F')
558 val |= (*Src-'A'+10);
559 else if('a' <= *Src && *Src <= 'f')
560 val |= (*Src-'a'+10);
567 for( ; i < BufSize; i++ )