User
INTEGER UserID PRIMARY_KEY
- STRING Username UNIQUE
+ STRING Username NULL, UNIQUE
- STRING PasswordHash ; < Hmm... needed?
- INTEGER UnixUID UNIQUE
- INTEGER PinCode
+ STRING PasswordHash NULL ; < Hmm... needed? (Nah, PAM only)
+ INTEGER UnixUID NULL, UNIQUE
+ INTEGER PinCode NULL
INTEGER Balance
- DATETIME LastSeen
+ DATETIME LastSeen
BOOL bEnabled
BOOL bCokeMember
BOOL bWheelMember
The protocol is ASCII based, similar to the FTP protocol
All server responses are on one line and are prefixed by a three digit response code.
+== Paramaters ===
+<username> is a textual username unless the user does not have a username,
+in which case the username is of the form '#<id>' where <id> is a unique
+account number assigned by the server.
+<card_id_hex> is the hexadecimal byte stream representation of a MIFARE card.
+<item_id> is an item ID of the form '<type>:<id>', where <type> is an
+alpha-numeric diriver identifier, and <id> is an non-negative integer assigned
+by the driver.
+<ammount> is an integer number of cents representing the size of a transactio.
+Individual commands may restrict the range of the ammount to non-negative
+numbers.
+
+
== Response Codes ==
100 Information
200 Command succeeded, no extra information
automatic authentication is allowed
c AUTOAUTH <username>\n
s 200 Auth OK\n or 404 Bad Username\n or 401 Untrusted\n
+--- Alternate Method (MIFARE Authentication)
+c MIFARE <card_id_hex>\n
+s 200 Auth OK as <username>\n or 404 Bad Card ID\n or 401 Untrusted\n
+
--- Set effective user (User in `dispense -u`) ---
c SETEUSER <username>\n
s 200 User set\n or 403 Not in coke\n or 404 User not found\n
- to remove the flag) Valid values are: user,coke,wheel,meta,disabled,door
c USER_FLAGS <username> <flags>\n
s 200 User Updated\n or 403 Not Wheel\n or 404 Bad User\n or 407 Unknown Flags\n
+--- Add MIFARE ID ---
+c ADD_CARD <card id hex>\n
+s 200 User Updated\n or 405 Card already registered\n
* This file is licenced under the 3-clause BSD Licence. See the file COPYING
* for full details.
*/
+/**
+ * \file cokebank.h
+ * \brief Coke Bank API Documentation
+ */
#ifndef _COKEBANK_H_
#define _COKEBANK_H_
#define COKEBANK_SALES_ACCT ">sales" //!< Sales made into
#define COKEBANK_DEBT_ACCT ">liability" //!< Credit taken out of
+/**
+ * \brief Account iterator opaque structure
+ *
+ * Opaque structure for account iterators returned by Bank_Iterator
+ * and used by Bank_IteratorNext and Bank_DelIterator
+ */
+typedef struct sAcctIterator tAcctIterator;
+
+enum eBank_ItFlags
+{
+ BANK_ITFLAG_MINBALANCE = 0x01,
+ BANK_ITFLAG_MAXBALANCE = 0x02,
+ BANK_ITFLAG_SEENBEFORE = 0x04,
+ BANK_ITFLAG_SEENAFTER = 0x08,
+
+ BANK_ITFLAG_SORT_NONE = 0x000,
+ BANK_ITFLAG_SORT_NAME = 0x100,
+ BANK_ITFLAG_SORT_BAL = 0x200,
+ BANK_ITFLAG_SORT_UNIXID = 0x300,
+ BANK_ITFLAG_SORT_LASTSEEN = 0x400,
+ BANK_ITFLAG_SORTMASK = 0x700,
+ BANK_ITFLAG_REVSORT = 0x800
+};
+
+/**
+ * \brief User flag values
+ *
+ * User flag values used by Bank_GetFlags and Bank_SetFlags
+ */
enum eCokebank_Flags {
- USER_FLAG_COKE = 0x01,
- USER_FLAG_WHEEL = 0x02,
- USER_FLAG_DOORGROUP = 0x04,
- USER_FLAG_INTERNAL = 0x40,
- USER_FLAG_DISABLED = 0x80
+ USER_FLAG_COKE = 0x01, //!< User is a coke member (can do coke accounting)
+ USER_FLAG_WHEEL = 0x02, //!< User is a wheel member (can create, delete and lock accounts)
+ USER_FLAG_DOORGROUP = 0x04, //!< User is in the door group (can open the clubroom door)
+ USER_FLAG_INTERNAL = 0x40, //!< Account is internal (cannot be authenticated, no lower balance limit)
+ USER_FLAG_DISABLED = 0x80 //!< Account is disabled (no transactions allowed)
};
// --- Cokebank Functions ---
+/**
+ * \brief Initialise the cokebank
+ * \param Argument Cokebank argument specified in the dispense config file (typically the database path)
+ * \return Boolean Failure
+ */
+extern int Bank_Initialise(const char *Argument);
+
/**
* \brief Transfer money from one account to another
- * \param SourceUser UID (from \a GetUserID) to take the money from
- * \param DestUser UID (from \a GetUserID) give money to
+ * \param SourceUser UID (from \a Bank_GetUserID) to take the money from
+ * \param DestUser UID (from \a Bank_GetUserID) give money to
* \param Ammount Amount of money (in cents) to transfer
* \param Reason Reason for the transfer
*/
/**
* \brief Get flags on an account
* \param User UID to get flags from
- * \see eCokebank_Flags
+ * \return Flag set as defined in eCokebank_Flags
*/
extern int Bank_GetFlags(int User);
/**
extern int Bank_GetUserID(const char *Username);
/**
* \brief Create a new account
+ * \param Username Name for the new account (if NULL, an anoymous account is created)
+ * \return User ID of the new account
*/
extern int Bank_CreateUser(const char *Username);
+
+/**
+ * \brief Create an account iterator
+ * \param FlagMask Mask of account flags to check
+ * \param FlagValues Wanted values for checked flags
+ * \param Flags Specifies the operation of \a MinMaxBalance and \a Timestamp (\see eBank_ItFlags)
+ * \param MinMaxBalance Mininum/Maximum balance
+ * \param LastSeen Latest/Earliest last seen time
+ * \return Pointer to an iterator across the selected data set
+ */
+extern tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues,
+ int Flags, int MinMaxBalance, time_t LastSeen);
+
+/**
+ * \brief Get the current entry in the iterator and move to the next
+ * \param It Iterator returned by Bank_Iterator
+ * \return User ID or -1 for end of list
+ */
+extern int Bank_IteratorNext(tAcctIterator *It);
+
/**
- * \brief Get the maximum UID
- * \note Used for iterating accounts
+ * \brief Free an allocated iterator
+ * \param It Iterator returned by Bank_Iterator
*/
-extern int Bank_GetMaxID(void);
+extern void Bank_DelIterator(tAcctIterator *It);
+
/**
* \brief Validates a user's authentication
* \param Salt Salt given to the client for hashing the password
* \param Username Username used
* \param Password Password sent by the client
+ * \return User ID
*/
extern int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password);
+/**
+ * \brief Get a User ID from a MIFARE card ID
+ * \param CardID MIFARE card ID
+ * \return User ID
+ */
+extern int Bank_GetUserByCard(const char *CardID);
+
+/**
+ * \brief Add a card to a user's account
+ * \param User User ID
+ * \param CardID MIFARE card ID
+ * \return Boolean failure
+ * \retval 0 Success
+ * \retval 1 Bad user ID
+ * \retval 2 Card in use
+ */
+extern int Bank_AddUserCard(int User, const char *CardID);
+
#endif
#include "../cokebank.h"
+typedef struct sFileUser {
+ int UnixID;
+ int Balance;
+ int Flags;
+} tFileUser;
+
typedef struct sUser {
int UnixID;
+ const char *Name;
int Balance;
int Flags;
} tUser;
* UCC (University [of WA] Computer Club) Electronic Accounting System
*
* cokebank.c - Coke-Bank management
+ * > Simple custom database format (uses PAM for usernames)
*
* This file is licenced under the 3-clause BSD Licence. See the file COPYING
* for full details.
#define HACK_TPG_NOAUTH 1
#define HACK_ROOT_NOAUTH 1
+#define indexof(array, ent) (((intptr_t)(ent)-(intptr_t)(array))/sizeof((array)[0]))
+
+// === TYPES ===
+struct sAcctIterator
+{
+ int CurUser;
+
+ int Sort;
+
+ int MinBalance;
+ int MaxBalance;
+
+ int FlagMask;
+ int FlagValue;
+};
+
// === PROTOTYPES ===
void Init_Cokebank(const char *Argument);
static int Bank_int_ReadDatabase(void);
static int Bank_int_WriteEntry(int ID);
int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
int Bank_CreateUser(const char *Username);
- int Bank_GetMaxID(void);
+tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues,
+ int Flags, int MinMaxBalance, time_t LastSeen);
int Bank_GetUserID(const char *Username);
int Bank_GetBalance(int User);
int Bank_GetFlags(int User);
int Bank_int_AddUser(const char *Username);
char *Bank_GetUserName(int User);
int Bank_int_GetUnixID(const char *Username);
- int Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString);
+ int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password);
#if USE_LDAP
char *ReadLDAPValue(const char *Filter, char *Value);
#endif
tUser *gaBank_Users;
int giBank_NumUsers;
FILE *gBank_File;
+tUser **gaBank_UsersByName;
+tUser **gaBank_UsersByBalance;
// === CODE ===
-/**
+/*
* \brief Load the cokebank database
*/
void Init_Cokebank(const char *Argument)
#endif
}
+/**
+ * \brief Name compare function for qsort
+ */
+static int Bank_int_CompareNames(const void *Ent1, const void *Ent2)
+{
+ const tUser *user1 = *(const tUser**)Ent1;
+ const tUser *user2 = *(const tUser**)Ent2;
+
+ return strcmp(user1->Name, user2->Name);
+}
+
+/**
+ * \brief Name compare function for qsort
+ */
+static int Bank_int_CompareBalance(const void *Ent1, const void *Ent2)
+{
+ const tUser *user1 = *(const tUser**)Ent1;
+ const tUser *user2 = *(const tUser**)Ent2;
+
+ return user1->Balance - user2->Balance;
+}
+
#if 1
static int Bank_int_ReadDatabase(void)
{
+ int i;
if( gaBank_Users ) return 1;
// Get size
fseek(gBank_File, 0, SEEK_END);
- giBank_NumUsers = ftell(gBank_File) / sizeof(gaBank_Users[0]);
+ giBank_NumUsers = ftell(gBank_File) / sizeof(tFileUser);
fseek(gBank_File, 0, SEEK_SET);
+
+ // Allocate structures
+ gaBank_Users = malloc( giBank_NumUsers * sizeof(tUser) );
+ gaBank_UsersByName = malloc( giBank_NumUsers * sizeof(tUser*) );
+ gaBank_UsersByBalance = malloc( giBank_NumUsers * sizeof(tUser*) );
// Read data
- gaBank_Users = malloc( giBank_NumUsers * sizeof(gaBank_Users[0]) );
- fread(gaBank_Users, sizeof(gaBank_Users[0]), giBank_NumUsers, gBank_File);
+ for( i = 0; i < giBank_NumUsers; i ++ )
+ {
+ tFileUser fu;
+ fread(&fu, sizeof(tFileUser), 1, gBank_File);
+ gaBank_Users[i].Name = NULL;
+ gaBank_Users[i].UnixID = fu.UnixID;
+ gaBank_Users[i].Balance = fu.Balance;
+ gaBank_Users[i].Flags = fu.Flags;
+ gaBank_Users[i].Name = Bank_GetUserName(i);
+ gaBank_UsersByName[i] = &gaBank_Users[i]; // Add to name index
+ gaBank_UsersByBalance[i] = &gaBank_Users[i]; // Add to balance index
+ }
+
+ // Sort indexes
+ qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
+ qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
return 0;
}
{
fgets(buf, BUFSIZ-1, gBank_File);
}
- #endif
}
#endif
static int Bank_int_WriteEntry(int ID)
{
+ tFileUser fu;
if( ID < 0 || ID >= giBank_NumUsers ) {
return -1;
}
// Commit to file
- fseek(gBank_File, ID*sizeof(gaBank_Users[0]), SEEK_SET);
- fwrite(&gaBank_Users[ID], sizeof(gaBank_Users[0]), 1, gBank_File);
+ fseek(gBank_File, ID*sizeof(fu), SEEK_SET);
+
+ fu.UnixID = gaBank_Users[ID].UnixID;
+ fu.Balance = gaBank_Users[ID].Balance;
+ fu.Flags = gaBank_Users[ID].Flags;
+
+ fwrite(&fu, sizeof(fu), 1, gBank_File);
return 0;
}
-/**
- * \brief Transfers money from one user to another
+/*
+ * Transfers money from one user to another
* \param SourceUser Source user
* \param DestUser Destination user
* \param Ammount Ammount of cents to move from \a SourceUser to \a DestUser
return Bank_int_AddUser(Username);
}
-int Bank_GetMaxID(void)
+tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
{
- return giBank_NumUsers;
+ tAcctIterator *ret;
+
+ ret = calloc( 1, sizeof(tAcctIterator) );
+ if( !ret )
+ return NULL;
+ ret->MinBalance = INT_MIN;
+ ret->MaxBalance = INT_MAX;
+
+ ret->FlagMask = FlagMask;
+ ret->FlagValue = FlagValues & FlagMask;
+
+ if(Flags & BANK_ITFLAG_MINBALANCE)
+ ret->MinBalance = MinMaxBalance;
+ if(Flags & BANK_ITFLAG_MAXBALANCE)
+ ret->MaxBalance = MinMaxBalance;
+
+ ret->Sort = Flags & (BANK_ITFLAG_SORTMASK|BANK_ITFLAG_REVSORT);
+
+ // Shut up GCC
+ LastSeen = 0;
+
+ //if(Flags & BANK_ITFLAG_SEENBEFORE)
+ // ret->MinBalance = MinMaxBalance;
+ //if(Flags & BANK_ITFLAG_SEENAFTER)
+ // ret->MinBalance = MinMaxBalance;
+
+ return ret;
}
-/**
+int Bank_IteratorNext(tAcctIterator *It)
+{
+ int ret;
+
+ while(It->CurUser < giBank_NumUsers)
+ {
+ switch(It->Sort)
+ {
+ case BANK_ITFLAG_SORT_NONE:
+ case BANK_ITFLAG_SORT_NONE | BANK_ITFLAG_REVSORT:
+ ret = It->CurUser;
+ break;
+ case BANK_ITFLAG_SORT_NAME:
+ ret = indexof(gaBank_Users, gaBank_UsersByName[It->CurUser]);
+ break;
+ case BANK_ITFLAG_SORT_NAME | BANK_ITFLAG_REVSORT:
+ ret = indexof(gaBank_Users, gaBank_UsersByName[giBank_NumUsers-It->CurUser]);
+ break;
+ case BANK_ITFLAG_SORT_BAL:
+ ret = indexof(gaBank_Users, gaBank_UsersByBalance[It->CurUser]);
+ printf("Sort by balance (ret = %i)\n", ret);
+ break;
+ case BANK_ITFLAG_SORT_BAL | BANK_ITFLAG_REVSORT:
+ ret = indexof(gaBank_Users, gaBank_UsersByBalance[giBank_NumUsers-It->CurUser]);
+ break;
+ default:
+ fprintf(stderr, "BUG: Unsupported sort in Bank_IteratorNext\n");
+ return -1;
+ }
+ It->CurUser ++;
+
+ if( gaBank_Users[ret].Balance < It->MinBalance )
+ continue;
+ if( gaBank_Users[ret].Balance > It->MaxBalance )
+ continue;
+ if( (gaBank_Users[ret].Flags & It->FlagMask) != It->FlagValue )
+ continue;
+
+ return ret;
+ }
+ return -1;
+}
+
+void Bank_DelIterator(tAcctIterator *It)
+{
+ free(It);
+}
+
+/*
* \brief Get the User ID of the named user
*/
int Bank_GetUserID(const char *Username)
-{
+{
+ #if 0
+ int i, size;
+ i = giBank_NumUsers / 2;
+ size = giBank_NumUsers;
+ for(;;)
+ {
+ cmp = strcmp(gaBank_UsersByName[i]->Name, Username);
+ if( cmp == 0 )
+ return indexof(gaBank_Users, gaBank_UsersByName[i];
+
+ // Not found
+ if( size == 0 )
+ return -1;
+
+ if( cmp < 0 )
+ i += size;
+ else
+ i -= size;
+ size /= 2;
+ }
+ #else
int i, uid;
-
uid = Bank_int_GetUnixID(Username);
// Expensive search :(
if( gaBank_Users[i].UnixID == uid )
return i;
}
+ #endif
return -1;
}
return 0;
}
-/**
- * \brief Create a new user in our database
+/*
+ * Create a new user in our database
*/
int Bank_int_AddUser(const char *Username)
{
int uid = Bank_int_GetUnixID(Username);
// Can has moar space plz?
+ // - Structures
tmp = realloc(gaBank_Users, (giBank_NumUsers+1)*sizeof(gaBank_Users[0]));
if( !tmp ) return -1;
gaBank_Users = tmp;
+ // - Name index
+ tmp = realloc(gaBank_UsersByName, (giBank_NumUsers+1)*sizeof(tUser*));
+ if( !tmp ) return -1;
+ gaBank_UsersByName = tmp;
+ // - Balance index
+ tmp = realloc(gaBank_UsersByBalance, (giBank_NumUsers+1)*sizeof(tUser*));
+ if( !tmp ) return -1;
+ gaBank_UsersByBalance = tmp;
// Crete new user
+ gaBank_Users[giBank_NumUsers].Name = NULL;
gaBank_Users[giBank_NumUsers].UnixID = uid;
gaBank_Users[giBank_NumUsers].Balance = 0;
gaBank_Users[giBank_NumUsers].Flags = 0;
+ gaBank_UsersByName[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
+ gaBank_UsersByBalance[giBank_NumUsers] = &gaBank_Users[giBank_NumUsers];
+ // Set default flags
if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
gaBank_Users[giBank_NumUsers].Flags = USER_FLAG_INTERNAL;
}
// Increment count
giBank_NumUsers ++;
+ // Get name
+ gaBank_Users[giBank_NumUsers-1].Name = Bank_GetUserName(giBank_NumUsers-1);
+
+ // Update indexes
+ qsort(gaBank_UsersByName, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareNames);
+ qsort(gaBank_UsersByBalance, giBank_NumUsers, sizeof(tUser*), Bank_int_CompareBalance);
+
+ // Save
Bank_int_WriteEntry(giBank_NumUsers - 1);
return 0;
// Unix user dependent code
// TODO: Modify to keep its own list of usernames
// ---
-/**
- * \brief Return the name the passed user
- */
char *Bank_GetUserName(int ID)
{
struct passwd *pwd;
if( ID < 0 || ID >= giBank_NumUsers )
return NULL;
+ if( gaBank_Users[ID].Name ) {
+ return strdup(gaBank_Users[ID].Name);
+ }
+
if( gaBank_Users[ID].UnixID == -1 )
return strdup(COKEBANK_SALES_ACCT);
}
-/**
- * \brief Authenticate a user
- * \return User ID, or -1 if authentication failed
+/*
+ * Authenticate a user
*/
-int Bank_GetUserAuth(const char *Salt, const char *Username, const char *PasswordString)
+int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
{
#if USE_LDAP
uint8_t hash[20];
#endif
#if 1
- // Only here to shut GCC up (until password auth is implemented
+ // Only here to shut GCC up (until password auth is implemented)
if( Salt == NULL )
return -1;
- if( PasswordString == NULL )
+ if( Password == NULL )
return -1;
#endif
#endif
#if USE_LDAP
- HexBin(hash, 20, PasswordString);
+ HexBin(hash, 20, Password);
// Build string to hash
strcpy(input, Username);
--- /dev/null
+/*
+ * OpenDispense 2
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * SQLite Coke Bank (Accounts Database)
+ *
+ * This file is licenced under the 3-clause BSD Licence. See the file
+ * COPYING for full details.
+ */
+#include "../cokebank.h"
+#include <sqlite3.h>
+
+const char * const csBank_CreateAccountQry = "CREATE TABLE IF NOT EXISTS accounts ("
+" acct_id INTEGER PRIMARY KEY NOT NULL,"
+" acct_balance INTEGER NOT NULL,"
+" acct_name STRING UNIQUE,"
+" acct_uid INTEGER UNIQUE,"
+" acct_pin INTEGER CHECK (acct_pin > 0 AND acct_pin < 10000),"
+" acct_is_disabled BOOLEAN NOT NULL DEFAULT false,"
+" acct_is_coke BOOLEAN NOT NULL DEFAULT false,"
+" acct_is_wheel BOOLEAN NOT NULL DEFAULT false,"
+" acct_is_door BOOLEAN NOT NULL DEFAULT false,"
+" acct_is_internal BOOLEAN NOT NULL DEFAULT false"
+")";
+const char * const csBank_CreateCardsQry = "CREATE TABLE IF NOT EXISTS cards ("
+" acct_id INTEGER NOT NULL,"
+" card_name STRING NOT NULL UNIQUE,"
+" FOREIGN KEY (acct_id) REFERENCES accounts (acct_id) ON DELETE CASCADE"
+// Deletion of the account frees the card ^ ^ ^
+")";
+
+// === PROTOYPES ===
+ int Bank_Initialise(const char *Argument);
+ int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
+ int Bank_GetUserFlags(int UserID);
+ int Bank_SetUserFlags(int UserID, int Mask, int Value);
+sqlite3_stmt *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query);
+
+// === GLOBALS ===
+sqlite3 *gBank_Database;
+
+// === CODE ===
+int Bank_Initialise(const char *Argument)
+{
+ int rv;
+ char *errmsg;
+ // Open database
+ rv = sqlite3_open(Argument, &gBank_Database);
+ if(rv != 0)
+ {
+ fprintf(stderr, "CokeBank: Unable to open database '%s'\n", Argument);
+ fprintf(stderr, "Reason: %s\n", sqlite3_errmsg(gBank_Database));
+ sqlite3_close(gBank_Database);
+ return 1;
+ }
+
+ // Check structure
+ rv = sqlite3_exec(gBank_Database, "SELECT acct_id FROM accounts LIMIT 1", NULL, NULL, &errmsg);
+ if( rv == SQLITE_OK )
+ {
+ // NOP
+ }
+ else if( rv == SQLITE_NOTFOUND )
+ {
+ sqlite3_free(errmsg);
+ // Create tables
+ // - Accounts
+ rv = sqlite3_exec(gBank_Database, csBank_CreateAccountQry, NULL, NULL, &errmsg);
+ if( rv != SQLITE_OK ) {
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return 1;
+ }
+ // - Mifare relation
+ rv = sqlite3_exec(gBank_Database, csBank_CreateCardsQry, NULL, NULL, &errmsg);
+ if( rv != SQLITE_OK ) {
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return 1;
+ }
+ }
+ else
+ {
+ // Unknown error
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Move Money
+ */
+int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
+{
+ char *query
+ int rv;
+ char *errmsg;
+
+ // Begin SQL Transaction
+ sqlite3_exec(gBank_Database, "BEGIN TRANSACTION", NULL, NULL, NULL);
+
+ // Take from the source
+ query = mkstr("UPDATE accounts SET acct_balance=acct_balance-%i WHERE acct_id=%i", Ammount, SourceUser);
+ rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
+ free(query);
+ if( rv != SQLITE_OK )
+ {
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errMsg);
+ sqlite3_query(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
+ return 1;
+ }
+
+ // Give to the destination
+ query = mkstr("UPDATE accounts SET acct_balance=acct_balance+%i WHERE acct_id=%i", Ammount, DestUser);
+ rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
+ free(query);
+ if( rv != SQLITE_OK )
+ {
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ sqlite3_query(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
+ return 1;
+ }
+
+ // Commit transaction
+ sqlite3_query(gBank_Database, "COMMIT", NULL, NULL, NULL);
+
+ return 0;
+}
+
+/*
+ * Get user flags
+ */
+int Bank_GetUserFlags(int UserID)
+{
+ sqlite3_stmt *statement;
+ char *query;
+ int rv;
+ int ret;
+
+ // Build Query
+ query = mkstr("SELECT acct_is_disabled,acct_is_coke,acct_is_wheel,acct_is_door,acct_is_internal FROM accounts WHERE acct_id=%i LIMIT 1", UserID);
+ rv = sqlite3_prepare_v2(gBank_Database, query, strlen(query)+1, &statement, NULL);
+ free(query);
+ if( rv != SQLITE_OK ) {
+ fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
+ return -1;
+ }
+
+ // Execute Query
+ rv = sqlite3_step(statement);
+ if( rv != SQLITE_ROW )
+ {
+ sqlite3_finalise(statement);
+ if( rv == SQLITE_DONE )
+ {
+ return -1; // User not found
+ }
+ if( rv != SQLITE_OK ) {
+ fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
+ return -1;
+ }
+ }
+
+ // Get Flags
+ ret = 0;
+ // - Disabled
+ if( sqlite3_column_int(statement, 0) ) ret |= USER_FLAG_DISABLED;
+ // - Coke
+ if( sqlite3_column_int(statement, 1) ) ret |= USER_FLAG_COKE;
+ // - Wheel
+ if( sqlite3_column_int(statement, 2) ) ret |= USER_FLAG_WHEEL;
+ // - Door
+ if( sqlite3_column_int(statement, 3) ) ret |= USER_FLAG_DOOR;
+ // - Internal
+ if( sqlite3_column_int(statement, 3) ) ret |= USER_FLAG_INTERNAL;
+
+ // Destroy and return
+ sqlite3_finalise(statement);
+
+ return ret;
+}
+
+/*
+ * Set user flags
+ */
+int Bank_SetUserFlags(int UserID, int Mask, int Value)
+{
+ char *query;
+ int rv;
+ char *errmsg;
+
+ #define MAP_FLAG(name, flag) (Mask&(flag)?(Value&(flag)?","name"=1":","name"=0"))
+ query = mkstr(
+ "UDPATE accounts WHERE acct_id=%i SET acct_id=acct_id%s%s%s%s%s",
+ MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
+ MAP_FLAG("acct_is_wheel", USER_FLAG_WHEEL),
+ MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
+ MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
+ MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED)
+ );
+ #undef MAP_FLAG
+
+ // Execute Query
+ rv = sqlite3_query(gBank_Database, query, NULL, NULL, &errmsg);
+ free(query);
+ if( rv != SQLITE_OK )
+ {
+ fprintf(stderr, "SQLite Error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Get user balance
+ */
+int Bank_GetBalance(int User)
+{
+ sqlite3_stmt *statement;
+ char *query;
+
+ query = mkstr("SELECT acct_balance FROM accounts WHERE acct_id=%i", User);
+
+}
+
+
+sqlite3_stmt *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query)
+{
+ int rv;
+ sqlite3_stmt *ret;
+ rv = sqlite3_prepare_v2(Database, Query, strlen(Query)+1, &ret, NULL);
+ free(query);
+ if( rv != SQLITE_OK ) {
+ fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
+ fprintf(stderr, "query = \"%s\"\n", Query);
+ return NULL;
+ }
+
+ return ret;
+}
// Sanity please
if( Item < 0 || Item > 6 ) return -1; // -EYOURBAD
+ // Can't dispense if the machine is not connected
+ if( giCoke_SerialFD == -1 )
+ return -2;
+
+ // Wait for a prompt
ret = 0;
do {
write(giCoke_SerialFD, "d7\r\n", 4);
// Sanity please
if( Item < 0 || Item > 6 ) return -1;
+ // Can't dispense if the machine is not connected
+ if( giCoke_SerialFD == -1 )
+ return -2;
+
// Wait for prompt
i = 0;
do {
{
int i;
- if( Args != NULL || strlen(Args) ) {
+ if( Args != NULL && strlen(Args) ) {
sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
return ;
}
{
int i, numRet = 0;
int maxBal = INT_MAX, minBal = INT_MIN;
- int numUsr = Bank_GetMaxID();
+ tAcctIterator *it;
+ int sort = BANK_ITFLAG_SORT_NAME;
// Parse arguments
if( Args && strlen(Args) )
maxBal = atoi(max);
}
+ // Create iterator
+ if( maxBal != INT_MAX )
+ it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MAXBALANCE, maxBal, 0);
+ else if( minBal != INT_MIN )
+ it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MINBALANCE, minBal, 0);
+ else
+ it = Bank_Iterator(0, 0, sort, 0, 0);
+
// Get return number
- for( i = 0; i < numUsr; i ++ )
+ while( (i = Bank_IteratorNext(it)) != -1 )
{
int bal = Bank_GetBalance(i);
numRet ++;
}
+ Bank_DelIterator(it);
+
// Send count
sendf(Client->Socket, "201 Users %i\n", numRet);
- for( i = 0; i < numUsr; i ++ )
+
+ // Create iterator
+ if( maxBal != INT_MAX )
+ it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MAXBALANCE, maxBal, 0);
+ else if( minBal != INT_MIN )
+ it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MINBALANCE, minBal, 0);
+ else
+ it = Bank_Iterator(0, 0, sort, 0, 0);
+
+ while( (i = Bank_IteratorNext(it)) != -1 )
{
int bal = Bank_GetBalance(i);
_SendUserInfo(Client, i);
}
+ Bank_DelIterator(it);
+
sendf(Client->Socket, "200 List End\n");
}