From 2b718e476dbff288ddf4e948024bcb6c92cc3281 Mon Sep 17 00:00:00 2001 From: John Hodge Date: Sun, 20 Feb 2011 09:42:20 +0800 Subject: [PATCH] Implemented `dispense ` - Also cleaned up some bugs in the server --- items.cfg | 7 +- src/client/main.c | 233 ++++++++++++++++++++++++++++++++++--- src/cokebank_sqlite/main.c | 1 + src/server/server.c | 53 ++++++--- 4 files changed, 264 insertions(+), 30 deletions(-) diff --git a/items.cfg b/items.cfg index f8e7106..df3305a 100644 --- a/items.cfg +++ b/items.cfg @@ -1,4 +1,7 @@ +# Items prefixed with a '-' will not show up in the GUI +# (Actually aren't returned by ENUM_ITEMS) + # Type ID Price Description # Drinks @@ -13,11 +16,11 @@ coke 6 93 Coke # Pseudo items pseudo 0 10 laserprint 5 pages # print 5 pages pseudo 1 30 manual phone # Ring Ring! -pseudo 2 128 clue # clue.flac - Dont' Ask +pseudo 2 128 clue # clue.flac - Don't Ask pseudo 3 3500 polo postorder # Polo Shirt! (With UCC Sun Logo) pseudo 4 2500 membership # here comes the money! -door 0 0 Open Door # Open Sesame +-door 0 0 Open Door # Open Sesame # Snack machine -snack 13 128 Smiths Salt & Vinegar diff --git a/src/client/main.c b/src/client/main.c index 46e1259..f30ce43 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -48,7 +48,8 @@ void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const int OpenConnection(const char *Host, int Port); int Authenticate(int Socket); void PopulateItemList(int Socket); - int DispenseItem(int Socket, int ItemID); + int Dispense_ItemInfo(int Socket, const char *Type, int ID); + int DispenseItem(int Socket, const char *Type, int ID); int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason); int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason); int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason); @@ -71,12 +72,13 @@ char *gsDispenseServer = "heathred"; tItem *gaItems; int giNumItems; -regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex; +regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex; int gbIsAuthenticated = 0; char *gsItemPattern; //!< Item pattern char *gsEffectiveUser; //!< '-u' Dispense as another user int gbUseNCurses = 0; //!< '-G' Use the NCurses GUI? + int gbDryRun = 0; //!< '-n' Read-only int giMinimumBalance = INT_MIN; //!< '-m' Minumum balance for `dispense acct` int giMaximumBalance = INT_MAX; //!< '-M' Maximum balance for `dispense acct` @@ -96,6 +98,8 @@ int main(int argc, char *argv[]) CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED); // > Code 'User' Username Balance Flags CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED); + // > Item Ident + CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED); // Parse Arguments for( i = 1; i < argc; i ++ ) @@ -112,19 +116,49 @@ int main(int argc, char *argv[]) return 0; case 'm': // Minimum balance + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -m takes an argument\n", argv[0]); + ShowUsage(); + } giMinimumBalance = atoi(argv[++i]); break; case 'M': // Maximum balance + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -M takes an argument\n", argv[0]); + ShowUsage(); + } giMaximumBalance = atoi(argv[++i]); break; case 'u': // Override User + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -u takes an argument\n", argv[0]); + ShowUsage(); + } gsEffectiveUser = argv[++i]; break; + case 'H': // Override remote host + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -H takes an argument\n", argv[0]); + ShowUsage(); + } + gsDispenseServer = argv[++i]; + break; + case 'P': // Override remote port + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -P takes an argument\n", argv[0]); + ShowUsage(); + } + giDispensePort = atoi(argv[++i]); + break; + case 'G': // Use GUI gbUseNCurses = 1; break; + case 'n': // Dry Run / read-only + gbDryRun = 1; + break; } continue; @@ -194,7 +228,6 @@ int main(int argc, char *argv[]) ShowUsage(); return -1; } - // TODO: `dispense give` // argv[i+1]: Destination // argv[i+2]: Ammount @@ -305,9 +338,89 @@ int main(int argc, char *argv[]) if( gsItemPattern ) { - // TODO: Implement `dispense ` - printf("TODO: Implement `dispense `\n"); - i = -1; + regmatch_t matches[3]; + // Door (hard coded) + if( strcmp(gsItemPattern, "door") == 0 ) + { + // Connect, Authenticate, dispense and close + sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return -1; + Authenticate(sock); + DispenseItem(sock, "door", 0); + close(sock); + return 0; + } + // Item id (:) + else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 ) + { + char *ident; + int id; + + // Get and finish ident + ident = gsItemPattern + matches[1].rm_so; + gsItemPattern[matches[1].rm_eo] = '\0'; + // Get ID + id = atoi( gsItemPattern + matches[2].rm_so ); + + // Connect, Authenticate, dispense and close + sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return -1; + + Dispense_ItemInfo(sock, ident, id); + + Authenticate(sock); + DispenseItem(sock, ident, id); + close(sock); + return 0; + } + // Item number (6 = coke) + else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 ) + { + i = atoi(gsItemPattern); + } + // Item prefix + else + { + int j; + int best = -1; + for( i = 0; i < giNumItems; i ++ ) + { + // Prefix match (with case-insensitive match) + for( j = 0; gsItemPattern[j]; j ++ ) + { + if( gaItems[i].Desc[j] == gsItemPattern[j] ) + continue; + if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) ) + continue; + break; + } + // Check if the prefix matched + if( gsItemPattern[j] != '\0' ) + continue; + + // Prefect match + if( gaItems[i].Desc[j] == '\0' ) { + best = i; + break; + } + + // Only one match allowed + if( best == -1 ) { + best = i; + } + else { + // TODO: Allow ambiguous matches? + } + } + + if( best == -1 ) + { + fprintf(stderr, "No item matches the passed string\n"); + return 1; + } + + i = best; + } } else if( gbUseNCurses ) { @@ -316,7 +429,7 @@ int main(int argc, char *argv[]) else { // Very basic dispense interface - for( i = 0; i < giNumItems; i ++ ) { + for( i = 0; i < giNumItems; i ++ ) { printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID, gaItems[i].Price, gaItems[i].Desc); } @@ -353,8 +466,11 @@ int main(int argc, char *argv[]) // Connect, Authenticate, dispense and close sock = OpenConnection(gsDispenseServer, giDispensePort); if( sock < 0 ) return -1; + + Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID); + Authenticate(sock); - DispenseItem(sock, i); + DispenseItem(sock, gaItems[i].Type, gaItems[i].ID); close(sock); } @@ -947,19 +1063,74 @@ void PopulateItemList(int Socket) free(buf); } + +/** + * \brief Get information on an item + * \return Boolean Failure + */ +int Dispense_ItemInfo(int Socket, const char *Type, int ID) +{ + int responseCode; + char *buf; + regmatch_t matches[7]; + char *type, *desc; + int id, price; + + // Dispense! + sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID); + buf = ReadLine(Socket); + + responseCode = atoi(buf); + switch( responseCode ) + { + case 202: + break; + + case 406: + printf("Bad item name\n"); + free(buf); + return 1; + + default: + printf("Unknown response code %i ('%s')\n", responseCode, buf); + free(buf); + return -1; + } + + + RunRegex(&gItemRegex, buf, 7, matches, "Malformed server response"); + + buf[ matches[3].rm_eo ] = '\0'; + + type = buf + matches[3].rm_so; buf[matches[3].rm_eo] = '\0'; + id = atoi( buf + matches[4].rm_so ); + price = atoi( buf + matches[5].rm_so ); + desc = buf + matches[6].rm_so; buf[matches[6].rm_eo] = '\0'; + + printf("%8s:%-2i %2i.%02i %s\n", type, id, price/100, price%100, desc); + + free(buf); + + return 0; +} + /** * \brief Dispense an item * \return Boolean Failure */ -int DispenseItem(int Socket, int ItemID) +int DispenseItem(int Socket, const char *Type, int ID) { int ret, responseCode; char *buf; - if( ItemID < 0 || ItemID > giNumItems ) return -1; + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } // Dispense! - sendf(Socket, "DISPENSE %s:%i\n", gaItems[ItemID].Type, gaItems[ItemID].ID); + sendf(Socket, "DISPENSE %s:%i\n", Type, ID); buf = ReadLine(Socket); responseCode = atoi(buf); @@ -978,7 +1149,7 @@ int DispenseItem(int Socket, int ItemID) ret = 1; break; case 406: - printf("Bad item name, bug report\n"); + printf("Bad item name\n"); ret = 1; break; case 500: @@ -1007,6 +1178,12 @@ int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const c char *buf; int responseCode; + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason); buf = ReadLine(Socket); @@ -1042,6 +1219,12 @@ int Dispense_SetBalance(int Socket, const char *Username, int Balance, const cha char *buf; int responseCode; + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason); buf = ReadLine(Socket); @@ -1084,6 +1267,12 @@ int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Rea return 0; } + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason); buf = ReadLine(Socket); @@ -1130,6 +1319,12 @@ int Dispense_Donate(int Socket, int Ammount, const char *Reason) return 0; } + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + sendf(Socket, "DONATE %i %s\n", Ammount, Reason); buf = ReadLine(Socket); @@ -1285,6 +1480,12 @@ int Dispense_AddUser(int Socket, const char *Username) char *buf; int responseCode, ret; + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + sendf(Socket, "USER_ADD %s\n", Username); buf = ReadLine(Socket); @@ -1323,6 +1524,12 @@ int Dispense_SetUserType(int Socket, const char *Username, const char *TypeStrin char *buf; int responseCode, ret; + // Check for a dry run + if( gbDryRun ) { + printf("Dry Run - No action\n"); + return 0; + } + // TODO: Pre-validate the string sendf(Socket, "USER_FLAGS %s %s\n", Username, TypeString); @@ -1463,7 +1670,7 @@ int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *match int ret; ret = regexec(regex, string, nMatches, matches, 0); - if( ret ) { + if( ret && errorMessage ) { size_t len = regerror(ret, regex, NULL, 0); char errorStr[len]; regerror(ret, regex, errorStr, len); diff --git a/src/cokebank_sqlite/main.c b/src/cokebank_sqlite/main.c index c386ee9..6165107 100644 --- a/src/cokebank_sqlite/main.c +++ b/src/cokebank_sqlite/main.c @@ -592,6 +592,7 @@ sqlite3_stmt *Bank_int_QuerySingle(sqlite3 *Database, const char *Query) */ int Bank_int_IsValidName(const char *Name) { + if( !Name ) return 0; while(*Name) { if( *Name == '\'' ) return 0; diff --git a/src/server/server.c b/src/server/server.c index 04f1c39..dbb08cb 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -296,22 +296,23 @@ void Server_ParseClientCommand(tClient *Client, char *CommandString) { char *command, *args; int i; - #if 0 - char **argList; - int numArgs = 0; - #endif + + if( giDebugLevel >= 2 ) + Debug(Client, "Server_ParseClientCommand: (CommandString = '%s')", CommandString); if( Server_int_ParseArgs(1, CommandString, &command, &args, NULL) ) { // Is this an error? (just ignore for now) - args = ""; + //args = ""; } // Find command for( i = 0; i < NUM_COMMANDS; i++ ) { - if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) { + if(strcmp(command, gaServer_Commands[i].Name) == 0) { + if( giDebugLevel >= 2 ) + Debug(Client, "CMD %s - \"%s\"", command, args); gaServer_Commands[i].Function(Client, args); return ; } @@ -394,7 +395,11 @@ void Server_Cmd_AUTOAUTH(tClient *Client, char *Args) { char *username; - Server_int_ParseArgs(0, Args, &username, NULL); + if( Server_int_ParseArgs(0, Args, &username, NULL) ) + { + sendf(Client->Socket, "407 AUTOAUTH takes 1 argument\n"); + return ; + } // Check if trusted if( !Client->bIsTrusted ) { @@ -415,6 +420,8 @@ void Server_Cmd_AUTOAUTH(tClient *Client, char *Args) // You can't be an internal account if( Bank_GetFlags(Client->UID) & USER_FLAG_INTERNAL ) { + if(giDebugLevel) + Debug(Client, "Autoauth as '%s', not allowed", username); Client->UID = -1; sendf(Client->Socket, "401 Auth Failure\n"); return ; @@ -1163,9 +1170,20 @@ int sendf(int Socket, const char *Format, ...) int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...) { va_list args; - char savedChar = *ArgStr; + char savedChar; char **dest; va_start(args, ArgStr); + + // Check for null + if( !ArgStr ) + { + while( (dest = va_arg(args, char **)) ) + *dest = NULL; + va_end(args); + return 1; + } + + savedChar = *ArgStr; while( (dest = va_arg(args, char **)) ) { @@ -1180,37 +1198,42 @@ int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...) do { *dest = NULL; } while( (dest = va_arg(args, char **)) ); + va_end(args); return -1; } - // Set destination - *dest = ArgStr; - if( *ArgStr == '"' ) { + ArgStr ++; + *dest = ArgStr; // Read until quote while( *ArgStr && *ArgStr != '"' ) ArgStr ++; } else { + // Set destination + *dest = ArgStr; // Read until a space while( *ArgStr && *ArgStr != ' ' && *ArgStr != '\t' ) ArgStr ++; } savedChar = *ArgStr; // savedChar is used to un-mangle the last string *ArgStr = '\0'; + ArgStr ++; } + va_end(args); // Oops, extra arguments, and greedy not set - if( savedChar == ' ' && bUseLongLast ) + if( (savedChar == ' ' || savedChar == '\t') && !bUseLongLast ) { return -1; + } // Un-mangle last - if(bUseLongLast) + if(bUseLongLast) { + ArgStr --; *ArgStr = savedChar; - - va_end(args); + } return 0; // Success! } -- 2.20.1