X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=src%2Fclient%2Fmain.c;h=f30ce43b7f5d7f5f08e30557bb5925fb9e674f48;hb=2b718e476dbff288ddf4e948024bcb6c92cc3281;hp=9b136f896fc80cdb662b3a3b9c679a41a99396b7;hpb=eaaa22b9437e80a965c8bcec480d5d023dec3a03;p=tpg%2Fopendispense2.git diff --git a/src/client/main.c b/src/client/main.c index 9b136f8..f30ce43 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -27,6 +27,7 @@ #define USE_NCURSES_INTERFACE 0 #define DEBUG_TRACE_SERVER 0 +#define USE_AUTOAUTH 1 // === TYPES === typedef struct sItem { @@ -47,9 +48,12 @@ 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); + int Dispense_Donate(int Socket, int Ammount, const char *Reason); int Dispense_EnumUsers(int Socket); int Dispense_ShowUser(int Socket, const char *Username); void _PrintUserLine(const char *Line); @@ -63,17 +67,18 @@ char *trim(char *string); void CompileRegex(regex_t *regex, const char *pattern, int flags); // === GLOBALS === -char *gsDispenseServer = "localhost"; +char *gsDispenseServer = "heathred"; int giDispensePort = 11020; 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` @@ -93,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 ++ ) @@ -109,24 +116,57 @@ 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; } + // + // `dispense acct` + // - if( strcmp(arg, "acct") == 0 ) { // Connect to server @@ -157,8 +197,19 @@ int main(int argc, char *argv[]) // argv[i+2]: Ammount // argv[i+3]: Reason - // Alter balance - Dispense_AlterBalance(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]); + if( argv[i+2][0] == '=' ) { + // Set balance + if( argv[i+2][1] != '0' && atoi(&argv[i+2][1]) == 0 ) { + fprintf(stderr, "Error: Invalid balance to be set\n"); + exit(1); + } + + Dispense_SetBalance(sock, argv[i+1], atoi(argv[i+2]+1), argv[i+3]); + } + else { + // Alter balance + Dispense_AlterBalance(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]); + } } // Show user information @@ -170,14 +221,13 @@ int main(int argc, char *argv[]) // // `dispense give` // - "Here, have some money." - else if( strcmp(arg, "give") == 0 ) + if( strcmp(arg, "give") == 0 ) { if( i + 3 >= argc ) { fprintf(stderr, "`dispense give` takes three arguments\n"); ShowUsage(); return -1; } - // TODO: `dispense give` // argv[i+1]: Destination // argv[i+2]: Ammount @@ -196,8 +246,8 @@ int main(int argc, char *argv[]) } // // `dispense user` - // - User administration (Wheel Only) - else if( strcmp(arg, "user") == 0 ) + // - User administration (Admin Only) + if( strcmp(arg, "user") == 0 ) { // Check argument count if( i + 1 >= argc ) { @@ -244,6 +294,31 @@ int main(int argc, char *argv[]) } return 0; } + + // Donation! + if( strcmp(arg, "donate") == 0 ) + { + // Check argument count + if( i + 2 >= argc ) { + fprintf(stderr, "Error: `dispense donate` requires two arguments\n"); + ShowUsage(); + exit(1); + } + + // Connect to server + sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return -1; + + // Attempt authentication + if( Authenticate(sock) ) + return -1; + + // Do donation + Dispense_Donate(sock, atoi(argv[i+1]), argv[i+1]); + + return 0; + } + else { // Item name / pattern gsItemPattern = arg; @@ -254,18 +329,98 @@ int main(int argc, char *argv[]) // Connect to server sock = OpenConnection(gsDispenseServer, giDispensePort); if( sock < 0 ) return -1; - - // Authenticate - Authenticate(sock); // Get items PopulateItemList(sock); + // Disconnect from server + close(sock); + 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 ) { @@ -273,7 +428,8 @@ int main(int argc, char *argv[]) } else { - for( i = 0; i < giNumItems; i ++ ) { + // Very basic dispense interface + 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); } @@ -303,11 +459,20 @@ int main(int argc, char *argv[]) } } + // Check for a valid item ID if( i >= 0 ) - DispenseItem(sock, i); - - close(sock); + { + // 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, gaItems[i].Type, gaItems[i].ID); + close(sock); + } return 0; } @@ -316,21 +481,28 @@ void ShowUsage(void) { printf( "Usage:\n" + " == Everyone ==\n" " dispense\n" " Show interactive list\n" " dispense \n" " Dispense named item\n" " dispense give \"\"\n" - " Give some of your money away\n" + " Give money to another user\n" + " dispense donate \"\"\n" + " Donate to the club\n" + " == Coke members == \n" " dispense acct []\n" " Show user balances\n" " dispense acct [+-] \"\"\n" - " Alter a account value (Coke members only)\n" + " Alter a account value\n" + " == Dispense administrators ==\n" + " dispense acct = \"\"\n" + " Set an account balance\n" " dispense user add \n" - " Create new coke account (Wheel members only)\n" - " dispense user type \n" + " Create new coke account (Admins only)\n" + " dispense user type \n" " Alter a user's flags\n" - " is a comma-separated list of user,coke,wheel,disabled\n" + " is a comma-separated list of user, coke, admin or disabled\n" " Flags are removed by preceding the name with '-' or '!'\n" "\n" "General Options:\n" @@ -623,16 +795,24 @@ int OpenConnection(const char *Host, int Port) return -1; } - #if USE_AUTOAUTH + if( geteuid() == 0 ) { + int i; struct sockaddr_in localAddr; memset(&localAddr, 0, sizeof(localAddr)); localAddr.sin_family = AF_INET; // IPv4 - localAddr.sin_port = 1023; // IPv4 - // Attempt to bind to low port for autoauth - bind(sock, &localAddr, sizeof(localAddr)); + + // Loop through all the top ports until one is avaliable + for( i = 1001; i < 1024; i ++) + { + localAddr.sin_port = htons(i); // IPv4 + // Attempt to bind to low port for autoauth + if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 ) + break; + } + if( i == 1024 ) + printf("Warning: AUTOAUTH unavaliable\n"); } - #endif if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) { fprintf(stderr, "Failed to connect to server\n"); @@ -883,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); @@ -914,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: @@ -943,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); @@ -970,7 +1211,45 @@ int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const c } /** - * \brief Alter a user's balance + * \brief Set a user's balance + * \note Only avaliable to dispense admins + */ +int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason) +{ + 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); + + responseCode = atoi(buf); + free(buf); + + switch(responseCode) + { + case 200: return 0; // OK + case 403: // Not in coke + fprintf(stderr, "You are not an admin\n"); + return 1; + case 404: // Unknown user + fprintf(stderr, "Unknown user '%s'\n", Username); + return 2; + default: + fprintf(stderr, "Unknown response code %i\n", responseCode); + return -1; + } + + return -1; +} + +/** + * \brief Give money to another user */ int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason) { @@ -988,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); @@ -1014,6 +1299,54 @@ int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Rea return -1; } + +/** + * \brief Donate money to the club + */ +int Dispense_Donate(int Socket, int Ammount, const char *Reason) +{ + char *buf; + int responseCode; + + if( Ammount < 0 ) { + printf("Sorry, you can only give, you can't take.\n"); + return -1; + } + + // Fast return on zero + if( Ammount == 0 ) { + printf("Are you actually going to give any?\n"); + 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); + + responseCode = atoi(buf); + free(buf); + + switch(responseCode) + { + case 200: return 0; // OK + + case 402: + fprintf(stderr, "Insufficient balance\n"); + return 1; + + default: + fprintf(stderr, "Unknown response code %i\n", responseCode); + return -1; + } + + return -1; +} + /** * \brief Enumerate users */ @@ -1026,15 +1359,15 @@ int Dispense_EnumUsers(int Socket) if( giMinimumBalance != INT_MIN ) { if( giMaximumBalance != INT_MAX ) { - sendf(Socket, "ENUM_USERS %i %i\n", giMinimumBalance, giMaximumBalance); + sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance); } else { - sendf(Socket, "ENUM_USERS %i\n", giMinimumBalance); + sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance); } } else { if( giMaximumBalance != INT_MAX ) { - sendf(Socket, "ENUM_USERS - %i\n", giMaximumBalance); + sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance); } else { sendf(Socket, "ENUM_USERS\n"); @@ -1138,7 +1471,7 @@ void _PrintUserLine(const char *Line) flags[flagsLen] = '\0'; bal = atoi(Line + matches[4].rm_so); - printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, bal%100, flags); + printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, abs(bal)%100, flags); } } @@ -1147,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); @@ -1185,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); @@ -1325,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);