X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=src%2Fclient%2Fmain.c;h=3c017a49a7cc06ba068f0a1cf665eb3c1a9d0e71;hb=9caffbc7ed251136bab144a957058e7d9f470b00;hp=1f4485d6153430140ee2a268fbe6b5dd2e1d6499;hpb=6ce2ae9922415d9f65ded1c468e45eed50758666;p=tpg%2Fopendispense2.git diff --git a/src/client/main.c b/src/client/main.c index 1f4485d..3c017a4 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -16,6 +16,8 @@ #include // close/getuid #include // INT_MIN/INT_MAX #include "common.h" +#include "../common/doregex.h" +#include "../common/config.h" #define USE_NCURSES_INTERFACE 0 #define DEBUG_TRACE_SERVER 0 @@ -29,6 +31,7 @@ // === PROTOTYPES === void ShowUsage(void); int main(int argc, char *argv[]); + int ParseArguments(int argc, char *argv[]); // --- Coke Server Communication --- // --- Helpers --- char *trim(char *string); @@ -36,8 +39,12 @@ char *trim(char *string); void CompileRegex(regex_t *regex, const char *pattern, int flags); // === GLOBALS === -char *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au"; +const char *gsConfigFile = "/etc/opendispense/client.conf"; + +const char *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au"; int giDispensePort = 11020; + int giDispenseServerSet = 0; // True if set by command line + int giDispensePortSet = 0; // True if set by command line tItem *gaItems; int giNumItems; @@ -142,11 +149,19 @@ void ShowUsage(void) " Show help text\n" " -G\n" " Use simple textual interface (instead of ncurses)\n" + " -D\n" + " Drinks only in user interface\n" " -n\n" " Dry run - Do not actually do dispenses\n" " -m \n" " -M \n" " Set the Maximum/Minimum balances shown in `dispense acct`\n" + " -f \n" + " Set the config file path (default: `/etc/opendispense/client.conf'\n" + " -H \n" + " Set a different dispense host\n" + " -P \n" + " Set a different dispense port\n" "Definitions:\n" " \n" " Item ID of the form : where is a non-empty string of alpha-numeric characters, and is a non-negative integer\n" @@ -155,551 +170,506 @@ void ShowUsage(void) ); } -int main(int argc, char *argv[]) +// +// `dispense finger` +// - Display coke@ucc.gu.uwa.edu.au finger output +// +int subcommand_finger(void) { - int sock; - int i, ret = 0; - char buffer[BUFSIZ]; - - gsTextArgs[0] = ""; - - // -- Create regular expressions - // > Code Type Count ... - CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED); // - // > Code Type Ident Status Price Desc - CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z]+):([0-9]+)\\s+(avail|sold|error)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED); - // > Code 'SALT' salt - 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); + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; - // Parse Arguments - for( i = 1; i < argc; i ++ ) - { - char *arg = argv[i]; - - if( arg[0] == '-' ) - { - switch(arg[1]) - { - case 'h': - case '?': - ShowUsage(); - return 0; - - case 'c': - if( i > 2 && strcmp(argv[i-1], "type") == 0 ) - goto _default; - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -c takes an argument\n", argv[0]); - ShowUsage(); - return -1; - } - giDispenseCount = atoi(argv[++i]); - if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) { - fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n"); - return -1; - } - - break ; - - case 'm': // Minimum balance - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -m takes an argument\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - giMinimumBalance = atoi(argv[++i]); - break; - case 'M': // Maximum balance - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -M takes an argument\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - giMaximumBalance = atoi(argv[++i]); - break; - - case 'u': // Override User - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -u takes an argument\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - gsEffectiveUser = argv[++i]; - break; - - case 'H': // Override remote host - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -H takes an argument\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - gsDispenseServer = argv[++i]; - break; - case 'P': // Override remote port - if( i + 1 >= argc ) { - fprintf(stderr, "%s: -P takes an argument\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - giDispensePort = atoi(argv[++i]); - break; - - // Set slot name/price - case 's': - if( giTextArgc != 0 ) { - fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]); - ShowUsage(); - return RV_ARGUMENTS; - } - gsTextArgs[0] = "slot"; // HACK!! - giTextArgc ++; - break; - - case 'G': // Don't use GUI - giUIMode = UI_MODE_BASIC; - break; - case 'D': // Drinks only - giUIMode = UI_MODE_DRINKSONLY; - break; - case 'n': // Dry Run / read-only - gbDryRun = 1; - break; - case '-': - if( strcmp(argv[i], "--help") == 0 ) { - ShowUsage(); - return 0; - } - else if( strcmp(argv[i], "--dry-run") == 0 ) { - gbDryRun = 1; - } - else if( strcmp(argv[i], "--drinks-only") == 0 ) { - giUIMode = UI_MODE_DRINKSONLY; - } - else if( strcmp(argv[i], "--can-select-all") == 0 ) { - gbDisallowSelectWithoutBalance = 0; - } - else { - fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]); - ShowUsage(); - return RV_ARGUMENTS; - } - break; - default: _default: - // The first argument is not allowed to begin with 'i' - // (catches most bad flags) - if( giTextArgc == 0 ) { - fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]); - ShowUsage(); - return RV_ARGUMENTS; - } - if( giTextArgc == MAX_TXT_ARGS ) - { - fprintf(stderr, "ERROR: Too many arguments\n"); - return RV_ARGUMENTS; - } - gsTextArgs[giTextArgc++] = argv[i]; - break; - } + // Get items + PopulateItemList(sock); - continue; - } + printf("The UCC Coke machine.\n\n"); - if( giTextArgc == MAX_TXT_ARGS ) + // Only get coke slot statuses + for( int i = 0; i <= 6; i ++ ) + { + const char *status; + switch(gaItems[i].Status) { - fprintf(stderr, "ERROR: Too many arguments\n"); - return RV_ARGUMENTS; + case 0: status = "Avail"; break; + case 1: status = "Sold "; break; + default: + status = "Error"; + break; } - - gsTextArgs[giTextArgc++] = argv[i]; - + printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc); } - // - // `dispense finger` - // - - if( strcmp(gsTextArgs[0], "finger") == 0 ) - { - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - - // Get items - PopulateItemList(sock); - - printf("The UCC Coke machine.\n\n"); - - // Only get coke slot statuses - for( i = 0; i <= 6; i ++ ) - { - const char *status; - switch(gaItems[i].Status) - { - case 0: status = "Avail"; break; - case 1: status = "Sold "; break; - default: - status = "Error"; - break; - } - printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc); - } + printf("\nMay your pink fish bing into the distance.\n"); - printf("\nMay your pink fish bing into the distance.\n"); + return 0; +} - return 0; - } +// +// `dispense acct` +// - Display/manipulate account balances +// +int subcommand_acct(void) +{ + int ret = 0; - // - // `dispense acct` - // - - if( strcmp(gsTextArgs[0], "acct") == 0 ) - { - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - // List accounts? - if( giTextArgc == 1 ) { - ret = Dispense_EnumUsers(sock); - close(sock); - return ret; - } - - // gsTextArgs[1]: Username - - // Alter account? - if( giTextArgc != 2 ) - { - if( giTextArgc != 4 ) { - fprintf(stderr, "`dispense acct` requires a reason\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - - // Authentication required - ret = Authenticate(sock); - if(ret) return ret; - - // gsTextArgs[1]: Username - // gsTextArgs[2]: Ammount - // gsTextArgs[3]: Reason - char *tmp = NULL; - long int balance = strtol(gsTextArgs[2]+(gsTextArgs[2][0] == '='), &tmp, 10); - if(!tmp || *tmp != '\0') { - fprintf(stderr, "dispense acct: Value must be a decimal number of cents\n"); - return RV_ARGUMENTS; - } - - if( gsTextArgs[2][0] == '=' ) { - // Set balance - ret = Dispense_SetBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]); - } - else { - // Alter balance - ret = Dispense_AlterBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]); - } - } - // On error, quit - if( ret ) { - close(sock); - return ret; - } - - // Show user information - ret = Dispense_ShowUser(sock, gsTextArgs[1]); - + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + // List accounts? + if( giTextArgc == 1 ) { + ret = Dispense_EnumUsers(sock); close(sock); return ret; } - // - // `dispense give` - // - "Here, have some money." - else if( strcmp(gsTextArgs[0], "give") == 0 ) + + // gsTextArgs[1]: Username + + // Alter account? + if( giTextArgc != 2 ) { if( giTextArgc != 4 ) { - fprintf(stderr, "`dispense give` takes three arguments\n"); + fprintf(stderr, "`dispense acct` requires a reason\n"); ShowUsage(); return RV_ARGUMENTS; } - // gsTextArgs[1]: Destination - // gsTextArgs[2]: Ammount - // gsTextArgs[3]: Reason - - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - - // Authenticate + // Authentication required ret = Authenticate(sock); if(ret) return ret; - - char *tmp = NULL; - int amt = strtol(gsTextArgs[2], &tmp, 10); - if( !tmp || *tmp != '\0' ) { - fprintf(stderr, "dispense give: Balance is invalid, must be decimal number of cents"); + + // gsTextArgs[1]: Username + // gsTextArgs[2]: Ammount + // gsTextArgs[3]: Reason + char *tmp = NULL; + long int balance = strtol(gsTextArgs[2]+(gsTextArgs[2][0] == '='), &tmp, 10); + if(!tmp || *tmp != '\0') { + fprintf(stderr, "dispense acct: Value must be a decimal number of cents\n"); return RV_ARGUMENTS; } - ret = Dispense_Give(sock, gsTextArgs[1], amt, gsTextArgs[3]); - + + if( gsTextArgs[2][0] == '=' ) { + // Set balance + ret = Dispense_ShowUser(sock, gsTextArgs[1]); + ret = Dispense_SetBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]); + } + else { + // Alter balance + ret = Dispense_AlterBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]); + } + } + // On error, quit + if( ret ) { close(sock); - return ret; } - // - // `dispense user` - // - User administration (Admin Only) - if( strcmp(gsTextArgs[0], "user") == 0 ) + + // Show user information + ret = Dispense_ShowUser(sock, gsTextArgs[1]); + + close(sock); + return ret; +} + +// +// `dispense give` +// - Transfer credit from the current user to another account +// +// "Here, have some money." +// +int subcommand_give(int argc, char *args[]) +{ + int ret; + + if( argc != 3 ) { + fprintf(stderr, "`dispense give` takes three arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + const char *dst_acct = args[0]; + const char *amt_str = args[1]; + const char *message = args[2]; + + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + // Authenticate + ret = Authenticate(sock); + if(ret) return ret; + + char *end = NULL; + long amt = strtol(amt_str, &end, 10); + if( !end || *end != '\0' ) { + fprintf(stderr, "dispense give: Balance is invalid, must be decimal number of cents"); + return RV_ARGUMENTS; + } + ret = Dispense_Give(sock, dst_acct, amt, message); + + close(sock); + return ret; +} + +// +// `dispense user` +// - User administration (Admin Only) +// +int subcommand_user(int argc, char *args[]) +{ + int ret; + + // Check argument count + if( argc == 0 ) { + fprintf(stderr, "Error: `dispense user` requires arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + // Attempt authentication + ret = Authenticate(sock); + if(ret) return ret; + + // Add new user? + if( strcmp(args[0], "add") == 0 ) { - // Check argument count - if( giTextArgc == 1 ) { - fprintf(stderr, "Error: `dispense user` requires arguments\n"); + if( giTextArgc != 3 ) { + fprintf(stderr, "Error: `dispense user add` requires an argument\n"); ShowUsage(); return RV_ARGUMENTS; } - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - - // Attempt authentication - ret = Authenticate(sock); - if(ret) return ret; - - // Add new user? - if( strcmp(gsTextArgs[1], "add") == 0 ) - { - if( giTextArgc != 3 ) { - fprintf(stderr, "Error: `dispense user add` requires an argument\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - - ret = Dispense_AddUser(sock, gsTextArgs[2]); - } - // Update a user - else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 ) - { - if( giTextArgc < 4 || giTextArgc > 5 ) { - fprintf(stderr, "Error: `dispense user type` requires two arguments\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - - if( giTextArgc == 4 ) - ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], ""); - else - ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]); - } - else - { - fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - close(sock); - return ret; + ret = Dispense_AddUser(sock, args[1]); } - // Donation! - else if( strcmp(gsTextArgs[0], "donate") == 0 ) + // Update a user + else if( strcmp(args[0], "type") == 0 || strcmp(args[0], "flags") == 0 ) { - // Check argument count - if( giTextArgc != 3 ) { - fprintf(stderr, "Error: `dispense donate` requires two arguments\n"); + if( argc < 3 || argc > 4 ) { + fprintf(stderr, "Error: `dispense user type` requires two arguments\n"); ShowUsage(); return RV_ARGUMENTS; } - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - - // Attempt authentication - ret = Authenticate(sock); - if(ret) return ret; - - // Do donation - ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]); - - close(sock); + ret = Dispense_SetUserType(sock, args[1], args[2], (argc == 3 ? "" : args[3])); + } + else + { + fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + close(sock); + return ret; +} - return ret; +// +// `dispense donate` +// - Donate money to the club +// +int subcommand_donate(int argc, char *args[]) +{ + int ret; + + // Check argument count + if( argc != 2 ) { + fprintf(stderr, "Error: `dispense donate` requires two arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + // Attempt authentication + ret = Authenticate(sock); + if(ret) return ret; + + // Do donation + ret = Dispense_Donate(sock, atoi(args[0]), args[1]); + + close(sock); + + return ret; +} + +// +// `dispense refund` +// - Refund a purchased item +// +// "Well excuuuuse me, princess" +// +int subcommand_refund(int argc, char *args[]) +{ + int ret; + + // Check argument count + if( argc != 2 && argc != 3 ) { + fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + // Connect to server + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if(sock < 0) return RV_SOCKET_ERROR; + + // Attempt authentication + ret = Authenticate(sock); + if(ret) return ret; + + int price = 0; + if( argc > 2 ) { + price = atoi(args[2]); + if( price <= 0 ) { + fprintf(stderr, "Error: Override price is invalid (should be > 0)\n"); + return RV_ARGUMENTS; + } } - // Refund an item - else if( strcmp(gsTextArgs[0], "refund") == 0 ) + + // Username, Item, cost + ret = Dispense_Refund(sock, args[0], args[1], price); + + // TODO: More + close(sock); + return ret; +} + +// +// `dispense iteminfo` +// - Get the state of an item +// +int subcommand_iteminfo(int argc, char *args[]) +{ + int ret; + + // Check argument count + if( argc != 1 ) { + fprintf(stderr, "Error: `dispense iteminfo` takes one argument\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + char *item_id = args[0]; + + regmatch_t matches[3]; + // Parse item ID + if( RunRegex(&gUserItemIdentRegex, item_id, 3, matches, NULL) != 0 ) { + fprintf(stderr, "Error: Invalid item ID passed (: expected)\n"); + return RV_ARGUMENTS; + } + char *type = item_id + matches[1].rm_so; + item_id[ matches[1].rm_eo ] = '\0'; + int id = atoi( item_id + matches[2].rm_so ); + + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + ret = Dispense_ItemInfo(sock, type, id); + close(sock); + return ret; +} + +// +// `dispense slot` +// - Update the name/price of an item +// +int subcommand_slot(int argc, char *args[]) +{ + int ret; + + // Check arguments + if( argc != 3 ) { + fprintf(stderr, "Error: `dispense slot` takes three arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + char *slot_id = args[0]; + char *price_str = args[1]; + char *newname = args[2]; + + // Parse arguments + regmatch_t matches[3]; + if( RunRegex(&gUserItemIdentRegex, slot_id, 3, matches, NULL) != 0 ) { + fprintf(stderr, "Error: Invalid item ID passed (: expected)\n"); + return RV_ARGUMENTS; + } + const char *item_type = slot_id + matches[1].rm_so; + slot_id[ matches[1].rm_eo ] = '\0'; + int item_id = atoi( slot_id + matches[2].rm_so ); + + // - Price + char *end; + int price = strtol( price_str, &end, 0 ); + if( price < 0 || *end != '\0' ) { + fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n"); + return RV_ARGUMENTS; + } + + // -- Sanity + for( char *pos = newname; *pos; pos ++ ) { - int price = 0; - // Check argument count - if( giTextArgc != 3 && giTextArgc != 4 ) { - fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n"); - ShowUsage(); + if( !isalnum(*pos) && *pos != ' ' ) { + fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n"); return RV_ARGUMENTS; } + } - // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); - if(sock < 0) return RV_SOCKET_ERROR; + // Connect & Authenticate + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + ret = Authenticate(sock); + if(ret) return ret; + + // Update the slot + ret = Dispense_SetItem(sock, item_type, item_id, price, newname); + + close(sock); + return ret; +} + +// +// `dispense pincheck` +// - Validate a user's pin (used by the snack machine) +// +int subcommand_pincheck(int argc, char *args[]) +{ + int ret; + + if( argc < 1 || argc > 2 ) { + fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + struct passwd *pwd = getpwuid( getuid() ); + gsUserName = strdup(pwd->pw_name); + + const char *pin = args[0]; + const char *user = (argc > 1 ? args[1] : gsUserName); + + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + ret = Authenticate(sock); + if(ret) return ret; + + ret = DispenseCheckPin(sock, user, pin); + + close(sock); + return ret; +} + +// +// `dispense pinset` +// - Set the pin of the current account +// +int subcommand_pinset(int argc, char *args[]) +{ + int ret; + + if( argc != 1 ) { + fprintf(stderr, "Error: `dispense pinset` takes one argument\n"); + ShowUsage(); + return RV_ARGUMENTS; + } + + const char *pin = args[0]; + + int sock = OpenConnection(gsDispenseServer, giDispensePort); + if( sock < 0 ) return RV_SOCKET_ERROR; + + ret = Authenticate(sock); + if(ret) return ret; - // Attempt authentication - ret = Authenticate(sock); - if(ret) return ret; + ret = DispenseSetPin(sock, pin); + + close(sock); + return ret; +} - if( giTextArgc == 4 ) { - price = atoi(gsTextArgs[3]); - if( price <= 0 ) { - fprintf(stderr, "Error: Override price is invalid (should be > 0)\n"); - return RV_ARGUMENTS; - } - } +int main(int argc, char *argv[]) +{ + int i, ret = 0; + char buffer[BUFSIZ]; + + gsTextArgs[0] = ""; - // Username, Item, cost - ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price); + // -- Create regular expressions + // > Code Type Count ... + CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED); // + // > Code Type Ident Status Price Desc + CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z]+):([0-9]+)\\s+(avail|sold|error)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED); + // > Code 'SALT' salt + 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); - // TODO: More - close(sock); + // Parse Arguments + ret = ParseArguments(argc, argv); + if( ret ) return ret; + + // Load config file + // - Don't check return value, leads to defaults being used + if( Config_ParseFile(gsConfigFile) ) { + fprintf(stderr, "NOTICE: Loading of config file '%s' failed, using defaults\n", gsConfigFile); } - // Query an item price - else if( strcmp(gsTextArgs[0], "iteminfo") == 0 ) - { - regmatch_t matches[3]; - char *type; - int id; - // Check argument count - if( giTextArgc != 2 ) { - fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - // Parse item ID - if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) { - fprintf(stderr, "Error: Invalid item ID passed (: expected)\n"); - return RV_ARGUMENTS; - } - type = gsTextArgs[1] + matches[1].rm_so; - gsTextArgs[1][ matches[1].rm_eo ] = '\0'; - id = atoi( gsTextArgs[1] + matches[2].rm_so ); - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - - ret = Dispense_ItemInfo(sock, type, id); - close(sock); - return ret; + // Parse config values + if( !giDispenseServerSet ) { + Config_GetValue_Str("dispense_server", &gsDispenseServer); } - // Set slot - else if( strcmp(gsTextArgs[0], "slot") == 0 ) - { - regmatch_t matches[3]; - char *item_type, *newname; - int item_id, price; - - // Check arguments - if( giTextArgc != 4 ) { - fprintf(stderr, "Error: `dispense slot` takes three arguments\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - - // Parse arguments - if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) { - fprintf(stderr, "Error: Invalid item ID passed (: expected)\n"); - return RV_ARGUMENTS; - } - item_type = gsTextArgs[1] + matches[1].rm_so; - gsTextArgs[1][ matches[1].rm_eo ] = '\0'; - item_id = atoi( gsTextArgs[1] + matches[2].rm_so ); - - // - Price - price = atoi( gsTextArgs[2] ); - if( price <= 0 && gsTextArgs[2][0] != '0' ) { - fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n"); - return RV_ARGUMENTS; - } - - // - New name - newname = gsTextArgs[3]; - // -- Sanity - { - char *pos; - for( pos = newname; *pos; pos ++ ) - { - if( !isalnum(*pos) && *pos != ' ' ) { - fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n"); - return RV_ARGUMENTS; - } - } - } - - // Connect & Authenticate - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - ret = Authenticate(sock); - if(ret) return ret; - // Update the slot - ret = Dispense_SetItem(sock, item_type, item_id, price, newname); - - close(sock); - return ret; + if (!giDispensePortSet) { + Config_GetValue_Int("dispense_port", &giDispensePort); } - // Check a user's pin - else if(strcmp(gsTextArgs[0], "pincheck") == 0) - { - if( giTextArgc < 2 || giTextArgc > 3 ) { - fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - struct passwd *pwd = getpwuid( getuid() ); - gsUserName = strdup(pwd->pw_name); - - const char *pin = gsTextArgs[1]; - const char *user = gsUserName; - if( giTextArgc == 3 ) - user = gsTextArgs[2]; - - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - ret = Authenticate(sock); - if(ret) return ret; - - ret = DispenseCheckPin(sock, user, pin); - - close(sock); - return ret; - } - // Update 'your' pin - else if(strcmp(gsTextArgs[0], "pinset") == 0) - { - if( giTextArgc != 2 ) { - fprintf(stderr, "Error: `dispense pinset` takes one argument\n"); - ShowUsage(); - return RV_ARGUMENTS; - } - - const char *pin = gsTextArgs[1]; - - sock = OpenConnection(gsDispenseServer, giDispensePort); - if( sock < 0 ) return RV_SOCKET_ERROR; - ret = Authenticate(sock); - if(ret) return ret; - ret = DispenseSetPin(sock, pin); - - close(sock); - return ret; + // Sub-commands + if( strcmp(gsTextArgs[0], "finger") == 0 ) { + return subcommand_finger(); } - // Item name / pattern - else - { + else if( strcmp(gsTextArgs[0], "acct") == 0 ) { + return subcommand_acct(); + } + else if( strcmp(gsTextArgs[0], "give") == 0 ) { + return subcommand_give(giTextArgc-1, gsTextArgs+1); + } + else if( strcmp(gsTextArgs[0], "user") == 0 ) { + return subcommand_user(giTextArgc-1, gsTextArgs+1); + } + else if( strcmp(gsTextArgs[0], "donate") == 0 ) { + return subcommand_donate(giTextArgc-1, gsTextArgs+1); + } + else if( strcmp(gsTextArgs[0], "refund") == 0 ) { + return subcommand_refund(giTextArgc-1, gsTextArgs+1); + } + else if( strcmp(gsTextArgs[0], "iteminfo") == 0 ) { + return subcommand_iteminfo(giTextArgc-1, gsTextArgs+1); + } + else if( strcmp(gsTextArgs[0], "slot") == 0 ) { + return subcommand_slot(giTextArgc-1, gsTextArgs+1); + } + else if(strcmp(gsTextArgs[0], "pincheck") == 0) { + return subcommand_pincheck(giTextArgc-1, gsTextArgs+1); + } + else if(strcmp(gsTextArgs[0], "pinset") == 0) { + return subcommand_pinset(giTextArgc-1, gsTextArgs+1); + } + else { + // Item name / pattern gsItemPattern = gsTextArgs[0]; } // Connect to server - sock = OpenConnection(gsDispenseServer, giDispensePort); + int sock = OpenConnection(gsDispenseServer, giDispensePort); if( sock < 0 ) return RV_SOCKET_ERROR; // Get the user's balance @@ -875,52 +845,180 @@ int main(int argc, char *argv[]) return ret; } +int ParseArguments(int argc, char *argv[]) +{ + bool rest_free = false; + for( int i = 1; i < argc; i ++ ) + { + char *arg = argv[i]; + + // If it doesn't start with a '-', or -- has been seen + // XXX: Hack - If parsing "user type", don't parse - options + bool hack_usertype = (i > 2 && strcmp(argv[i-2], "user") == 0 && strcmp(argv[i-1], "type") == 0); + if( rest_free || arg[0] != '-' || hack_usertype ) + { + if( giTextArgc == MAX_TXT_ARGS ) + { + fprintf(stderr, "ERROR: Too many arguments\n"); + return RV_ARGUMENTS; + } + + gsTextArgs[giTextArgc++] = argv[i]; + } + else if( arg[1] != '-' ) + { + switch(arg[1]) + { + case 'h': + case '?': + ShowUsage(); + exit(0); + + case 'c': + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -c takes an argument\n", argv[0]); + ShowUsage(); + return -1; + } + giDispenseCount = atoi(argv[++i]); + if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) { + fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n"); + return -1; + } + + break ; + + case 'm': // Minimum balance + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -m takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + giMinimumBalance = atoi(argv[++i]); + break; + case 'M': // Maximum balance + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -M takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + giMaximumBalance = atoi(argv[++i]); + break; + + case 'f': // Override Config File + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -f takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + gsConfigFile = argv[++i]; + break; + + case 'u': // Override User + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -u takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + gsEffectiveUser = argv[++i]; + break; + + case 'H': // Override remote host + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -H takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + gsDispenseServer = argv[++i]; + giDispenseServerSet = 1; + break; + case 'P': // Override remote port + if( i + 1 >= argc ) { + fprintf(stderr, "%s: -P takes an argument\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + giDispensePort = atoi(argv[++i]); + giDispensePortSet = 1; + break; + + // Set slot name/price + case 's': + if( giTextArgc != 0 ) { + fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]); + ShowUsage(); + return RV_ARGUMENTS; + } + gsTextArgs[0] = "slot"; // HACK!! + giTextArgc ++; + break; + + case 'G': // Don't use GUI + giUIMode = UI_MODE_BASIC; + break; + case 'D': // Drinks only + giUIMode = UI_MODE_DRINKSONLY; + break; + case 'n': // Dry Run / read-only + gbDryRun = 1; + break; + default: + fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]); + ShowUsage(); + return RV_ARGUMENTS; + } + + continue; + } + else + { + // '--' : Terminate argument processing (remainder is free) + if( arg[2] == '\0' ) { + rest_free = true; + } + else if( strcmp(arg, "--help") == 0 ) { + ShowUsage(); + exit(0); + } + else if( strcmp(arg, "--dry-run") == 0 ) { + gbDryRun = 1; + } + else if( strcmp(arg, "--drinks-only") == 0 ) { + giUIMode = UI_MODE_DRINKSONLY; + } + else if( strcmp(arg, "--can-select-all") == 0 ) { + gbDisallowSelectWithoutBalance = 0; + } + else if( strcmp(arg, "--configfile") == 0 ) { + if( i + 1 >= argc ) { + fprintf(stderr, "%s: %s takes an argument\n", argv[0], arg); + ShowUsage(); + return RV_ARGUMENTS; + } + gsConfigFile = argv[++i]; + } + else { + fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], arg); + ShowUsage(); + return RV_ARGUMENTS; + } + } + } + return 0; +} + // --------------- // --- Helpers --- // --------------- char *trim(char *string) { - int i; - + // Increment pointer while it points to a space while( isspace(*string) ) string ++; - for( i = strlen(string); i--; ) - { - if( isspace(string[i]) ) - string[i] = '\0'; - else - break; - } + // And repalce trailing spaces with NUL bytes + for( int i = strlen(string); i-- && isspace(string[i]); ) + string[i] = '\0'; return string; } - -int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage) -{ - int ret; - - ret = regexec(regex, string, nMatches, matches, 0); - if( ret && errorMessage ) { - size_t len = regerror(ret, regex, NULL, 0); - char errorStr[len]; - regerror(ret, regex, errorStr, len); - printf("string = '%s'\n", string); - fprintf(stderr, "%s\n%s", errorMessage, errorStr); - exit(-1); - } - - return ret; -} - -void CompileRegex(regex_t *regex, const char *pattern, int flags) -{ - int ret = regcomp(regex, pattern, flags); - if( ret ) { - size_t len = regerror(ret, regex, NULL, 0); - char errorStr[len]; - regerror(ret, regex, errorStr, len); - fprintf(stderr, "Regex compilation failed - %s\n", errorStr); - exit(-1); - } -}