Client - Fixed edge case where -coke is interpreted as a count of dispenses
[tpg/opendispense2.git] / src / client / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  * - Dispense Client
5  *
6  * main.c - Core and Initialisation
7  *
8  * This file is licenced under the 3-clause BSD Licence. See the file
9  * COPYING for full details.
10  */
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <ctype.h>      // isspace
15 #include <stdarg.h>
16 #include <regex.h>
17 #include <ncurses.h>
18 #include <limits.h>
19
20 #include <unistd.h>     // close
21 #include <netdb.h>      // gethostbyname
22 #include <pwd.h>        // getpwuids
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 //#include <openssl/sha.h>      // SHA1
27
28 #define USE_NCURSES_INTERFACE   0
29 #define DEBUG_TRACE_SERVER      0
30 #define USE_AUTOAUTH    1
31
32 #define MAX_TXT_ARGS    5       // Maximum number of textual arguments (including command)
33 #define DISPENSE_MULTIPLE_MAX   20      // Maximum argument to -c
34
35 enum eUI_Modes
36 {
37         UI_MODE_BASIC,  // Non-NCurses
38         UI_MODE_STANDARD,
39         UI_MODE_DRINKSONLY,
40         UI_MODE_ALL,
41         NUM_UI_MODES
42 };
43
44 enum eReturnValues
45 {
46         RV_SUCCESS,
47         RV_BAD_ITEM,
48         RV_INVALID_USER,
49         RV_PERMISSIONS,
50         RV_ARGUMENTS,
51         RV_BALANCE,
52         RV_SERVER_ERROR,        // Generic for 5xx codes
53         RV_UNKNOWN_ERROR = -1,
54         RV_SOCKET_ERROR = -2,
55         RV_UNKNOWN_RESPONSE = -3,
56 };
57
58 // === TYPES ===
59 typedef struct sItem {
60         char    *Type;
61          int    ID;
62          int    Status; // 0: Availiable, 1: Sold out, -1: Error
63         char    *Desc;
64          int    Price;
65 }       tItem;
66
67 // === PROTOTYPES ===
68 void    ShowUsage(void);
69  int    main(int argc, char *argv[]);
70 // --- GUI ---
71  int    ShowNCursesUI(void);
72  int    ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
73 void    PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
74 // --- Coke Server Communication ---
75  int    OpenConnection(const char *Host, int Port);
76  int    Authenticate(int Socket);
77  int    GetUserBalance(int Socket);
78 void    PopulateItemList(int Socket);
79  int    Dispense_ItemInfo(int Socket, const char *Type, int ID);
80  int    DispenseItem(int Socket, const char *Type, int ID);
81  int    Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
82  int    Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason);
83  int    Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason);
84  int    Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride);
85  int    Dispense_Donate(int Socket, int Ammount, const char *Reason);
86  int    Dispense_EnumUsers(int Socket);
87  int    Dispense_ShowUser(int Socket, const char *Username);
88 void    _PrintUserLine(const char *Line);
89  int    Dispense_AddUser(int Socket, const char *Username);
90  int    Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason);
91  int    Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName);
92 // --- Helpers ---
93 char    *ReadLine(int Socket);
94  int    sendf(int Socket, const char *Format, ...);
95 char    *trim(char *string);
96  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
97 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
98
99 // === GLOBALS ===
100 char    *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
101  int    giDispensePort = 11020;
102
103 tItem   *gaItems;
104  int    giNumItems;
105 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
106  int    gbIsAuthenticated = 0;
107
108 char    *gsItemPattern; //!< Item pattern
109 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
110
111  int    giUIMode = UI_MODE_STANDARD;
112  int    gbDryRun = 0;   //!< '-n' Read-only
113  int    gbDisallowSelectWithoutBalance = 1;     //!< Don't allow items to be hilighted if not affordable
114
115  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
116  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
117
118  char   *gsUserName;    //!< User that dispense will happen as
119 char    *gsUserFlags;   //!< User's flag set
120  int    giUserBalance = -1;     //!< User balance (set by Authenticate)
121  int    giDispenseCount = 1;    //!< Number of dispenses to do
122
123 char    *gsTextArgs[MAX_TXT_ARGS];
124  int    giTextArgc;
125
126 // === CODE ===
127 void ShowUsage(void)
128 {
129         printf( "Usage:\n" );
130         if( giTextArgc == 0 )
131                 printf(
132                         "  == Everyone ==\n"
133                         "    dispense\n"
134                         "        Show interactive list\n"
135                         "    dispense <name>|<index>|<itemid>\n"
136                         "        Dispense named item (<name> matches if it is a unique prefix)\n"
137                         "    dispense finger\n"
138                         "        Show the finger output\n"
139                         );
140         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
141                 printf(
142                         "    dispense give <user> <amount> \"<reason>\"\n"
143                         "        Give money to another user\n"
144                         );
145         
146         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
147                 printf(
148                         "    dispense donate <amount> \"<reason>\"\n"
149                         "        Donate to the club\n"
150                         );
151         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
152                 printf(
153                         "    dispense iteminfo <itemid>\n"
154                         "        Get the name and price for an item\n"
155                         );
156 //      if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
157 //              printf(
158 //                      "    dispense enumitems\n"
159 //                      "        List avaliable items\n"
160 //                      );
161         if( giTextArgc == 0 )
162                 printf("  == Coke members == \n");
163         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
164                 printf(
165                         "    dispense acct [<user>]\n"
166                         "        Show user balances\n"
167                         "    dispense acct <user> [+-]<amount> \"<reason>\"\n"
168                         "        Alter a account value\n"
169                         "    dispense acct <user> =<amount> \"<reason>\"\n"
170                         "        Set an account balance\n"
171                         );
172         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
173                 printf(
174                         "    dispense refund <user> <itemid> [<price>]\n"
175                         "        Refund an item to a user (with optional price override)\n"
176                         "        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
177                         "        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
178                         );
179         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
180                 printf(
181                         "    dispense slot <itemid> <price> <name>\n"
182                         "        Rename/Re-price a slot\n"
183                         );
184         if( giTextArgc == 0 )
185                 printf("  == Dispense administrators ==\n");
186         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
187                 printf(
188                         "    dispense user add <user>\n"
189                         "        Create new account\n"
190                         "    dispense user type <user> <flags> <reason>\n"
191                         "        Alter a user's flags\n"
192                         "        <flags> is a comma-separated list of user, coke, admin, internal or disabled\n"
193                         "        Flags are removed by preceding the name with '-' or '!'\n"
194                         );
195         if( giTextArgc == 0 )
196                 printf( "\n"
197                         "General Options:\n"
198                         "    -c <count>\n"
199                         "        Dispense multiple times\n"
200                         "    -u <username>\n"
201                         "        Set a different user (Coke members only)\n"
202                         "    -h / -?\n"
203                         "        Show help text\n"
204                         "    -G\n"
205                         "        Use simple textual interface (instead of ncurses)\n"
206                         "    -n\n"
207                         "        Dry run - Do not actually do dispenses\n"
208                         "    -m <min balance>\n"
209                         "    -M <max balance>\n"
210                         "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
211                         "Definitions:\n"
212                         "    <itemid>\n"
213                         "        Item ID of the form <type>:<num> where <type> is a non-empty string of alpha-numeric characters, and <num> is a non-negative integer\n"
214 //                      "    <user>\n"
215 //                      "        Account name\n"
216                         );
217 }
218
219 int main(int argc, char *argv[])
220 {
221          int    sock;
222          int    i, ret = 0;
223         char    buffer[BUFSIZ];
224         
225         gsTextArgs[0] = "";
226
227         // -- Create regular expressions
228         // > Code Type Count ...
229         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
230         // > Code Type Ident Status Price Desc
231         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);
232         // > Code 'SALT' salt
233         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
234         // > Code 'User' Username Balance Flags
235         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
236         // > Item Ident
237         CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED);
238
239         // Parse Arguments
240         for( i = 1; i < argc; i ++ )
241         {
242                 char    *arg = argv[i];
243                 
244                 if( arg[0] == '-' )
245                 {                       
246                         switch(arg[1])
247                         {
248                         case 'h':
249                         case '?':
250                                 ShowUsage();
251                                 return 0;
252                                         
253                         case 'c':
254                                 if( i > 2 && strcmp(argv[i-1], "type") == 0 )
255                                         goto _default;
256                                 if( i + 1 >= argc ) {
257                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
258                                         ShowUsage();
259                                         return -1;
260                                 }
261                                 giDispenseCount = atoi(argv[++i]);
262                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
263                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
264                                         return -1;
265                                 }
266                                 
267                                 break ;
268         
269                         case 'm':       // Minimum balance
270                                 if( i + 1 >= argc ) {
271                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
272                                         ShowUsage();
273                                         return RV_ARGUMENTS;
274                                 }
275                                 giMinimumBalance = atoi(argv[++i]);
276                                 break;
277                         case 'M':       // Maximum balance
278                                 if( i + 1 >= argc ) {
279                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
280                                         ShowUsage();
281                                         return RV_ARGUMENTS;
282                                 }
283                                 giMaximumBalance = atoi(argv[++i]);
284                                 break;
285                         
286                         case 'u':       // Override User
287                                 if( i + 1 >= argc ) {
288                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
289                                         ShowUsage();
290                                         return RV_ARGUMENTS;
291                                 }
292                                 gsEffectiveUser = argv[++i];
293                                 break;
294                         
295                         case 'H':       // Override remote host
296                                 if( i + 1 >= argc ) {
297                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
298                                         ShowUsage();
299                                         return RV_ARGUMENTS;
300                                 }
301                                 gsDispenseServer = argv[++i];
302                                 break;
303                         case 'P':       // Override remote port
304                                 if( i + 1 >= argc ) {
305                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
306                                         ShowUsage();
307                                         return RV_ARGUMENTS;
308                                 }
309                                 giDispensePort = atoi(argv[++i]);
310                                 break;
311                         
312                         // Set slot name/price
313                         case 's':
314                                 if( giTextArgc != 0 ) {
315                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
316                                         ShowUsage();
317                                         return RV_ARGUMENTS;
318                                 }
319                                 gsTextArgs[0] = "slot"; // HACK!!
320                                 giTextArgc ++;
321                                 break;
322                         
323                         case 'G':       // Don't use GUI
324                                 giUIMode = UI_MODE_BASIC;
325                                 break;
326                         case 'D':       // Drinks only
327                                 giUIMode = UI_MODE_DRINKSONLY;
328                                 break;
329                         case 'n':       // Dry Run / read-only
330                                 gbDryRun = 1;
331                                 break;
332                         case '-':
333                                 if( strcmp(argv[i], "--help") == 0 ) {
334                                         ShowUsage();
335                                         return 0;
336                                 }
337                                 else if( strcmp(argv[i], "--dry-run") == 0 ) {
338                                         gbDryRun = 1;
339                                 }
340                                 else if( strcmp(argv[i], "--drinks-only") == 0 ) {
341                                         giUIMode = UI_MODE_DRINKSONLY;
342                                 }
343                                 else if( strcmp(argv[i], "--can-select-all") == 0 ) {
344                                         gbDisallowSelectWithoutBalance = 0;
345                                 }
346                                 else {
347                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
348                                         ShowUsage();
349                                         return RV_ARGUMENTS;
350                                 }
351                                 break;
352                         default: _default:
353                                 // The first argument is not allowed to begin with 'i'
354                                 // (catches most bad flags)
355                                 if( giTextArgc == 0 ) {
356                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
357                                         ShowUsage();
358                                         return RV_ARGUMENTS;
359                                 }
360                                 if( giTextArgc == MAX_TXT_ARGS )
361                                 {
362                                         fprintf(stderr, "ERROR: Too many arguments\n");
363                                         return RV_ARGUMENTS;
364                                 }
365                                 gsTextArgs[giTextArgc++] = argv[i];
366                                 break;
367                         }
368
369                         continue;
370                 }
371
372                 if( giTextArgc == MAX_TXT_ARGS )
373                 {
374                         fprintf(stderr, "ERROR: Too many arguments\n");
375                         return RV_ARGUMENTS;
376                 }
377         
378                 gsTextArgs[giTextArgc++] = argv[i];
379         
380         }
381
382         //
383         // `dispense finger`
384         // -
385         if( strcmp(gsTextArgs[0], "finger") == 0 )
386         {
387                 // Connect to server
388                 sock = OpenConnection(gsDispenseServer, giDispensePort);
389                 if( sock < 0 )  return RV_SOCKET_ERROR;
390
391                 // Get items
392                 PopulateItemList(sock);
393
394                 printf("The UCC Coke machine.\n\n");
395
396                 // Only get coke slot statuses
397                 for( i = 0; i <= 6; i ++ )
398                 {
399                         const char *status;
400                         switch(gaItems[i].Status)
401                         {
402                         case 0: status = "Avail";       break;
403                         case 1: status = "Sold ";       break;
404                         default:
405                                 status = "Error";
406                                 break;
407                         }
408                         printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc);
409                         
410                 }
411
412                 printf("\nMay your pink fish bing into the distance.\n");
413
414                 return 0;
415         }
416
417         //
418         // `dispense acct`
419         // - 
420         if( strcmp(gsTextArgs[0], "acct") == 0 )
421         {
422                 // Connect to server
423                 sock = OpenConnection(gsDispenseServer, giDispensePort);
424                 if( sock < 0 )  return RV_SOCKET_ERROR;
425                 // List accounts?
426                 if( giTextArgc == 1 ) {
427                         ret = Dispense_EnumUsers(sock);
428                         close(sock);
429                         return ret;
430                 }
431                         
432                 // gsTextArgs[1]: Username
433                 
434                 // Alter account?
435                 if( giTextArgc != 2 )
436                 {
437                         if( giTextArgc != 4 ) {
438                                 fprintf(stderr, "`dispense acct` requires a reason\n");
439                                 ShowUsage();
440                                 return RV_ARGUMENTS;
441                         }
442                         
443                         // Authentication required
444                         ret = Authenticate(sock);
445                         if(ret) return ret;
446                         
447                         // gsTextArgs[1]: Username
448                         // gsTextArgs[2]: Ammount
449                         // gsTextArgs[3]: Reason
450                         
451                         if( gsTextArgs[2][0] == '=' ) {
452                                 // Set balance
453                                 if( gsTextArgs[2][1] != '0' && atoi(gsTextArgs[2]+1) == 0 ) {
454                                         fprintf(stderr, "Error: Invalid balance to be set\n");
455                                         exit(1);
456                                 }
457                                 
458                                 ret = Dispense_SetBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]+1), gsTextArgs[3]);
459                         }
460                         else {
461                                 // Alter balance
462                                 ret = Dispense_AlterBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
463                         }
464                 }
465                 // On error, quit
466                 if( ret ) {
467                         close(sock);
468                         return ret;
469                 }
470                 
471                 // Show user information
472                 ret = Dispense_ShowUser(sock, gsTextArgs[1]);
473                 
474                 close(sock);
475                 return ret;
476         }
477         //
478         // `dispense give`
479         // - "Here, have some money."
480         else if( strcmp(gsTextArgs[0], "give") == 0 )
481         {
482                 if( giTextArgc != 4 ) {
483                         fprintf(stderr, "`dispense give` takes three arguments\n");
484                         ShowUsage();
485                         return RV_ARGUMENTS;
486                 }
487                 
488                 // gsTextArgs[1]: Destination
489                 // gsTextArgs[2]: Ammount
490                 // gsTextArgs[3]: Reason
491                 
492                 // Connect to server
493                 sock = OpenConnection(gsDispenseServer, giDispensePort);
494                 if( sock < 0 )  return RV_SOCKET_ERROR;
495                 
496                 // Authenticate
497                 ret = Authenticate(sock);
498                 if(ret) return ret;
499                 
500                 ret = Dispense_Give(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
501
502                 close(sock);
503         
504                 return ret;
505         }
506         // 
507         // `dispense user`
508         // - User administration (Admin Only)
509         if( strcmp(gsTextArgs[0], "user") == 0 )
510         {
511                 // Check argument count
512                 if( giTextArgc == 1 ) {
513                         fprintf(stderr, "Error: `dispense user` requires arguments\n");
514                         ShowUsage();
515                         return RV_ARGUMENTS;
516                 }
517                 
518                 // Connect to server
519                 sock = OpenConnection(gsDispenseServer, giDispensePort);
520                 if( sock < 0 )  return RV_SOCKET_ERROR;
521                 
522                 // Attempt authentication
523                 ret = Authenticate(sock);
524                 if(ret) return ret;
525                 
526                 // Add new user?
527                 if( strcmp(gsTextArgs[1], "add") == 0 )
528                 {
529                         if( giTextArgc != 3 ) {
530                                 fprintf(stderr, "Error: `dispense user add` requires an argument\n");
531                                 ShowUsage();
532                                 return RV_ARGUMENTS;
533                         }
534                         
535                         ret = Dispense_AddUser(sock, gsTextArgs[2]);
536                 }
537                 // Update a user
538                 else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 )
539                 {
540                         if( giTextArgc < 4 || giTextArgc > 5 ) {
541                                 fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
542                                 ShowUsage();
543                                 return RV_ARGUMENTS;
544                         }
545                         
546                         if( giTextArgc == 4 )
547                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], "");
548                         else
549                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]);
550                 }
551                 else
552                 {
553                         fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
554                         ShowUsage();
555                         return RV_ARGUMENTS;
556                 }
557                 close(sock);
558                 return ret;
559         }
560         // Donation!
561         else if( strcmp(gsTextArgs[0], "donate") == 0 )
562         {
563                 // Check argument count
564                 if( giTextArgc != 3 ) {
565                         fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
566                         ShowUsage();
567                         return RV_ARGUMENTS;
568                 }
569                 
570                 // Connect to server
571                 sock = OpenConnection(gsDispenseServer, giDispensePort);
572                 if( sock < 0 )  return RV_SOCKET_ERROR;
573                 
574                 // Attempt authentication
575                 ret = Authenticate(sock);
576                 if(ret) return ret;
577                 
578                 // Do donation
579                 ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]);
580                                 
581                 close(sock);
582
583                 return ret;
584         }
585         // Refund an item
586         else if( strcmp(gsTextArgs[0], "refund") == 0 )
587         {
588                  int     price = 0;
589                 // Check argument count
590                 if( giTextArgc != 3 && giTextArgc != 4 ) {
591                         fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
592                         ShowUsage();
593                         return RV_ARGUMENTS;
594                 }
595         
596                 // Connect to server
597                 sock = OpenConnection(gsDispenseServer, giDispensePort);
598                 if(sock < 0)    return RV_SOCKET_ERROR; 
599
600                 // Attempt authentication
601                 ret = Authenticate(sock);
602                 if(ret) return ret;
603
604                 if( giTextArgc == 4 ) {
605                         price = atoi(gsTextArgs[3]);
606                         if( price <= 0 ) {
607                                 fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
608                                 return RV_ARGUMENTS;
609                         }
610                 }
611
612                 // Username, Item, cost
613                 ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price);
614
615                 // TODO: More
616                 close(sock);
617                 return ret;
618         }
619         // Query an item price
620         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 )
621         {
622                 regmatch_t matches[3];
623                 char    *type;
624                  int    id;
625                 // Check argument count
626                 if( giTextArgc != 2 ) {
627                         fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n");
628                         ShowUsage();
629                         return RV_ARGUMENTS;
630                 }
631                 // Parse item ID
632                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
633                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
634                         return RV_ARGUMENTS;
635                 }
636                 type = gsTextArgs[1] + matches[1].rm_so;
637                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
638                 id = atoi( gsTextArgs[1] + matches[2].rm_so );
639
640                 sock = OpenConnection(gsDispenseServer, giDispensePort);
641                 if( sock < 0 )  return RV_SOCKET_ERROR;
642                 
643                 ret = Dispense_ItemInfo(sock, type, id);
644                 close(sock);
645                 return ret;
646         }
647         // Set slot
648         else if( strcmp(gsTextArgs[0], "slot") == 0 )
649         {
650                 regmatch_t matches[3];
651                 char    *item_type, *newname;
652                  int    item_id, price;
653                 
654                 // Check arguments
655                 if( giTextArgc != 4 ) {
656                         fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
657                         ShowUsage();
658                         return RV_ARGUMENTS;
659                 }
660                 
661                 // Parse arguments
662                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
663                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
664                         return RV_ARGUMENTS;
665                 }
666                 item_type = gsTextArgs[1] + matches[1].rm_so;
667                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
668                 item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
669
670                 // - Price
671                 price = atoi( gsTextArgs[2] );
672                 if( price <= 0 && gsTextArgs[2][0] != '0' ) {
673                         fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
674                         return RV_ARGUMENTS;
675                 }
676                 
677                 // - New name
678                 newname = gsTextArgs[3];
679                 // -- Sanity
680                 {
681                         char *pos;
682                         for( pos = newname; *pos; pos ++ )
683                         {
684                                 if( !isalnum(*pos) && *pos != ' ' ) {
685                                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
686                                         return RV_ARGUMENTS;
687                                 }
688                         }
689                 }
690                 
691                 // Connect & Authenticate
692                 sock = OpenConnection(gsDispenseServer, giDispensePort);
693                 if( sock < 0 )  return RV_SOCKET_ERROR;
694                 ret = Authenticate(sock);
695                 if(ret) return ret;
696                 // Update the slot
697                 ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
698                 
699                 close(sock);
700                 return ret;
701         }
702         // Item name / pattern
703         else
704         {
705                 gsItemPattern = gsTextArgs[0];
706         }
707         
708         // Connect to server
709         sock = OpenConnection(gsDispenseServer, giDispensePort);
710         if( sock < 0 )  return RV_SOCKET_ERROR;
711
712         // Get the user's balance
713         ret = GetUserBalance(sock);
714         if(ret) return ret;
715
716         // Get items
717         PopulateItemList(sock);
718         
719         // Disconnect from server
720         close(sock);
721         
722         if( gsItemPattern && gsItemPattern[0] )
723         {
724                 regmatch_t matches[3];
725                 // Door (hard coded)
726                 if( strcmp(gsItemPattern, "door") == 0 )
727                 {
728                         // Connect, Authenticate, dispense and close
729                         sock = OpenConnection(gsDispenseServer, giDispensePort);
730                         if( sock < 0 )  return RV_SOCKET_ERROR;
731                         ret = Authenticate(sock);
732                         if(ret) return ret;
733                         ret = DispenseItem(sock, "door", 0);
734                         close(sock);
735                         return ret;
736                 }
737                 // Item id (<type>:<num>)
738                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
739                 {
740                         char    *ident;
741                          int    id;
742                         
743                         // Get and finish ident
744                         ident = gsItemPattern + matches[1].rm_so;
745                         gsItemPattern[matches[1].rm_eo] = '\0';
746                         // Get ID
747                         id = atoi( gsItemPattern + matches[2].rm_so );
748                         
749                         // Connect, Authenticate, dispense and close
750                         sock = OpenConnection(gsDispenseServer, giDispensePort);
751                         if( sock < 0 )  return RV_SOCKET_ERROR;
752                         
753                         Dispense_ItemInfo(sock, ident, id);
754                         
755                         ret = Authenticate(sock);
756                         if(ret) return ret;
757                         ret = DispenseItem(sock, ident, id);
758                         close(sock);
759                         return ret;
760                 }
761                 // Item number (6 = coke)
762                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
763                 {
764                         i = atoi(gsItemPattern);
765                 }
766                 // Item prefix
767                 else
768                 {
769                          int    j;
770                          int    best = -1;
771                         for( i = 0; i < giNumItems; i ++ )
772                         {
773                                 // Prefix match (with case-insensitive match)
774                                 for( j = 0; gsItemPattern[j]; j ++ )
775                                 {
776                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
777                                                 continue;
778                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
779                                                 continue;
780                                         break;
781                                 }
782                                 // Check if the prefix matched
783                                 if( gsItemPattern[j] != '\0' )
784                                         continue;
785                                 
786                                 // Prefect match
787                                 if( gaItems[i].Desc[j] == '\0' ) {
788                                         best = i;
789                                         break;
790                                 }
791                                 
792                                 // Only one match allowed
793                                 if( best == -1 ) {
794                                         best = i;
795                                 }
796                                 else {
797                                         // TODO: Allow ambiguous matches?
798                                         // or just print a wanrning
799                                         printf("Warning - Ambiguous pattern, stopping\n");
800                                         return RV_BAD_ITEM;
801                                 }
802                         }
803                         
804                         // Was a match found?
805                         if( best == -1 )
806                         {
807                                 fprintf(stderr, "No item matches the passed string\n");
808                                 return RV_BAD_ITEM;
809                         }
810                         
811                         i = best;
812                 }
813         }
814         else if( giUIMode != UI_MODE_BASIC )
815         {
816                 i = ShowNCursesUI();
817         }
818         else
819         {
820                 // Very basic dispense interface
821                 for( i = 0; i < giNumItems; i ++ ) {
822                         // Add a separator
823                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
824                                 printf("   ---\n");
825                         
826                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
827                                 gaItems[i].Price, gaItems[i].Desc);
828                 }
829                 printf(" q Quit\n");
830                 for(;;)
831                 {
832                         char    *buf;
833                         
834                         i = -1;
835                         
836                         fgets(buffer, BUFSIZ, stdin);
837                         
838                         buf = trim(buffer);
839                         
840                         if( buf[0] == 'q' )     break;
841                         
842                         i = atoi(buf);
843                         
844                         if( i != 0 || buf[0] == '0' )
845                         {
846                                 if( i < 0 || i >= giNumItems ) {
847                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
848                                         continue;
849                                 }
850                                 break;
851                         }
852                 }
853         }
854         
855         
856         // Check for a valid item ID
857         if( i >= 0 )
858         {
859                  int j;
860                 // Connect, Authenticate, dispense and close
861                 sock = OpenConnection(gsDispenseServer, giDispensePort);
862                 if( sock < 0 )  return RV_SOCKET_ERROR;
863                         
864                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
865                 if(ret) return ret;
866                 
867                 ret = Authenticate(sock);
868                 if(ret) return ret;
869                 
870                 for( j = 0; j < giDispenseCount; j ++ ) {
871                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
872                         if( ret )       break;
873                 }
874                 if( j > 1 ) {
875                         printf("%i items dispensed\n", j);
876                 }
877                 Dispense_ShowUser(sock, gsUserName);
878                 close(sock);
879
880         }
881
882         return ret;
883 }
884
885 // -------------------
886 // --- NCurses GUI ---
887 // -------------------
888 /**
889  * \brief Render the NCurses UI
890  */
891 int ShowNCursesUI(void)
892 {
893          int    ch;
894          int    i, times;
895          int    xBase, yBase;
896         const int       displayMinWidth = 50;
897         char    *titleString = "Dispense";
898          int    items_in_view;
899          int    maxItemIndex;
900          int    itemBase = 0;
901          int    currentItem;
902          int    ret = -2;       // -2: Used for marking "no return yet"
903         
904         char    balance_str[5+1+2+1];   // If $9999.99 is too little, something's wrong
905         char    *username;
906         struct passwd *pwd;
907          
908          int    height, width;
909         
910         void _ItemDown(void)
911         {
912                 currentItem ++;
913                 // Skip over spacers
914                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
915                         currentItem ++;
916                 
917                 if( currentItem >= maxItemIndex ) {
918                         currentItem = 0;
919                         // Skip over spacers
920                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
921                                 currentItem ++;
922                 }
923         }
924         
925         void _ItemUp(void)
926         {
927                 currentItem --;
928                 // Skip over spacers
929                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
930                         currentItem --;
931                 
932                 if( currentItem < 0 ) {
933                         currentItem = maxItemIndex - 1;
934                         // Skip over spacers
935                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
936                                 currentItem --;
937                 }
938         }
939
940         // Get Username
941         if( gsEffectiveUser )
942                 username = gsEffectiveUser;
943         else {
944                 pwd = getpwuid( getuid() );
945                 username = pwd->pw_name;
946         }
947         // Get balance
948         snprintf(balance_str, sizeof(balance_str), "$%i.%02i", giUserBalance/100, abs(giUserBalance)%100);
949         
950         // Enter curses mode
951         initscr();
952         cbreak(); noecho();
953         
954         // Get max index
955         maxItemIndex = ShowItemAt(0, 0, 0, -1, 0);
956         // Get item count per screen
957         // - 6: randomly chosen (Need at least 3)
958         items_in_view = LINES - 6;
959         if( items_in_view > maxItemIndex )
960                 items_in_view = maxItemIndex;
961         // Get first index
962         currentItem = 0;
963         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
964                 currentItem ++;
965         
966         
967         // Get dimensions
968         height = items_in_view + 3;
969         width = displayMinWidth;
970         
971         // Get positions
972         xBase = COLS/2 - width/2;
973         yBase = LINES/2 - height/2;
974         
975         for( ;; )
976         {
977                 // Header
978                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
979                 
980                 // Items
981                 for( i = 0; i < items_in_view; i ++ )
982                 {
983                          int    pos = 0;
984                         
985                         move( yBase + 1 + i, xBase );
986                         printw("| ");
987                         
988                         pos += 2;
989                         
990                         // Check for the '...' row
991                         // - Oh god, magic numbers!
992                         if( (i == 0 && itemBase > 0)
993                          || (i == items_in_view - 1 && itemBase < maxItemIndex - items_in_view) )
994                         {
995                                 printw("     ...");     pos += 8;
996                                 times = (width - pos) - 1;
997                                 while(times--)  addch(' ');
998                         }
999                         // Show an item
1000                         else {
1001                                 ShowItemAt(
1002                                         yBase + 1 + i, xBase + pos,     // Position
1003                                         (width - pos) - 3,      // Width
1004                                         itemBase + i,   // Index
1005                                         !!(currentItem == itemBase + i) // Hilighted
1006                                         );
1007                                 printw("  ");
1008                         }
1009                         
1010                         // Scrollbar (if needed)
1011                         if( maxItemIndex > items_in_view ) {
1012                                 if( i == 0 ) {
1013                                         addch('A');
1014                                 }
1015                                 else if( i == items_in_view - 1 ) {
1016                                         addch('V');
1017                                 }
1018                                 else {
1019                                          int    percentage = itemBase * 100 / (maxItemIndex-items_in_view);
1020                                         if( i-1 == percentage*(items_in_view-3)/100 ) {
1021                                                 addch('#');
1022                                         }
1023                                         else {
1024                                                 addch('|');
1025                                         }
1026                                 }
1027                         }
1028                         else {
1029                                 addch('|');
1030                         }
1031                 }
1032                 
1033                 // Footer
1034                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
1035                 
1036                 // User line
1037                 // - Username, balance, flags
1038                 PrintAlign(yBase+height-1, xBase+1, width-2,
1039                         username, ' ', balance_str, ' ', gsUserFlags);
1040                 
1041                 
1042                 // Get input
1043                 ch = getch();
1044                 
1045                 if( ch == '\x1B' ) {
1046                         ch = getch();
1047                         if( ch == '[' ) {
1048                                 ch = getch();
1049                                 
1050                                 switch(ch)
1051                                 {
1052                                 case 'B':       _ItemDown();    break;
1053                                 case 'A':       _ItemUp();      break;
1054                                 }
1055                         }
1056                         else {
1057                                 
1058                         }
1059                 }
1060                 else {
1061                         switch(ch)
1062                         {
1063                         case '\n':
1064                                 ret = ShowItemAt(0, 0, 0, currentItem, 0);
1065                                 break;
1066                         case 'h':       break;
1067                         case 'j':       _ItemDown();    break;
1068                         case 'k':       _ItemUp();      break;
1069                         case 'l':       break;
1070                         case 0x1b:      // Escape
1071                         case 'q':
1072                                 ret = -1;       // -1: Return with no dispense
1073                                 break;
1074                         }
1075                         
1076                         // Check if the return value was changed
1077                         if( ret != -2 ) break;
1078                 }
1079                 
1080                 // Scroll only if needed
1081                 if( items_in_view < maxItemIndex )
1082                 {
1083                         // - If the current item is above the second item shown, and we're not at the top
1084                         if( currentItem < itemBase + 2 && itemBase > 0 ) {
1085                                 itemBase = currentItem - 2;
1086                                 if(itemBase < 0)        itemBase = 0;
1087                         }
1088                         // - If the current item is below the second item show, and we're not at the bottom
1089                         if( currentItem > itemBase + items_in_view - 2 && itemBase + items_in_view < maxItemIndex ) {
1090                                 itemBase = currentItem - items_in_view + 2;
1091                                 if( itemBase > maxItemIndex - items_in_view )
1092                                         itemBase = maxItemIndex - items_in_view;
1093                         }
1094                 }
1095         }
1096         
1097         
1098         // Leave
1099         endwin();
1100         return ret;
1101 }
1102
1103 /**
1104  * \brief Show item \a Index at (\a Col, \a Row)
1105  * \return Dispense index of item
1106  * \note Part of the NCurses UI
1107  */
1108 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted)
1109 {
1110         char    *name = NULL;
1111          int    price = 0;
1112          int    status = -1;
1113         
1114         switch(giUIMode)
1115         {
1116         // Standard UI
1117         // - This assumes that 
1118         case UI_MODE_STANDARD:
1119                 // Bounds check
1120                 // Index = -1, request limit
1121                 if( Index < 0 || Index >= giNumItems+2 )
1122                         return giNumItems+2;
1123                 // Drink label
1124                 if( Index == 0 )
1125                 {
1126                         price = 0;
1127                         name = "Coke Machine";
1128                         Index = -1;     // -1 indicates a label
1129                         break;
1130                 }
1131                 Index --;
1132                 // Drinks 0 - 6
1133                 if( Index <= 6 )
1134                 {
1135                         name = gaItems[Index].Desc;
1136                         price = gaItems[Index].Price;
1137                         status = gaItems[Index].Status;
1138                         break;
1139                 }
1140                 Index -= 7;
1141                 // EPS label
1142                 if( Index == 0 )
1143                 {
1144                         price = 0;
1145                         name = "Electronic Payment System";
1146                         Index = -1;     // -1 indicates a label
1147                         break;
1148                 }
1149                 Index --;
1150                 Index += 7;
1151                 name = gaItems[Index].Desc;
1152                 price = gaItems[Index].Price;
1153                 status = gaItems[Index].Status;
1154                 break;
1155         default:
1156                 return -1;
1157         }
1158         
1159         // Width = 0, don't print
1160         if( Width > 0 )
1161         {
1162                 // 4 preceding, 5 price
1163                 int nameWidth = Width - 4 - snprintf(NULL, 0, " %4i", price);
1164                 move( Row, Col );
1165                 
1166                 if( Index >= 0 )
1167                 {
1168                         // Show hilight and status
1169                         switch( status )
1170                         {
1171                         case 0:
1172                                 if( bHilighted )
1173                                         printw("->  ");
1174                                 else
1175                                         printw("    ");
1176                                 break;
1177                         case 1:
1178                                 printw("SLD ");
1179                                 break;
1180                         
1181                         default:
1182                         case -1:
1183                                 printw("ERR ");
1184                                 break;
1185                         }
1186                         
1187                         printw("%-*.*s", nameWidth, nameWidth, name);
1188                 
1189                         printw(" %4i", price);
1190                 }
1191                 else
1192                 {
1193                         printw("-- %-*.*s ", Width-4, Width-4, name);
1194                 }
1195         }
1196         
1197         // If the item isn't availiable for sale, return -1 (so it's skipped)
1198         if( status || (price > giUserBalance && gbDisallowSelectWithoutBalance) )
1199                 Index = -1;
1200         
1201         return Index;
1202 }
1203
1204 /**
1205  * \brief Print a three-part string at the specified position (formatted)
1206  * \note NCurses UI Helper
1207  * 
1208  * Prints \a Left on the left of the area, \a Right on the righthand side
1209  * and \a Mid in the middle of the area. These are padded with \a Pad1
1210  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
1211  * 
1212  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
1213  * and the arguments to these are read in that order.
1214  */
1215 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
1216         const char *Mid, char Pad2, const char *Right, ...)
1217 {
1218          int    lLen, mLen, rLen;
1219          int    times;
1220         
1221         va_list args;
1222         
1223         // Get the length of the strings
1224         va_start(args, Right);
1225         lLen = vsnprintf(NULL, 0, Left, args);
1226         mLen = vsnprintf(NULL, 0, Mid, args);
1227         rLen = vsnprintf(NULL, 0, Right, args);
1228         va_end(args);
1229         
1230         // Sanity check
1231         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
1232                 return ;        // TODO: What to do?
1233         }
1234         
1235         move(Row, Col);
1236         
1237         // Render strings
1238         va_start(args, Right);
1239         // - Left
1240         {
1241                 char    tmp[lLen+1];
1242                 vsnprintf(tmp, lLen+1, Left, args);
1243                 addstr(tmp);
1244         }
1245         // - Left padding
1246         times = (Width - mLen)/2 - lLen;
1247         while(times--)  addch(Pad1);
1248         // - Middle
1249         {
1250                 char    tmp[mLen+1];
1251                 vsnprintf(tmp, mLen+1, Mid, args);
1252                 addstr(tmp);
1253         }
1254         // - Right Padding
1255         times = (Width - mLen)/2 - rLen;
1256         if( (Width - mLen) % 2 )        times ++;
1257         while(times--)  addch(Pad2);
1258         // - Right
1259         {
1260                 char    tmp[rLen+1];
1261                 vsnprintf(tmp, rLen+1, Right, args);
1262                 addstr(tmp);
1263         }
1264 }
1265
1266 // ---------------------
1267 // --- Coke Protocol ---
1268 // ---------------------
1269 int OpenConnection(const char *Host, int Port)
1270 {
1271         struct hostent  *host;
1272         struct sockaddr_in      serverAddr;
1273          int    sock;
1274         
1275         host = gethostbyname(Host);
1276         if( !host ) {
1277                 fprintf(stderr, "Unable to look up '%s'\n", Host);
1278                 return -1;
1279         }
1280         
1281         memset(&serverAddr, 0, sizeof(serverAddr));
1282         
1283         serverAddr.sin_family = AF_INET;        // IPv4
1284         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
1285         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
1286         serverAddr.sin_port = htons(Port);
1287         
1288         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
1289         if( sock < 0 ) {
1290                 fprintf(stderr, "Failed to create socket\n");
1291                 return -1;
1292         }
1293
1294 //      printf("geteuid() = %i, getuid() = %i\n", geteuid(), getuid());
1295         
1296         if( geteuid() == 0 || getuid() == 0 )
1297         {
1298                  int    i;
1299                 struct sockaddr_in      localAddr;
1300                 memset(&localAddr, 0, sizeof(localAddr));
1301                 localAddr.sin_family = AF_INET; // IPv4
1302                 
1303                 // Loop through all the top ports until one is avaliable
1304                 for( i = 512; i < 1024; i ++)
1305                 {
1306                         localAddr.sin_port = htons(i);  // IPv4
1307                         // Attempt to bind to low port for autoauth
1308                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
1309                                 break;
1310                 }
1311                 if( i == 1024 )
1312                         printf("Warning: AUTOAUTH unavaliable\n");
1313 //              else
1314 //                      printf("Bound to 0.0.0.0:%i\n", i);
1315         }
1316         
1317         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
1318                 fprintf(stderr, "Failed to connect to server\n");
1319                 return -1;
1320         }
1321
1322         // We're not authenticated if the connection has just opened
1323         gbIsAuthenticated = 0;
1324         
1325         return sock;
1326 }
1327
1328 int Authenticate_AutoAuth(int Socket, const char *Username)
1329 {
1330         char    *buf;
1331          int    responseCode;
1332          int    ret = -1;
1333         
1334         // Attempt automatic authentication
1335         sendf(Socket, "AUTOAUTH %s\n", Username);
1336         
1337         // Check if it worked
1338         buf = ReadLine(Socket);
1339         
1340         responseCode = atoi(buf);
1341         switch( responseCode )
1342         {
1343         case 200:       // Autoauth succeeded, return
1344                 ret = 0;
1345                 break;
1346         
1347         case 401:       // Untrusted
1348 //              fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
1349                 ret = RV_PERMISSIONS;
1350                 break;
1351         case 404:       // Bad Username
1352                 fprintf(stderr, "Bad Username '%s'\n", Username);
1353                 ret = RV_INVALID_USER;
1354                 break;
1355         
1356         default:
1357                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1358                 printf("%s\n", buf);
1359                 ret = RV_UNKNOWN_ERROR;
1360                 break;;
1361         }
1362         
1363         free(buf);
1364         return ret;
1365 }
1366
1367 int Authenticate_AuthIdent(int Socket)
1368 {
1369         char    *buf;
1370          int    responseCode;
1371          int    ret = -1;
1372         
1373         // Attempt automatic authentication
1374         sendf(Socket, "AUTHIDENT\n");
1375         
1376         // Check if it worked
1377         buf = ReadLine(Socket);
1378         
1379         responseCode = atoi(buf);
1380         switch( responseCode )
1381         {
1382         case 200:       // Autoauth succeeded, return
1383                 ret = 0;
1384                 break;
1385         
1386         case 401:       // Untrusted
1387 //              fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
1388                 ret = RV_PERMISSIONS;
1389                 break;
1390         
1391         default:
1392                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1393                 printf("%s\n", buf);
1394                 ret = RV_UNKNOWN_RESPONSE;
1395                 break;
1396         }
1397         
1398         free(buf);
1399
1400         return ret;
1401 }
1402
1403 int Authenticate_Password(int Socket, const char *Username)
1404 {
1405         #if USE_PASSWORD_AUTH
1406         char    *buf;
1407          int    responseCode;   
1408         char    salt[32];
1409          int    i;
1410         regmatch_t      matches[4];
1411
1412         sendf(Socket, "USER %s\n", Username);
1413         printf("Using username %s\n", Username);
1414         
1415         buf = ReadLine(Socket);
1416         
1417         // TODO: Get Salt
1418         // Expected format: 100 SALT <something> ...
1419         // OR             : 100 User Set
1420         RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
1421         responseCode = atoi(buf);
1422         if( responseCode != 100 ) {
1423                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1424                 free(buf);
1425                 return RV_UNKNOWN_ERROR;        // ERROR
1426         }
1427         
1428         // Check for salt
1429         if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
1430                 // Store it for later
1431                 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
1432                 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
1433         }
1434         free(buf);
1435         
1436         // Give three attempts
1437         for( i = 0; i < 3; i ++ )
1438         {
1439                  int    ofs = strlen(Username)+strlen(salt);
1440                 char    tmpBuf[42];
1441                 char    tmp[ofs+20];
1442                 char    *pass = getpass("Password: ");
1443                 uint8_t h[20];
1444                 
1445                 // Create hash string
1446                 // <username><salt><hash>
1447                 strcpy(tmp, Username);
1448                 strcat(tmp, salt);
1449                 SHA1( (unsigned char*)pass, strlen(pass), h );
1450                 memcpy(tmp+ofs, h, 20);
1451                 
1452                 // Hash all that
1453                 SHA1( (unsigned char*)tmp, ofs+20, h );
1454                 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1455                         h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
1456                         h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
1457                         );
1458         
1459                 // Send password
1460                 sendf(Socket, "PASS %s\n", tmpBuf);
1461                 buf = ReadLine(Socket);
1462         
1463                 responseCode = atoi(buf);
1464                 // Auth OK?
1465                 if( responseCode == 200 )       break;
1466                 // Bad username/password
1467                 if( responseCode == 401 )       continue;
1468                 
1469                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1470                 free(buf);
1471                 return -1;
1472         }
1473         free(buf);
1474         if( i == 3 )
1475                 return RV_INVALID_USER; // 2 = Bad Password
1476
1477         return 0;
1478         #else
1479         return RV_INVALID_USER;
1480         #endif
1481 }
1482
1483 /**
1484  * \brief Authenticate with the server
1485  * \return Boolean Failure
1486  */
1487 int Authenticate(int Socket)
1488 {
1489         struct passwd   *pwd;
1490         
1491         if( gbIsAuthenticated ) return 0;
1492         
1493         // Get user name
1494         pwd = getpwuid( getuid() );
1495
1496         // Attempt AUTOAUTH
1497         if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
1498                 ;
1499         else if( Authenticate_AuthIdent(Socket) == 0 )
1500                 ;
1501         else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
1502                 return RV_INVALID_USER;
1503
1504         // Set effective user
1505         if( gsEffectiveUser ) {
1506                 char    *buf;
1507                  int    responseCode;
1508                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
1509                 
1510                 buf = ReadLine(Socket);
1511                 responseCode = atoi(buf);
1512                 
1513                 switch(responseCode)
1514                 {
1515                 case 200:
1516                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
1517                         break;
1518                 
1519                 case 403:
1520                         printf("Only coke members can use `dispense -u`\n");
1521                         free(buf);
1522                         return RV_PERMISSIONS;
1523                 
1524                 case 404:
1525                         printf("Invalid user selected\n");
1526                         free(buf);
1527                         return RV_INVALID_USER;
1528                 
1529                 default:
1530                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1531                         printf("%s\n", buf);
1532                         free(buf);
1533                         return RV_UNKNOWN_ERROR;
1534                 }
1535                 
1536                 free(buf);
1537         }
1538         
1539         gbIsAuthenticated = 1;
1540         
1541         return 0;
1542 }
1543
1544 int GetUserBalance(int Socket)
1545 {
1546         regmatch_t      matches[6];
1547         struct passwd   *pwd;
1548         char    *buf;
1549          int    responseCode;
1550         
1551         if( !gsUserName )
1552         {
1553                 if( gsEffectiveUser ) {
1554                         gsUserName = gsEffectiveUser;
1555                 }
1556                 else {
1557                         pwd = getpwuid( getuid() );
1558                         gsUserName = strdup(pwd->pw_name);
1559                 }
1560         }
1561         
1562         sendf(Socket, "USER_INFO %s\n", gsUserName);
1563         buf = ReadLine(Socket);
1564         responseCode = atoi(buf);
1565         switch(responseCode)
1566         {
1567         case 202:       break;  // Ok
1568         
1569         case 404:
1570                 printf("Invalid user? (USER_INFO failed)\n");
1571                 free(buf);
1572                 return RV_INVALID_USER;
1573         
1574         default:
1575                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1576                 printf("%s\n", buf);
1577                 free(buf);
1578                 return RV_UNKNOWN_ERROR;
1579         }
1580
1581         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
1582         
1583         giUserBalance = atoi( buf + matches[4].rm_so );
1584         gsUserFlags = strdup( buf + matches[5].rm_so );
1585         
1586         free(buf);
1587         
1588         return 0;
1589 }
1590
1591 /**
1592  * \brief Read an item info response from the server
1593  * \param Dest  Destination for the read item (strings will be on the heap)
1594  */
1595 int ReadItemInfo(int Socket, tItem *Dest)
1596 {
1597         char    *buf;
1598          int    responseCode;
1599         
1600         regmatch_t      matches[8];
1601         char    *statusStr;
1602         
1603         // Get item info
1604         buf = ReadLine(Socket);
1605         responseCode = atoi(buf);
1606         
1607         switch(responseCode)
1608         {
1609         case 202:       break;
1610         
1611         case 406:
1612                 printf("Bad item name\n");
1613                 free(buf);
1614                 return RV_BAD_ITEM;
1615         
1616         default:
1617                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
1618                 free(buf);
1619                 return RV_UNKNOWN_ERROR;
1620         }
1621         
1622         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
1623         
1624         buf[ matches[3].rm_eo ] = '\0';
1625         buf[ matches[5].rm_eo ] = '\0';
1626         buf[ matches[7].rm_eo ] = '\0';
1627         
1628         statusStr = &buf[ matches[5].rm_so ];
1629         
1630         Dest->ID = atoi( buf + matches[4].rm_so );
1631         
1632         if( strcmp(statusStr, "avail") == 0 )
1633                 Dest->Status = 0;
1634         else if( strcmp(statusStr, "sold") == 0 )
1635                 Dest->Status = 1;
1636         else if( strcmp(statusStr, "error") == 0 )
1637                 Dest->Status = -1;
1638         else {
1639                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
1640                         statusStr);
1641                 return RV_UNKNOWN_ERROR;
1642         }
1643         Dest->Price = atoi( buf + matches[6].rm_so );
1644         
1645         // Hack a little to reduce heap fragmentation
1646         {
1647                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
1648                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
1649                 strcpy(tmpType, buf + matches[3].rm_so);
1650                 strcpy(tmpDesc, buf + matches[7].rm_so);
1651                 free(buf);
1652                 Dest->Type = strdup( tmpType );
1653                 Dest->Desc = strdup( tmpDesc );
1654         }
1655         
1656         return 0;
1657 }
1658
1659 /**
1660  * \brief Fill the item information structure
1661  * \return Boolean Failure
1662  */
1663 void PopulateItemList(int Socket)
1664 {
1665         char    *buf;
1666          int    responseCode;
1667         
1668         char    *arrayType;
1669          int    count, i;
1670         regmatch_t      matches[4];
1671         
1672         // Ask server for stock list
1673         send(Socket, "ENUM_ITEMS\n", 11, 0);
1674         buf = ReadLine(Socket);
1675         
1676         //printf("Output: %s\n", buf);
1677         
1678         responseCode = atoi(buf);
1679         if( responseCode != 201 ) {
1680                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
1681                 exit(RV_UNKNOWN_ERROR);
1682         }
1683         
1684         // - Get item list -
1685         
1686         // Expected format:
1687         //  201 Items <count>
1688         //  202 Item <count>
1689         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1690                 
1691         arrayType = &buf[ matches[2].rm_so ];   buf[ matches[2].rm_eo ] = '\0';
1692         count = atoi( &buf[ matches[3].rm_so ] );
1693                 
1694         // Check array type
1695         if( strcmp(arrayType, "Items") != 0 ) {
1696                 // What the?!
1697                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
1698                         arrayType);
1699                 exit(RV_UNKNOWN_ERROR);
1700         }
1701         free(buf);
1702         
1703         giNumItems = count;
1704         gaItems = malloc( giNumItems * sizeof(tItem) );
1705         
1706         // Fetch item information
1707         for( i = 0; i < giNumItems; i ++ )
1708         {
1709                 ReadItemInfo( Socket, &gaItems[i] );
1710         }
1711         
1712         // Read end of list
1713         buf = ReadLine(Socket);
1714         responseCode = atoi(buf);
1715                 
1716         if( responseCode != 200 ) {
1717                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
1718                         responseCode, buf
1719                         );
1720                 exit(-1);
1721         }
1722         
1723         free(buf);
1724 }
1725
1726
1727 /**
1728  * \brief Get information on an item
1729  * \return Boolean Failure
1730  */
1731 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
1732 {
1733         tItem   item;
1734          int    ret;
1735         
1736         // Query
1737         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
1738         
1739         ret = ReadItemInfo(Socket, &item);
1740         if(ret) return ret;
1741         
1742         printf("%8s:%-2i %2i.%02i %s\n",
1743                 item.Type, item.ID,
1744                 item.Price/100, item.Price%100,
1745                 item.Desc);
1746         
1747         free(item.Type);
1748         free(item.Desc);
1749         
1750         return 0;
1751 }
1752
1753 /**
1754  * \brief Dispense an item
1755  * \return Boolean Failure
1756  */
1757 int DispenseItem(int Socket, const char *Type, int ID)
1758 {
1759          int    ret, responseCode;
1760         char    *buf;
1761         
1762         // Check for a dry run
1763         if( gbDryRun ) {
1764                 printf("Dry Run - No action\n");
1765                 return 0;
1766         }
1767         
1768         // Dispense!
1769         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
1770         buf = ReadLine(Socket);
1771         
1772         responseCode = atoi(buf);
1773         switch( responseCode )
1774         {
1775         case 200:
1776                 printf("Dispense OK\n");
1777                 ret = 0;
1778                 break;
1779         case 401:
1780                 printf("Not authenticated\n");
1781                 ret = RV_PERMISSIONS;
1782                 break;
1783         case 402:
1784                 printf("Insufficient balance\n");
1785                 ret = RV_BALANCE;
1786                 break;
1787         case 406:
1788                 printf("Bad item name\n");
1789                 ret = RV_BAD_ITEM;
1790                 break;
1791         case 500:
1792                 printf("Item failed to dispense, is the slot empty?\n");
1793                 ret = RV_SERVER_ERROR;
1794                 break;
1795         case 501:
1796                 printf("Dispense not possible (slot empty/permissions)\n");
1797                 ret = RV_SERVER_ERROR;
1798                 break;
1799         default:
1800                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
1801                 ret = RV_UNKNOWN_ERROR;
1802                 break;
1803         }
1804         
1805         free(buf);
1806         return ret;
1807 }
1808
1809 /**
1810  * \brief Alter a user's balance
1811  */
1812 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
1813 {
1814         char    *buf;
1815          int    responseCode, rv = -1;
1816         
1817         // Check for a dry run
1818         if( gbDryRun ) {
1819                 printf("Dry Run - No action\n");
1820                 return 0;
1821         }
1822
1823         // Sanity
1824         if( Ammount == 0 ) {
1825                 printf("An amount would be nice\n");
1826                 return RV_ARGUMENTS;
1827         }
1828         
1829         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
1830         buf = ReadLine(Socket);
1831         
1832         responseCode = atoi(buf);
1833         
1834         switch(responseCode)
1835         {
1836         case 200:
1837                 rv = 0; // OK
1838                 break;
1839         case 402:
1840                 fprintf(stderr, "Insufficient balance\n");
1841                 rv = RV_BAD_ITEM;
1842                 break;
1843         case 403:       // Not in coke
1844                 fprintf(stderr, "You are not in coke (sucker)\n");
1845                 rv = RV_PERMISSIONS;
1846                 break;
1847         case 404:       // Unknown user
1848                 fprintf(stderr, "Unknown user '%s'\n", Username);
1849                 rv = RV_INVALID_USER;
1850                 break;
1851         default:
1852                 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
1853                 rv = RV_UNKNOWN_RESPONSE;
1854                 break;
1855         }
1856         free(buf);
1857         
1858         return rv;
1859 }
1860
1861 /**
1862  * \brief Set a user's balance
1863  * \note Only avaliable to dispense admins
1864  */
1865 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1866 {
1867         char    *buf;
1868          int    responseCode;
1869         
1870         // Check for a dry run
1871         if( gbDryRun ) {
1872                 printf("Dry Run - No action\n");
1873                 return 0;
1874         }
1875         
1876         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1877         buf = ReadLine(Socket);
1878         
1879         responseCode = atoi(buf);
1880         free(buf);
1881         
1882         switch(responseCode)
1883         {
1884         case 200:       return 0;       // OK
1885         case 403:       // Not in coke
1886                 fprintf(stderr, "You are not an admin\n");
1887                 return RV_PERMISSIONS;
1888         case 404:       // Unknown user
1889                 fprintf(stderr, "Unknown user '%s'\n", Username);
1890                 return RV_INVALID_USER;
1891         default:
1892                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1893                 return RV_UNKNOWN_RESPONSE;
1894         }
1895         
1896         return -1;
1897 }
1898
1899 /**
1900  * \brief Give money to another user
1901  */
1902 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1903 {
1904         char    *buf;
1905          int    responseCode;
1906         
1907         if( Ammount < 0 ) {
1908                 printf("Sorry, you can only give, you can't take.\n");
1909                 return RV_ARGUMENTS;
1910         }
1911         
1912         // Fast return on zero
1913         if( Ammount == 0 ) {
1914                 printf("Are you actually going to give any?\n");
1915                 return RV_ARGUMENTS;
1916         }
1917         
1918         // Check for a dry run
1919         if( gbDryRun ) {
1920                 printf("Dry Run - No action\n");
1921                 return 0;
1922         }
1923         
1924         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1925
1926         buf = ReadLine(Socket);
1927         responseCode = atoi(buf);
1928         free(buf);      
1929         switch(responseCode)
1930         {
1931         case 200:
1932                 printf("Give succeeded\n");
1933                 return RV_SUCCESS;      // OK
1934         
1935         case 402:       
1936                 fprintf(stderr, "Insufficient balance\n");
1937                 return RV_BALANCE;
1938         
1939         case 404:       // Unknown user
1940                 fprintf(stderr, "Unknown user '%s'\n", Username);
1941                 return RV_INVALID_USER;
1942         
1943         default:
1944                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1945                 return RV_UNKNOWN_RESPONSE;
1946         }
1947         
1948         return -1;
1949 }
1950
1951 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
1952 {
1953         char    *buf;
1954          int    responseCode, ret = -1;
1955         
1956         // Check item id
1957         if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
1958         {
1959                 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
1960                 return RV_ARGUMENTS;
1961         }
1962
1963         // Check username (quick)
1964         if( strchr(Username, ' ') || strchr(Username, '\n') )
1965         {
1966                 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
1967                 return RV_ARGUMENTS;
1968         }
1969
1970         // Send the query
1971         sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
1972
1973         buf = ReadLine(Socket);
1974         responseCode = atoi(buf);
1975         switch(responseCode)
1976         {
1977         case 200:
1978                 Dispense_ShowUser(Socket, Username);    // Show destination account
1979                 ret = 0;
1980                 break;
1981         case 403:
1982                 fprintf(stderr, "Refund access is only avaliable to coke members\n");
1983                 ret = RV_PERMISSIONS;
1984                 break;
1985         case 404:
1986                 fprintf(stderr, "Unknown user '%s' passed\n", Username);
1987                 ret = RV_INVALID_USER;
1988                 break;
1989         case 406:
1990                 fprintf(stderr, "Invalid item '%s' passed\n", Item);
1991                 ret = RV_BAD_ITEM;
1992                 break;
1993         default:
1994                 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
1995                 ret = -1;
1996                 break;
1997         }
1998         free(buf);
1999         return ret;
2000 }
2001
2002 /**
2003  * \brief Donate money to the club
2004  */
2005 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
2006 {
2007         char    *buf;
2008          int    responseCode;
2009         
2010         if( Ammount < 0 ) {
2011                 printf("Sorry, you can only give, you can't take.\n");
2012                 return -1;
2013         }
2014         
2015         // Fast return on zero
2016         if( Ammount == 0 ) {
2017                 printf("Are you actually going to give any?\n");
2018                 return 1;
2019         }
2020         
2021         // Check for a dry run
2022         if( gbDryRun ) {
2023                 printf("Dry Run - No action\n");
2024                 return 0;
2025         }
2026         
2027         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
2028         buf = ReadLine(Socket);
2029         
2030         responseCode = atoi(buf);
2031         free(buf);
2032         
2033         switch(responseCode)
2034         {
2035         case 200:       return 0;       // OK
2036         
2037         case 402:       
2038                 fprintf(stderr, "Insufficient balance\n");
2039                 return 1;
2040         
2041         default:
2042                 fprintf(stderr, "Unknown response code %i\n", responseCode);
2043                 return -1;
2044         }
2045         
2046         return -1;
2047 }
2048
2049 /**
2050  * \brief Enumerate users
2051  */
2052 int Dispense_EnumUsers(int Socket)
2053 {
2054         char    *buf;
2055          int    responseCode;
2056          int    nUsers;
2057         regmatch_t      matches[4];
2058         
2059         if( giMinimumBalance != INT_MIN ) {
2060                 if( giMaximumBalance != INT_MAX ) {
2061                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
2062                 }
2063                 else {
2064                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
2065                 }
2066         }
2067         else {
2068                 if( giMaximumBalance != INT_MAX ) {
2069                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
2070                 }
2071                 else {
2072                         sendf(Socket, "ENUM_USERS\n");
2073                 }
2074         }
2075         buf = ReadLine(Socket);
2076         responseCode = atoi(buf);
2077         
2078         switch(responseCode)
2079         {
2080         case 201:       break;  // Ok, length follows
2081         
2082         default:
2083                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
2084                 free(buf);
2085                 return -1;
2086         }
2087         
2088         // Get count (not actually used)
2089         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
2090         nUsers = atoi( buf + matches[3].rm_so );
2091         printf("%i users returned\n", nUsers);
2092         
2093         // Free string
2094         free(buf);
2095         
2096         // Read returned users
2097         do {
2098                 buf = ReadLine(Socket);
2099                 responseCode = atoi(buf);
2100                 
2101                 if( responseCode != 202 )       break;
2102                 
2103                 _PrintUserLine(buf);
2104                 free(buf);
2105         } while(responseCode == 202);
2106         
2107         // Check final response
2108         if( responseCode != 200 ) {
2109                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
2110                 free(buf);
2111                 return -1;
2112         }
2113         
2114         free(buf);
2115         
2116         return 0;
2117 }
2118
2119 int Dispense_ShowUser(int Socket, const char *Username)
2120 {
2121         char    *buf;
2122          int    responseCode, ret;
2123         
2124         sendf(Socket, "USER_INFO %s\n", Username);
2125         buf = ReadLine(Socket);
2126         
2127         responseCode = atoi(buf);
2128         
2129         switch(responseCode)
2130         {
2131         case 202:
2132                 _PrintUserLine(buf);
2133                 ret = 0;
2134                 break;
2135         
2136         case 404:
2137                 printf("Unknown user '%s'\n", Username);
2138                 ret = 1;
2139                 break;
2140         
2141         default:
2142                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2143                 ret = -1;
2144                 break;
2145         }
2146         
2147         free(buf);
2148         
2149         return ret;
2150 }
2151
2152 void _PrintUserLine(const char *Line)
2153 {
2154         regmatch_t      matches[6];
2155          int    bal;
2156         
2157         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
2158         // 3: Username
2159         // 4: Balance
2160         // 5: Flags
2161         {
2162                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
2163                 char    username[usernameLen + 1];
2164                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
2165                 char    flags[flagsLen + 1];
2166                 
2167                 memcpy(username, Line + matches[3].rm_so, usernameLen);
2168                 username[usernameLen] = '\0';
2169                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
2170                 flags[flagsLen] = '\0';
2171                 
2172                 bal = atoi(Line + matches[4].rm_so);
2173                 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
2174         }
2175 }
2176
2177 int Dispense_AddUser(int Socket, const char *Username)
2178 {
2179         char    *buf;
2180          int    responseCode, ret;
2181         
2182         // Check for a dry run
2183         if( gbDryRun ) {
2184                 printf("Dry Run - No action\n");
2185                 return 0;
2186         }
2187         
2188         sendf(Socket, "USER_ADD %s\n", Username);
2189         
2190         buf = ReadLine(Socket);
2191         responseCode = atoi(buf);
2192         
2193         switch(responseCode)
2194         {
2195         case 200:
2196                 printf("User '%s' added\n", Username);
2197                 ret = 0;
2198                 break;
2199                 
2200         case 403:
2201                 printf("Only wheel can add users\n");
2202                 ret = 1;
2203                 break;
2204                 
2205         case 404:
2206                 printf("User '%s' already exists\n", Username);
2207                 ret = 0;
2208                 break;
2209         
2210         default:
2211                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2212                 ret = -1;
2213                 break;
2214         }
2215         
2216         free(buf);
2217         
2218         return ret;
2219 }
2220
2221 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
2222 {
2223         char    *buf;
2224          int    responseCode, ret;
2225         
2226         // Check for a dry run
2227         if( gbDryRun ) {
2228                 printf("Dry Run - No action\n");
2229                 return 0;
2230         }
2231         
2232         // TODO: Pre-validate the string
2233         
2234         sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
2235         
2236         buf = ReadLine(Socket);
2237         responseCode = atoi(buf);
2238         
2239         switch(responseCode)
2240         {
2241         case 200:
2242                 printf("User '%s' updated\n", Username);
2243                 ret = 0;
2244                 break;
2245                 
2246         case 403:
2247                 printf("Only dispense admins can modify users\n");
2248                 ret = RV_PERMISSIONS;
2249                 break;
2250         
2251         case 404:
2252                 printf("User '%s' does not exist\n", Username);
2253                 ret = RV_INVALID_USER;
2254                 break;
2255         
2256         case 407:
2257                 printf("Flag string is invalid\n");
2258                 ret = RV_ARGUMENTS;
2259                 break;
2260         
2261         default:
2262                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2263                 ret = RV_UNKNOWN_RESPONSE;
2264                 break;
2265         }
2266         
2267         free(buf);
2268         
2269         return ret;
2270 }
2271
2272 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
2273 {
2274         char    *buf;
2275          int    responseCode, ret;
2276         
2277         // Check for a dry run
2278         if( gbDryRun ) {
2279                 printf("Dry Run - No action\n");
2280                 return 0;
2281         }
2282         
2283         sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
2284         
2285         buf = ReadLine(Socket);
2286         responseCode = atoi(buf);
2287         
2288         switch(responseCode)
2289         {
2290         case 200:
2291                 printf("Item %s:%i updated\n", Type, ID);
2292                 ret = 0;
2293                 break;
2294                 
2295         case 403:
2296                 printf("Only coke members can modify the slots\n");
2297                 ret = RV_PERMISSIONS;
2298                 break;
2299         
2300         case 406:
2301                 printf("Invalid item passed\n");
2302                 ret = RV_BAD_ITEM;
2303                 break;
2304         
2305         default:
2306                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2307                 ret = -1;
2308                 break;
2309         }
2310         
2311         free(buf);
2312         
2313         return ret;
2314 }
2315
2316 // ---------------
2317 // --- Helpers ---
2318 // ---------------
2319 char *ReadLine(int Socket)
2320 {
2321         static char     buf[BUFSIZ];
2322         static int      bufPos = 0;
2323         static int      bufValid = 0;
2324          int    len;
2325         char    *newline = NULL;
2326          int    retLen = 0;
2327         char    *ret = malloc(10);
2328         
2329         #if DEBUG_TRACE_SERVER
2330         printf("ReadLine: ");
2331         fflush(stdout);
2332         #endif
2333         
2334         ret[0] = '\0';
2335         
2336         while( !newline )
2337         {
2338                 if( bufValid ) {
2339                         len = bufValid;
2340                 }
2341                 else {
2342                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
2343                         if( len <= 0 ) {
2344                                 free(ret);
2345                                 return strdup("599 Client Connection Error\n");
2346                         }
2347                 }
2348                 buf[bufPos+len] = '\0';
2349                 
2350                 newline = strchr( buf+bufPos, '\n' );
2351                 if( newline ) {
2352                         *newline = '\0';
2353                 }
2354                 
2355                 retLen += strlen(buf+bufPos);
2356                 ret = realloc(ret, retLen + 1);
2357                 strcat( ret, buf+bufPos );
2358                 
2359                 if( newline ) {
2360                          int    newLen = newline - (buf+bufPos) + 1;
2361                         bufValid = len - newLen;
2362                         len = newLen;
2363                 }
2364                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
2365                 else    bufPos += len;
2366         }
2367         
2368         #if DEBUG_TRACE_SERVER
2369         printf("%i '%s'\n", retLen, ret);
2370         #endif
2371         
2372         return ret;
2373 }
2374
2375 int sendf(int Socket, const char *Format, ...)
2376 {
2377         va_list args;
2378          int    len;
2379         
2380         va_start(args, Format);
2381         len = vsnprintf(NULL, 0, Format, args);
2382         va_end(args);
2383         
2384         {
2385                 char    buf[len+1];
2386                 va_start(args, Format);
2387                 vsnprintf(buf, len+1, Format, args);
2388                 va_end(args);
2389                 
2390                 #if DEBUG_TRACE_SERVER
2391                 printf("sendf: %s", buf);
2392                 #endif
2393                 
2394                 return send(Socket, buf, len, 0);
2395         }
2396 }
2397
2398 char *trim(char *string)
2399 {
2400          int    i;
2401         
2402         while( isspace(*string) )
2403                 string ++;
2404         
2405         for( i = strlen(string); i--; )
2406         {
2407                 if( isspace(string[i]) )
2408                         string[i] = '\0';
2409                 else
2410                         break;
2411         }
2412         
2413         return string;
2414 }
2415
2416 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
2417 {
2418          int    ret;
2419         
2420         ret = regexec(regex, string, nMatches, matches, 0);
2421         if( ret && errorMessage ) {
2422                 size_t  len = regerror(ret, regex, NULL, 0);
2423                 char    errorStr[len];
2424                 regerror(ret, regex, errorStr, len);
2425                 printf("string = '%s'\n", string);
2426                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
2427                 exit(-1);
2428         }
2429         
2430         return ret;
2431 }
2432
2433 void CompileRegex(regex_t *regex, const char *pattern, int flags)
2434 {
2435          int    ret = regcomp(regex, pattern, flags);
2436         if( ret ) {
2437                 size_t  len = regerror(ret, regex, NULL, 0);
2438                 char    errorStr[len];
2439                 regerror(ret, regex, errorStr, len);
2440                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
2441                 exit(-1);
2442         }
2443 }

UCC git Repository :: git.ucc.asn.au