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 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);
53 char *ReadLDAPValue(const char *Filter, char *Value);
55 void HexBin(uint8_t *Dest, int BufSize, const char *Src);
60 char *gsLDAPPath = "ldapi:///";
69 * \brief Load the cokebank database
71 void Init_Cokebank(const char *Argument)
78 gBank_File = fopen(Argument, "rb+");
80 gBank_File = fopen(Argument, "wb+");
83 perror("Opening coke bank");
87 // TODO: Do I need this?
88 gBank_LogFile = fopen("cokebank.log", "a");
89 if( !gBank_LogFile ) gBank_LogFile = stdout;
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);
100 rv = ldap_create(&gpLDAP);
102 fprintf(stderr, "ldap_create: %s\n", ldap_err2string(rv));
105 rv = ldap_initialize(&gpLDAP, gsLDAPPath);
107 fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rv));
110 { int ver = LDAP_VERSION3; ldap_set_option(gpLDAP, LDAP_OPT_PROTOCOL_VERSION, &ver); }
112 rv = ldap_start_tls_s(gpLDAP, NULL, NULL);
114 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
120 struct berval *servcred;
121 cred.bv_val = "secret";
123 rv = ldap_sasl_bind_s(gpLDAP, "cn=root,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
124 "", &cred, NULL, NULL, &servcred);
126 fprintf(stderr, "ldap_start_tls_s: %s\n", ldap_err2string(rv));
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
141 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
143 int srcBal = Bank_GetBalance(SourceUser);
144 int dstBal = Bank_GetBalance(DestUser);
146 if( srcBal - Ammount < Bank_int_GetMinAllowedBalance(SourceUser) )
148 if( dstBal + Ammount < Bank_int_GetMinAllowedBalance(DestUser) )
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);
158 int Bank_CreateUser(const char *Username)
162 ret = Bank_GetUserID(Username);
163 if( ret != -1 ) return -1;
165 return Bank_int_AddUser(Username);
168 int Bank_GetMaxID(void)
170 return giBank_NumUsers;
173 static int Bank_int_WriteEntry(int ID)
175 if( ID < 0 || ID >= giBank_NumUsers ) {
180 fseek(gBank_File, ID*sizeof(gaBank_Users[0]), SEEK_SET);
181 fwrite(&gaBank_Users[ID], sizeof(gaBank_Users[0]), 1, gBank_File);
187 * \brief Get the User ID of the named user
189 int Bank_GetUserID(const char *Username)
193 uid = Bank_int_GetUnixID(Username);
195 // Expensive search :(
196 for( i = 0; i < giBank_NumUsers; i ++ )
198 if( gaBank_Users[i].UnixID == uid )
205 int Bank_GetBalance(int ID)
207 if( ID < 0 || ID >= giBank_NumUsers )
210 return gaBank_Users[ID].Balance;
213 int Bank_GetFlags(int ID)
215 if( ID < 0 || ID >= giBank_NumUsers )
219 if( gaBank_Users[ID].UnixID == 0 ) {
220 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL|USER_FLAG_COKE;
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 )
233 pwd = getpwuid( gaBank_Users[ID].UnixID );
235 // Check for additions to the "coke" group
236 grp = getgrnam("coke");
238 for( i = 0; grp->gr_mem[i]; i ++ )
240 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
241 gaBank_Users[ID].Flags |= USER_FLAG_COKE;
247 // Check for additions to the "wheel" group
248 grp = getgrnam("wheel");
250 for( i = 0; grp->gr_mem[i]; i ++ )
252 if( strcmp(grp->gr_mem[i], pwd->pw_name) == 0 ) {
253 gaBank_Users[ID].Flags |= USER_FLAG_WHEEL;
261 return gaBank_Users[ID].Flags;
264 int Bank_SetFlags(int ID, int Mask, int Value)
267 if( ID < 0 || ID >= giBank_NumUsers )
270 // Silently ignore changes to root and meta accounts
271 if( gaBank_Users[ID].UnixID <= 0 ) return 0;
273 gaBank_Users[ID].Flags &= ~Mask;
274 gaBank_Users[ID].Flags |= Value;
276 Bank_int_WriteEntry(ID);
281 int Bank_int_AlterUserBalance(int ID, int Delta)
284 if( ID < 0 || ID >= giBank_NumUsers )
288 gaBank_Users[ID].Balance += Delta;
290 Bank_int_WriteEntry(ID);
295 int Bank_int_GetMinAllowedBalance(int ID)
298 if( ID < 0 || ID >= giBank_NumUsers )
301 flags = Bank_GetFlags(ID);
303 // Internal accounts have no limit
304 if( (flags & USER_FLAG_INTERNAL) )
307 // Wheel is allowed to go to -$100
308 if( (flags & USER_FLAG_WHEEL) )
311 // Coke is allowed to go to -$20
312 if( (flags & USER_FLAG_COKE) )
315 // For everyone else, no negative
320 * \brief Create a new user in our database
322 int Bank_int_AddUser(const char *Username)
325 int uid = Bank_int_GetUnixID(Username);
327 // Can has moar space plz?
328 tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
329 if( !tmp ) return -1;
333 gaBank_Users[giBank_NumUsers].UnixID = uid;
334 gaBank_Users[giBank_NumUsers].Balance = 0;
335 gaBank_Users[giBank_NumUsers].Flags = 0;
337 if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
338 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
340 else if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {
341 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
343 else if( strcmp(Username, "root") == 0 ) {
344 gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_WHEEL|USER_FLAG_COKE;
350 Bank_int_WriteEntry(giBank_NumUsers - 1);
356 // Unix user dependent code
357 // TODO: Modify to keep its own list of usernames
360 * \brief Return the name the passed user
362 char *Bank_GetUserName(int ID)
366 if( ID < 0 || ID >= giBank_NumUsers )
369 if( gaBank_Users[ID].UnixID == -1 )
370 return strdup(COKEBANK_SALES_ACCT);
372 if( gaBank_Users[ID].UnixID == -2 )
373 return strdup(COKEBANK_DEBT_ACCT);
375 pwd = getpwuid(gaBank_Users[ID].UnixID);
376 if( !pwd ) return NULL;
378 return strdup(pwd->pw_name);
381 int Bank_int_GetUnixID(const char *Username)
385 if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) { // Pseudo account that sales are made into
388 else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) { // Pseudo acount that money is added from
394 pwd = getpwnam(Username);
395 if( !pwd ) return -1;
403 * \brief Authenticate a user
404 * \return User ID, or -1 if authentication failed
406 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString)
411 int ofs = strlen(Username) + strlen(Salt);
412 char input[ ofs + 40 + 1];
413 char tmp[4 + strlen(Username) + 1]; // uid=%s
418 // Only here to shut GCC up (until password auth is implemented
421 if( PasswordString == NULL )
426 if( strcmp(Username, "tpg") == 0 )
427 return Bank_GetUserID("tpg");
430 if( strcmp(Username, "root") == 0 ) {
431 int ret = Bank_GetUserID("root");
433 return Bank_CreateUser("root");
439 HexBin(hash, 20, PasswordString);
441 // Build string to hash
442 strcpy(input, Username);
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");
452 printf("LDAP hash '%s'\n", passhash);
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]
458 // Then create the hash from the provided salt
459 // Compare that with the provided hash
464 printf("Password hash ");
466 printf("%02x", hash[i]&0xFF);
477 char *ReadLDAPValue(const char *Filter, char *Value)
479 LDAPMessage *res, *res2;
480 struct berval **attrValues;
481 char *attrNames[] = {Value,NULL};
483 struct timeval timeout;
489 rv = ldap_search_ext_s(gpLDAP, "", LDAP_SCOPE_BASE, Filter,
490 attrNames, 0, NULL, NULL, &timeout, 1, &res
492 printf("ReadLDAPValue: rv = %i\n", rv);
494 fprintf(stderr, "LDAP Error reading '%s' with filter '%s'\n%s\n",
501 res2 = ldap_first_entry(gpLDAP, res);
502 attrValues = ldap_get_values_len(gpLDAP, res2, Value);
504 ret = strndup(attrValues[0]->bv_val, attrValues[0]->bv_len);
506 ldap_value_free_len(attrValues);
513 // TODO: Move to another file
514 void HexBin(uint8_t *Dest, int BufSize, const char *Src)
517 for( i = 0; i < BufSize; i ++ )
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;
531 if('0' <= *Src && *Src <= '9')
533 else if('A' <= *Src && *Src <= 'F')
534 val |= (*Src-'A'+10);
535 else if('a' <= *Src && *Src <= 'f')
536 val |= (*Src-'a'+10);
543 for( ; i < BufSize; i++ )