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

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