Client - Better scrolling (jk and arrows work)
[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         void _ItemDown(void)
909         {
910                 currentItem ++;
911                 // Skip over spacers
912                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
913                         currentItem ++;
914                 
915                 if( currentItem >= maxItemIndex ) {
916                         currentItem = 0;
917                         // Skip over spacers
918                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
919                                 currentItem ++;
920                 }
921         }
922         
923         void _ItemUp(void)
924         {
925                 currentItem --;
926                 // Skip over spacers
927                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
928                         currentItem --;
929                 
930                 if( currentItem < 0 ) {
931                         currentItem = maxItemIndex - 1;
932                         // Skip over spacers
933                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
934                                 currentItem --;
935                 }
936         }
937
938         // Get Username
939         if( gsEffectiveUser )
940                 username = gsEffectiveUser;
941         else {
942                 pwd = getpwuid( getuid() );
943                 username = pwd->pw_name;
944         }
945         // Get balance
946         snprintf(balance_str, sizeof(balance_str), "$%i.%02i", giUserBalance/100, abs(giUserBalance)%100);
947         
948         // Enter curses mode
949         initscr();
950         cbreak(); noecho();
951         
952         // Get max index
953         maxItemIndex = ShowItemAt(0, 0, 0, -1, 0);
954         // Get item count per screen
955         // - 6: randomly chosen (Need at least 3)
956         items_in_view = LINES - 6;
957         if( items_in_view > maxItemIndex )
958                 items_in_view = maxItemIndex;
959         // Get first index
960         currentItem = 0;
961         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
962                 currentItem ++;
963         
964         
965         // Get dimensions
966         height = items_in_view + 3;
967         width = displayMinWidth;
968         
969         // Get positions
970         xBase = COLS/2 - width/2;
971         yBase = LINES/2 - height/2;
972         
973         for( ;; )
974         {
975                 // Header
976                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
977                 
978                 // Items
979                 for( i = 0; i < items_in_view; i ++ )
980                 {
981                          int    pos = 0;
982                         
983                         move( yBase + 1 + i, xBase );
984                         printw("| ");
985                         
986                         pos += 2;
987                         
988                         // Check for the '...' row
989                         // - Oh god, magic numbers!
990                         if( (i == 0 && itemBase > 0)
991                          || (i == items_in_view - 1 && itemBase < maxItemIndex - items_in_view) )
992                         {
993                                 printw("     ...");     pos += 8;
994                                 times = (width - pos) - 1;
995                                 while(times--)  addch(' ');
996                         }
997                         // Show an item
998                         else {
999                                 ShowItemAt(
1000                                         yBase + 1 + i, xBase + pos,     // Position
1001                                         (width - pos) - 3,      // Width
1002                                         itemBase + i,   // Index
1003                                         !!(currentItem == itemBase + i) // Hilighted
1004                                         );
1005                                 printw("  ");
1006                         }
1007                         
1008                         // Scrollbar (if needed)
1009                         if( maxItemIndex > items_in_view ) {
1010                                 if( i == 0 ) {
1011                                         addch('A');
1012                                 }
1013                                 else if( i == items_in_view - 1 ) {
1014                                         addch('V');
1015                                 }
1016                                 else {
1017                                          int    percentage = itemBase * 100 / (maxItemIndex-items_in_view);
1018                                         if( i-1 == percentage*(items_in_view-3)/100 ) {
1019                                                 addch('#');
1020                                         }
1021                                         else {
1022                                                 addch('|');
1023                                         }
1024                                 }
1025                         }
1026                         else {
1027                                 addch('|');
1028                         }
1029                 }
1030                 
1031                 // Footer
1032                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
1033                 
1034                 // User line
1035                 // - Username, balance, flags
1036                 PrintAlign(yBase+height-1, xBase+1, width-2,
1037                         username, ' ', balance_str, ' ', gsUserFlags);
1038                 
1039                 
1040                 // Get input
1041                 ch = getch();
1042                 
1043                 if( ch == '\x1B' ) {
1044                         ch = getch();
1045                         if( ch == '[' ) {
1046                                 ch = getch();
1047                                 
1048                                 switch(ch)
1049                                 {
1050                                 case 'B':       _ItemDown();    break;
1051                                 case 'A':       _ItemUp();      break;
1052                                 }
1053                         }
1054                         else {
1055                                 
1056                         }
1057                 }
1058                 else {
1059                         switch(ch)
1060                         {
1061                         case '\n':
1062                                 ret = ShowItemAt(0, 0, 0, currentItem, 0);
1063                                 break;
1064                         case 'h':       break;
1065                         case 'j':       _ItemDown();    break;
1066                         case 'k':       _ItemUp();      break;
1067                         case 'l':       break;
1068                         case 0x1b:      // Escape
1069                         case 'q':
1070                                 ret = -1;       // -1: Return with no dispense
1071                                 break;
1072                         }
1073                         
1074                         // Check if the return value was changed
1075                         if( ret != -2 ) break;
1076                 }
1077                 
1078                 // Scroll only if needed
1079                 if( items_in_view < maxItemIndex )
1080                 {
1081                         // - If the current item is above the second item shown, and we're not at the top
1082                         if( currentItem < itemBase + 2 && itemBase > 0 ) {
1083                                 itemBase = currentItem - 2;
1084                                 if(itemBase < 0)        itemBase = 0;
1085                         }
1086                         // - If the current item is below the second item show, and we're not at the bottom
1087                         if( currentItem > itemBase + items_in_view - 2 && itemBase + items_in_view < maxItemIndex ) {
1088                                 itemBase = currentItem - items_in_view + 2;
1089                                 if( itemBase > maxItemIndex - items_in_view )
1090                                         itemBase = maxItemIndex - items_in_view;
1091                         }
1092                 }
1093         }
1094         
1095         
1096         // Leave
1097         endwin();
1098         return ret;
1099 }
1100
1101 /**
1102  * \brief Show item \a Index at (\a Col, \a Row)
1103  * \return Dispense index of item
1104  * \note Part of the NCurses UI
1105  */
1106 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted)
1107 {
1108         char    *name = NULL;
1109          int    price = 0;
1110          int    status = -1;
1111         
1112         switch(giUIMode)
1113         {
1114         // Standard UI
1115         // - This assumes that 
1116         case UI_MODE_STANDARD:
1117                 // Bounds check
1118                 // Index = -1, request limit
1119                 if( Index < 0 || Index >= giNumItems+2 )
1120                         return giNumItems+2;
1121                 // Drink label
1122                 if( Index == 0 )
1123                 {
1124                         price = 0;
1125                         name = "Coke Machine";
1126                         Index = -1;     // -1 indicates a label
1127                         break;
1128                 }
1129                 Index --;
1130                 // Drinks 0 - 6
1131                 if( Index <= 6 )
1132                 {
1133                         name = gaItems[Index].Desc;
1134                         price = gaItems[Index].Price;
1135                         status = gaItems[Index].Status;
1136                         break;
1137                 }
1138                 Index -= 7;
1139                 // EPS label
1140                 if( Index == 0 )
1141                 {
1142                         price = 0;
1143                         name = "Electronic Payment System";
1144                         Index = -1;     // -1 indicates a label
1145                         break;
1146                 }
1147                 Index --;
1148                 Index += 7;
1149                 name = gaItems[Index].Desc;
1150                 price = gaItems[Index].Price;
1151                 status = gaItems[Index].Status;
1152                 break;
1153         default:
1154                 return -1;
1155         }
1156         
1157         // Width = 0, don't print
1158         if( Width > 0 )
1159         {
1160                 // 4 preceding, 5 price
1161                 int nameWidth = Width - 4 - snprintf(NULL, 0, " %4i", price);
1162                 move( Row, Col );
1163                 
1164                 if( Index >= 0 )
1165                 {
1166                         // Show hilight and status
1167                         switch( status )
1168                         {
1169                         case 0:
1170                                 if( bHilighted )
1171                                         printw("->  ");
1172                                 else
1173                                         printw("    ");
1174                                 break;
1175                         case 1:
1176                                 printw("SLD ");
1177                                 break;
1178                         
1179                         default:
1180                         case -1:
1181                                 printw("ERR ");
1182                                 break;
1183                         }
1184                         
1185                         printw("%-*.*s", nameWidth, nameWidth, name);
1186                 
1187                         printw(" %4i", price);
1188                 }
1189                 else
1190                 {
1191                         printw("-- %-*.*s ", Width-4, Width-4, name);
1192                 }
1193         }
1194         
1195         // If the item isn't availiable for sale, return -1 (so it's skipped)
1196         if( status || (price > giUserBalance && gbDisallowSelectWithoutBalance) )
1197                 Index = -1;
1198         
1199         return Index;
1200 }
1201
1202 /**
1203  * \brief Print a three-part string at the specified position (formatted)
1204  * \note NCurses UI Helper
1205  * 
1206  * Prints \a Left on the left of the area, \a Right on the righthand side
1207  * and \a Mid in the middle of the area. These are padded with \a Pad1
1208  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
1209  * 
1210  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
1211  * and the arguments to these are read in that order.
1212  */
1213 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
1214         const char *Mid, char Pad2, const char *Right, ...)
1215 {
1216          int    lLen, mLen, rLen;
1217          int    times;
1218         
1219         va_list args;
1220         
1221         // Get the length of the strings
1222         va_start(args, Right);
1223         lLen = vsnprintf(NULL, 0, Left, args);
1224         mLen = vsnprintf(NULL, 0, Mid, args);
1225         rLen = vsnprintf(NULL, 0, Right, args);
1226         va_end(args);
1227         
1228         // Sanity check
1229         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
1230                 return ;        // TODO: What to do?
1231         }
1232         
1233         move(Row, Col);
1234         
1235         // Render strings
1236         va_start(args, Right);
1237         // - Left
1238         {
1239                 char    tmp[lLen+1];
1240                 vsnprintf(tmp, lLen+1, Left, args);
1241                 addstr(tmp);
1242         }
1243         // - Left padding
1244         times = (Width - mLen)/2 - lLen;
1245         while(times--)  addch(Pad1);
1246         // - Middle
1247         {
1248                 char    tmp[mLen+1];
1249                 vsnprintf(tmp, mLen+1, Mid, args);
1250                 addstr(tmp);
1251         }
1252         // - Right Padding
1253         times = (Width - mLen)/2 - rLen;
1254         if( (Width - mLen) % 2 )        times ++;
1255         while(times--)  addch(Pad2);
1256         // - Right
1257         {
1258                 char    tmp[rLen+1];
1259                 vsnprintf(tmp, rLen+1, Right, args);
1260                 addstr(tmp);
1261         }
1262 }
1263
1264 // ---------------------
1265 // --- Coke Protocol ---
1266 // ---------------------
1267 int OpenConnection(const char *Host, int Port)
1268 {
1269         struct hostent  *host;
1270         struct sockaddr_in      serverAddr;
1271          int    sock;
1272         
1273         host = gethostbyname(Host);
1274         if( !host ) {
1275                 fprintf(stderr, "Unable to look up '%s'\n", Host);
1276                 return -1;
1277         }
1278         
1279         memset(&serverAddr, 0, sizeof(serverAddr));
1280         
1281         serverAddr.sin_family = AF_INET;        // IPv4
1282         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
1283         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
1284         serverAddr.sin_port = htons(Port);
1285         
1286         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
1287         if( sock < 0 ) {
1288                 fprintf(stderr, "Failed to create socket\n");
1289                 return -1;
1290         }
1291
1292 //      printf("geteuid() = %i, getuid() = %i\n", geteuid(), getuid());
1293         
1294         if( geteuid() == 0 || getuid() == 0 )
1295         {
1296                  int    i;
1297                 struct sockaddr_in      localAddr;
1298                 memset(&localAddr, 0, sizeof(localAddr));
1299                 localAddr.sin_family = AF_INET; // IPv4
1300                 
1301                 // Loop through all the top ports until one is avaliable
1302                 for( i = 512; i < 1024; i ++)
1303                 {
1304                         localAddr.sin_port = htons(i);  // IPv4
1305                         // Attempt to bind to low port for autoauth
1306                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
1307                                 break;
1308                 }
1309                 if( i == 1024 )
1310                         printf("Warning: AUTOAUTH unavaliable\n");
1311 //              else
1312 //                      printf("Bound to 0.0.0.0:%i\n", i);
1313         }
1314         
1315         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
1316                 fprintf(stderr, "Failed to connect to server\n");
1317                 return -1;
1318         }
1319
1320         // We're not authenticated if the connection has just opened
1321         gbIsAuthenticated = 0;
1322         
1323         return sock;
1324 }
1325
1326 int Authenticate_AutoAuth(int Socket, const char *Username)
1327 {
1328         char    *buf;
1329          int    responseCode;
1330          int    ret = -1;
1331         
1332         // Attempt automatic authentication
1333         sendf(Socket, "AUTOAUTH %s\n", Username);
1334         
1335         // Check if it worked
1336         buf = ReadLine(Socket);
1337         
1338         responseCode = atoi(buf);
1339         switch( responseCode )
1340         {
1341         case 200:       // Autoauth succeeded, return
1342                 ret = 0;
1343                 break;
1344         
1345         case 401:       // Untrusted
1346 //              fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
1347                 ret = RV_PERMISSIONS;
1348                 break;
1349         case 404:       // Bad Username
1350                 fprintf(stderr, "Bad Username '%s'\n", Username);
1351                 ret = RV_INVALID_USER;
1352                 break;
1353         
1354         default:
1355                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1356                 printf("%s\n", buf);
1357                 ret = RV_UNKNOWN_ERROR;
1358                 break;;
1359         }
1360         
1361         free(buf);
1362         return ret;
1363 }
1364
1365 int Authenticate_AuthIdent(int Socket)
1366 {
1367         char    *buf;
1368          int    responseCode;
1369          int    ret = -1;
1370         
1371         // Attempt automatic authentication
1372         sendf(Socket, "AUTHIDENT\n");
1373         
1374         // Check if it worked
1375         buf = ReadLine(Socket);
1376         
1377         responseCode = atoi(buf);
1378         switch( responseCode )
1379         {
1380         case 200:       // Autoauth succeeded, return
1381                 ret = 0;
1382                 break;
1383         
1384         case 401:       // Untrusted
1385 //              fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
1386                 ret = RV_PERMISSIONS;
1387                 break;
1388         
1389         default:
1390                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1391                 printf("%s\n", buf);
1392                 ret = RV_UNKNOWN_RESPONSE;
1393                 break;
1394         }
1395         
1396         free(buf);
1397
1398         return ret;
1399 }
1400
1401 int Authenticate_Password(int Socket, const char *Username)
1402 {
1403         #if USE_PASSWORD_AUTH
1404         char    *buf;
1405          int    responseCode;   
1406         char    salt[32];
1407          int    i;
1408         regmatch_t      matches[4];
1409
1410         sendf(Socket, "USER %s\n", Username);
1411         printf("Using username %s\n", Username);
1412         
1413         buf = ReadLine(Socket);
1414         
1415         // TODO: Get Salt
1416         // Expected format: 100 SALT <something> ...
1417         // OR             : 100 User Set
1418         RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
1419         responseCode = atoi(buf);
1420         if( responseCode != 100 ) {
1421                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1422                 free(buf);
1423                 return RV_UNKNOWN_ERROR;        // ERROR
1424         }
1425         
1426         // Check for salt
1427         if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
1428                 // Store it for later
1429                 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
1430                 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
1431         }
1432         free(buf);
1433         
1434         // Give three attempts
1435         for( i = 0; i < 3; i ++ )
1436         {
1437                  int    ofs = strlen(Username)+strlen(salt);
1438                 char    tmpBuf[42];
1439                 char    tmp[ofs+20];
1440                 char    *pass = getpass("Password: ");
1441                 uint8_t h[20];
1442                 
1443                 // Create hash string
1444                 // <username><salt><hash>
1445                 strcpy(tmp, Username);
1446                 strcat(tmp, salt);
1447                 SHA1( (unsigned char*)pass, strlen(pass), h );
1448                 memcpy(tmp+ofs, h, 20);
1449                 
1450                 // Hash all that
1451                 SHA1( (unsigned char*)tmp, ofs+20, h );
1452                 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1453                         h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
1454                         h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
1455                         );
1456         
1457                 // Send password
1458                 sendf(Socket, "PASS %s\n", tmpBuf);
1459                 buf = ReadLine(Socket);
1460         
1461                 responseCode = atoi(buf);
1462                 // Auth OK?
1463                 if( responseCode == 200 )       break;
1464                 // Bad username/password
1465                 if( responseCode == 401 )       continue;
1466                 
1467                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1468                 free(buf);
1469                 return -1;
1470         }
1471         free(buf);
1472         if( i == 3 )
1473                 return RV_INVALID_USER; // 2 = Bad Password
1474
1475         return 0;
1476         #else
1477         return RV_INVALID_USER;
1478         #endif
1479 }
1480
1481 /**
1482  * \brief Authenticate with the server
1483  * \return Boolean Failure
1484  */
1485 int Authenticate(int Socket)
1486 {
1487         struct passwd   *pwd;
1488         
1489         if( gbIsAuthenticated ) return 0;
1490         
1491         // Get user name
1492         pwd = getpwuid( getuid() );
1493
1494         // Attempt AUTOAUTH
1495         if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
1496                 ;
1497         else if( Authenticate_AuthIdent(Socket) == 0 )
1498                 ;
1499         else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
1500                 return RV_INVALID_USER;
1501
1502         // Set effective user
1503         if( gsEffectiveUser ) {
1504                 char    *buf;
1505                  int    responseCode;
1506                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
1507                 
1508                 buf = ReadLine(Socket);
1509                 responseCode = atoi(buf);
1510                 
1511                 switch(responseCode)
1512                 {
1513                 case 200:
1514                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
1515                         break;
1516                 
1517                 case 403:
1518                         printf("Only coke members can use `dispense -u`\n");
1519                         free(buf);
1520                         return RV_PERMISSIONS;
1521                 
1522                 case 404:
1523                         printf("Invalid user selected\n");
1524                         free(buf);
1525                         return RV_INVALID_USER;
1526                 
1527                 default:
1528                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1529                         printf("%s\n", buf);
1530                         free(buf);
1531                         return RV_UNKNOWN_ERROR;
1532                 }
1533                 
1534                 free(buf);
1535         }
1536         
1537         gbIsAuthenticated = 1;
1538         
1539         return 0;
1540 }
1541
1542 int GetUserBalance(int Socket)
1543 {
1544         regmatch_t      matches[6];
1545         struct passwd   *pwd;
1546         char    *buf;
1547          int    responseCode;
1548         
1549         if( !gsUserName )
1550         {
1551                 if( gsEffectiveUser ) {
1552                         gsUserName = gsEffectiveUser;
1553                 }
1554                 else {
1555                         pwd = getpwuid( getuid() );
1556                         gsUserName = strdup(pwd->pw_name);
1557                 }
1558         }
1559         
1560         sendf(Socket, "USER_INFO %s\n", gsUserName);
1561         buf = ReadLine(Socket);
1562         responseCode = atoi(buf);
1563         switch(responseCode)
1564         {
1565         case 202:       break;  // Ok
1566         
1567         case 404:
1568                 printf("Invalid user? (USER_INFO failed)\n");
1569                 free(buf);
1570                 return RV_INVALID_USER;
1571         
1572         default:
1573                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1574                 printf("%s\n", buf);
1575                 free(buf);
1576                 return RV_UNKNOWN_ERROR;
1577         }
1578
1579         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
1580         
1581         giUserBalance = atoi( buf + matches[4].rm_so );
1582         gsUserFlags = strdup( buf + matches[5].rm_so );
1583         
1584         free(buf);
1585         
1586         return 0;
1587 }
1588
1589 /**
1590  * \brief Read an item info response from the server
1591  * \param Dest  Destination for the read item (strings will be on the heap)
1592  */
1593 int ReadItemInfo(int Socket, tItem *Dest)
1594 {
1595         char    *buf;
1596          int    responseCode;
1597         
1598         regmatch_t      matches[8];
1599         char    *statusStr;
1600         
1601         // Get item info
1602         buf = ReadLine(Socket);
1603         responseCode = atoi(buf);
1604         
1605         switch(responseCode)
1606         {
1607         case 202:       break;
1608         
1609         case 406:
1610                 printf("Bad item name\n");
1611                 free(buf);
1612                 return RV_BAD_ITEM;
1613         
1614         default:
1615                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
1616                 free(buf);
1617                 return RV_UNKNOWN_ERROR;
1618         }
1619         
1620         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
1621         
1622         buf[ matches[3].rm_eo ] = '\0';
1623         buf[ matches[5].rm_eo ] = '\0';
1624         buf[ matches[7].rm_eo ] = '\0';
1625         
1626         statusStr = &buf[ matches[5].rm_so ];
1627         
1628         Dest->ID = atoi( buf + matches[4].rm_so );
1629         
1630         if( strcmp(statusStr, "avail") == 0 )
1631                 Dest->Status = 0;
1632         else if( strcmp(statusStr, "sold") == 0 )
1633                 Dest->Status = 1;
1634         else if( strcmp(statusStr, "error") == 0 )
1635                 Dest->Status = -1;
1636         else {
1637                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
1638                         statusStr);
1639                 return RV_UNKNOWN_ERROR;
1640         }
1641         Dest->Price = atoi( buf + matches[6].rm_so );
1642         
1643         // Hack a little to reduce heap fragmentation
1644         {
1645                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
1646                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
1647                 strcpy(tmpType, buf + matches[3].rm_so);
1648                 strcpy(tmpDesc, buf + matches[7].rm_so);
1649                 free(buf);
1650                 Dest->Type = strdup( tmpType );
1651                 Dest->Desc = strdup( tmpDesc );
1652         }
1653         
1654         return 0;
1655 }
1656
1657 /**
1658  * \brief Fill the item information structure
1659  * \return Boolean Failure
1660  */
1661 void PopulateItemList(int Socket)
1662 {
1663         char    *buf;
1664          int    responseCode;
1665         
1666         char    *arrayType;
1667          int    count, i;
1668         regmatch_t      matches[4];
1669         
1670         // Ask server for stock list
1671         send(Socket, "ENUM_ITEMS\n", 11, 0);
1672         buf = ReadLine(Socket);
1673         
1674         //printf("Output: %s\n", buf);
1675         
1676         responseCode = atoi(buf);
1677         if( responseCode != 201 ) {
1678                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
1679                 exit(RV_UNKNOWN_ERROR);
1680         }
1681         
1682         // - Get item list -
1683         
1684         // Expected format:
1685         //  201 Items <count>
1686         //  202 Item <count>
1687         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1688                 
1689         arrayType = &buf[ matches[2].rm_so ];   buf[ matches[2].rm_eo ] = '\0';
1690         count = atoi( &buf[ matches[3].rm_so ] );
1691                 
1692         // Check array type
1693         if( strcmp(arrayType, "Items") != 0 ) {
1694                 // What the?!
1695                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
1696                         arrayType);
1697                 exit(RV_UNKNOWN_ERROR);
1698         }
1699         free(buf);
1700         
1701         giNumItems = count;
1702         gaItems = malloc( giNumItems * sizeof(tItem) );
1703         
1704         // Fetch item information
1705         for( i = 0; i < giNumItems; i ++ )
1706         {
1707                 ReadItemInfo( Socket, &gaItems[i] );
1708         }
1709         
1710         // Read end of list
1711         buf = ReadLine(Socket);
1712         responseCode = atoi(buf);
1713                 
1714         if( responseCode != 200 ) {
1715                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
1716                         responseCode, buf
1717                         );
1718                 exit(-1);
1719         }
1720         
1721         free(buf);
1722 }
1723
1724
1725 /**
1726  * \brief Get information on an item
1727  * \return Boolean Failure
1728  */
1729 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
1730 {
1731         tItem   item;
1732          int    ret;
1733         
1734         // Query
1735         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
1736         
1737         ret = ReadItemInfo(Socket, &item);
1738         if(ret) return ret;
1739         
1740         printf("%8s:%-2i %2i.%02i %s\n",
1741                 item.Type, item.ID,
1742                 item.Price/100, item.Price%100,
1743                 item.Desc);
1744         
1745         free(item.Type);
1746         free(item.Desc);
1747         
1748         return 0;
1749 }
1750
1751 /**
1752  * \brief Dispense an item
1753  * \return Boolean Failure
1754  */
1755 int DispenseItem(int Socket, const char *Type, int ID)
1756 {
1757          int    ret, responseCode;
1758         char    *buf;
1759         
1760         // Check for a dry run
1761         if( gbDryRun ) {
1762                 printf("Dry Run - No action\n");
1763                 return 0;
1764         }
1765         
1766         // Dispense!
1767         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
1768         buf = ReadLine(Socket);
1769         
1770         responseCode = atoi(buf);
1771         switch( responseCode )
1772         {
1773         case 200:
1774                 printf("Dispense OK\n");
1775                 ret = 0;
1776                 break;
1777         case 401:
1778                 printf("Not authenticated\n");
1779                 ret = RV_PERMISSIONS;
1780                 break;
1781         case 402:
1782                 printf("Insufficient balance\n");
1783                 ret = RV_BALANCE;
1784                 break;
1785         case 406:
1786                 printf("Bad item name\n");
1787                 ret = RV_BAD_ITEM;
1788                 break;
1789         case 500:
1790                 printf("Item failed to dispense, is the slot empty?\n");
1791                 ret = RV_SERVER_ERROR;
1792                 break;
1793         case 501:
1794                 printf("Dispense not possible (slot empty/permissions)\n");
1795                 ret = RV_SERVER_ERROR;
1796                 break;
1797         default:
1798                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
1799                 ret = RV_UNKNOWN_ERROR;
1800                 break;
1801         }
1802         
1803         free(buf);
1804         return ret;
1805 }
1806
1807 /**
1808  * \brief Alter a user's balance
1809  */
1810 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
1811 {
1812         char    *buf;
1813          int    responseCode, rv = -1;
1814         
1815         // Check for a dry run
1816         if( gbDryRun ) {
1817                 printf("Dry Run - No action\n");
1818                 return 0;
1819         }
1820
1821         // Sanity
1822         if( Ammount == 0 ) {
1823                 printf("An amount would be nice\n");
1824                 return RV_ARGUMENTS;
1825         }
1826         
1827         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
1828         buf = ReadLine(Socket);
1829         
1830         responseCode = atoi(buf);
1831         
1832         switch(responseCode)
1833         {
1834         case 200:
1835                 rv = 0; // OK
1836                 break;
1837         case 402:
1838                 fprintf(stderr, "Insufficient balance\n");
1839                 rv = RV_BAD_ITEM;
1840                 break;
1841         case 403:       // Not in coke
1842                 fprintf(stderr, "You are not in coke (sucker)\n");
1843                 rv = RV_PERMISSIONS;
1844                 break;
1845         case 404:       // Unknown user
1846                 fprintf(stderr, "Unknown user '%s'\n", Username);
1847                 rv = RV_INVALID_USER;
1848                 break;
1849         default:
1850                 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
1851                 rv = RV_UNKNOWN_RESPONSE;
1852                 break;
1853         }
1854         free(buf);
1855         
1856         return rv;
1857 }
1858
1859 /**
1860  * \brief Set a user's balance
1861  * \note Only avaliable to dispense admins
1862  */
1863 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1864 {
1865         char    *buf;
1866          int    responseCode;
1867         
1868         // Check for a dry run
1869         if( gbDryRun ) {
1870                 printf("Dry Run - No action\n");
1871                 return 0;
1872         }
1873         
1874         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1875         buf = ReadLine(Socket);
1876         
1877         responseCode = atoi(buf);
1878         free(buf);
1879         
1880         switch(responseCode)
1881         {
1882         case 200:       return 0;       // OK
1883         case 403:       // Not in coke
1884                 fprintf(stderr, "You are not an admin\n");
1885                 return RV_PERMISSIONS;
1886         case 404:       // Unknown user
1887                 fprintf(stderr, "Unknown user '%s'\n", Username);
1888                 return RV_INVALID_USER;
1889         default:
1890                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1891                 return RV_UNKNOWN_RESPONSE;
1892         }
1893         
1894         return -1;
1895 }
1896
1897 /**
1898  * \brief Give money to another user
1899  */
1900 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1901 {
1902         char    *buf;
1903          int    responseCode;
1904         
1905         if( Ammount < 0 ) {
1906                 printf("Sorry, you can only give, you can't take.\n");
1907                 return RV_ARGUMENTS;
1908         }
1909         
1910         // Fast return on zero
1911         if( Ammount == 0 ) {
1912                 printf("Are you actually going to give any?\n");
1913                 return RV_ARGUMENTS;
1914         }
1915         
1916         // Check for a dry run
1917         if( gbDryRun ) {
1918                 printf("Dry Run - No action\n");
1919                 return 0;
1920         }
1921         
1922         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1923
1924         buf = ReadLine(Socket);
1925         responseCode = atoi(buf);
1926         free(buf);      
1927         switch(responseCode)
1928         {
1929         case 200:
1930                 printf("Give succeeded\n");
1931                 return RV_SUCCESS;      // OK
1932         
1933         case 402:       
1934                 fprintf(stderr, "Insufficient balance\n");
1935                 return RV_BALANCE;
1936         
1937         case 404:       // Unknown user
1938                 fprintf(stderr, "Unknown user '%s'\n", Username);
1939                 return RV_INVALID_USER;
1940         
1941         default:
1942                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1943                 return RV_UNKNOWN_RESPONSE;
1944         }
1945         
1946         return -1;
1947 }
1948
1949 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
1950 {
1951         char    *buf;
1952          int    responseCode, ret = -1;
1953         
1954         // Check item id
1955         if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
1956         {
1957                 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
1958                 return RV_ARGUMENTS;
1959         }
1960
1961         // Check username (quick)
1962         if( strchr(Username, ' ') || strchr(Username, '\n') )
1963         {
1964                 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
1965                 return RV_ARGUMENTS;
1966         }
1967
1968         // Send the query
1969         sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
1970
1971         buf = ReadLine(Socket);
1972         responseCode = atoi(buf);
1973         switch(responseCode)
1974         {
1975         case 200:
1976                 Dispense_ShowUser(Socket, Username);    // Show destination account
1977                 ret = 0;
1978                 break;
1979         case 403:
1980                 fprintf(stderr, "Refund access is only avaliable to coke members\n");
1981                 ret = RV_PERMISSIONS;
1982                 break;
1983         case 404:
1984                 fprintf(stderr, "Unknown user '%s' passed\n", Username);
1985                 ret = RV_INVALID_USER;
1986                 break;
1987         case 406:
1988                 fprintf(stderr, "Invalid item '%s' passed\n", Item);
1989                 ret = RV_BAD_ITEM;
1990                 break;
1991         default:
1992                 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
1993                 ret = -1;
1994                 break;
1995         }
1996         free(buf);
1997         return ret;
1998 }
1999
2000 /**
2001  * \brief Donate money to the club
2002  */
2003 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
2004 {
2005         char    *buf;
2006          int    responseCode;
2007         
2008         if( Ammount < 0 ) {
2009                 printf("Sorry, you can only give, you can't take.\n");
2010                 return -1;
2011         }
2012         
2013         // Fast return on zero
2014         if( Ammount == 0 ) {
2015                 printf("Are you actually going to give any?\n");
2016                 return 1;
2017         }
2018         
2019         // Check for a dry run
2020         if( gbDryRun ) {
2021                 printf("Dry Run - No action\n");
2022                 return 0;
2023         }
2024         
2025         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
2026         buf = ReadLine(Socket);
2027         
2028         responseCode = atoi(buf);
2029         free(buf);
2030         
2031         switch(responseCode)
2032         {
2033         case 200:       return 0;       // OK
2034         
2035         case 402:       
2036                 fprintf(stderr, "Insufficient balance\n");
2037                 return 1;
2038         
2039         default:
2040                 fprintf(stderr, "Unknown response code %i\n", responseCode);
2041                 return -1;
2042         }
2043         
2044         return -1;
2045 }
2046
2047 /**
2048  * \brief Enumerate users
2049  */
2050 int Dispense_EnumUsers(int Socket)
2051 {
2052         char    *buf;
2053          int    responseCode;
2054          int    nUsers;
2055         regmatch_t      matches[4];
2056         
2057         if( giMinimumBalance != INT_MIN ) {
2058                 if( giMaximumBalance != INT_MAX ) {
2059                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
2060                 }
2061                 else {
2062                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
2063                 }
2064         }
2065         else {
2066                 if( giMaximumBalance != INT_MAX ) {
2067                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
2068                 }
2069                 else {
2070                         sendf(Socket, "ENUM_USERS\n");
2071                 }
2072         }
2073         buf = ReadLine(Socket);
2074         responseCode = atoi(buf);
2075         
2076         switch(responseCode)
2077         {
2078         case 201:       break;  // Ok, length follows
2079         
2080         default:
2081                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
2082                 free(buf);
2083                 return -1;
2084         }
2085         
2086         // Get count (not actually used)
2087         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
2088         nUsers = atoi( buf + matches[3].rm_so );
2089         printf("%i users returned\n", nUsers);
2090         
2091         // Free string
2092         free(buf);
2093         
2094         // Read returned users
2095         do {
2096                 buf = ReadLine(Socket);
2097                 responseCode = atoi(buf);
2098                 
2099                 if( responseCode != 202 )       break;
2100                 
2101                 _PrintUserLine(buf);
2102                 free(buf);
2103         } while(responseCode == 202);
2104         
2105         // Check final response
2106         if( responseCode != 200 ) {
2107                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
2108                 free(buf);
2109                 return -1;
2110         }
2111         
2112         free(buf);
2113         
2114         return 0;
2115 }
2116
2117 int Dispense_ShowUser(int Socket, const char *Username)
2118 {
2119         char    *buf;
2120          int    responseCode, ret;
2121         
2122         sendf(Socket, "USER_INFO %s\n", Username);
2123         buf = ReadLine(Socket);
2124         
2125         responseCode = atoi(buf);
2126         
2127         switch(responseCode)
2128         {
2129         case 202:
2130                 _PrintUserLine(buf);
2131                 ret = 0;
2132                 break;
2133         
2134         case 404:
2135                 printf("Unknown user '%s'\n", Username);
2136                 ret = 1;
2137                 break;
2138         
2139         default:
2140                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2141                 ret = -1;
2142                 break;
2143         }
2144         
2145         free(buf);
2146         
2147         return ret;
2148 }
2149
2150 void _PrintUserLine(const char *Line)
2151 {
2152         regmatch_t      matches[6];
2153          int    bal;
2154         
2155         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
2156         // 3: Username
2157         // 4: Balance
2158         // 5: Flags
2159         {
2160                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
2161                 char    username[usernameLen + 1];
2162                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
2163                 char    flags[flagsLen + 1];
2164                 
2165                 memcpy(username, Line + matches[3].rm_so, usernameLen);
2166                 username[usernameLen] = '\0';
2167                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
2168                 flags[flagsLen] = '\0';
2169                 
2170                 bal = atoi(Line + matches[4].rm_so);
2171                 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
2172         }
2173 }
2174
2175 int Dispense_AddUser(int Socket, const char *Username)
2176 {
2177         char    *buf;
2178          int    responseCode, ret;
2179         
2180         // Check for a dry run
2181         if( gbDryRun ) {
2182                 printf("Dry Run - No action\n");
2183                 return 0;
2184         }
2185         
2186         sendf(Socket, "USER_ADD %s\n", Username);
2187         
2188         buf = ReadLine(Socket);
2189         responseCode = atoi(buf);
2190         
2191         switch(responseCode)
2192         {
2193         case 200:
2194                 printf("User '%s' added\n", Username);
2195                 ret = 0;
2196                 break;
2197                 
2198         case 403:
2199                 printf("Only wheel can add users\n");
2200                 ret = 1;
2201                 break;
2202                 
2203         case 404:
2204                 printf("User '%s' already exists\n", Username);
2205                 ret = 0;
2206                 break;
2207         
2208         default:
2209                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2210                 ret = -1;
2211                 break;
2212         }
2213         
2214         free(buf);
2215         
2216         return ret;
2217 }
2218
2219 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
2220 {
2221         char    *buf;
2222          int    responseCode, ret;
2223         
2224         // Check for a dry run
2225         if( gbDryRun ) {
2226                 printf("Dry Run - No action\n");
2227                 return 0;
2228         }
2229         
2230         // TODO: Pre-validate the string
2231         
2232         sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
2233         
2234         buf = ReadLine(Socket);
2235         responseCode = atoi(buf);
2236         
2237         switch(responseCode)
2238         {
2239         case 200:
2240                 printf("User '%s' updated\n", Username);
2241                 ret = 0;
2242                 break;
2243                 
2244         case 403:
2245                 printf("Only dispense admins can modify users\n");
2246                 ret = RV_PERMISSIONS;
2247                 break;
2248         
2249         case 404:
2250                 printf("User '%s' does not exist\n", Username);
2251                 ret = RV_INVALID_USER;
2252                 break;
2253         
2254         case 407:
2255                 printf("Flag string is invalid\n");
2256                 ret = RV_ARGUMENTS;
2257                 break;
2258         
2259         default:
2260                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2261                 ret = RV_UNKNOWN_RESPONSE;
2262                 break;
2263         }
2264         
2265         free(buf);
2266         
2267         return ret;
2268 }
2269
2270 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
2271 {
2272         char    *buf;
2273          int    responseCode, ret;
2274         
2275         // Check for a dry run
2276         if( gbDryRun ) {
2277                 printf("Dry Run - No action\n");
2278                 return 0;
2279         }
2280         
2281         sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
2282         
2283         buf = ReadLine(Socket);
2284         responseCode = atoi(buf);
2285         
2286         switch(responseCode)
2287         {
2288         case 200:
2289                 printf("Item %s:%i updated\n", Type, ID);
2290                 ret = 0;
2291                 break;
2292                 
2293         case 403:
2294                 printf("Only coke members can modify the slots\n");
2295                 ret = RV_PERMISSIONS;
2296                 break;
2297         
2298         case 406:
2299                 printf("Invalid item passed\n");
2300                 ret = RV_BAD_ITEM;
2301                 break;
2302         
2303         default:
2304                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2305                 ret = -1;
2306                 break;
2307         }
2308         
2309         free(buf);
2310         
2311         return ret;
2312 }
2313
2314 // ---------------
2315 // --- Helpers ---
2316 // ---------------
2317 char *ReadLine(int Socket)
2318 {
2319         static char     buf[BUFSIZ];
2320         static int      bufPos = 0;
2321         static int      bufValid = 0;
2322          int    len;
2323         char    *newline = NULL;
2324          int    retLen = 0;
2325         char    *ret = malloc(10);
2326         
2327         #if DEBUG_TRACE_SERVER
2328         printf("ReadLine: ");
2329         fflush(stdout);
2330         #endif
2331         
2332         ret[0] = '\0';
2333         
2334         while( !newline )
2335         {
2336                 if( bufValid ) {
2337                         len = bufValid;
2338                 }
2339                 else {
2340                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
2341                         if( len <= 0 ) {
2342                                 free(ret);
2343                                 return strdup("599 Client Connection Error\n");
2344                         }
2345                 }
2346                 buf[bufPos+len] = '\0';
2347                 
2348                 newline = strchr( buf+bufPos, '\n' );
2349                 if( newline ) {
2350                         *newline = '\0';
2351                 }
2352                 
2353                 retLen += strlen(buf+bufPos);
2354                 ret = realloc(ret, retLen + 1);
2355                 strcat( ret, buf+bufPos );
2356                 
2357                 if( newline ) {
2358                          int    newLen = newline - (buf+bufPos) + 1;
2359                         bufValid = len - newLen;
2360                         len = newLen;
2361                 }
2362                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
2363                 else    bufPos += len;
2364         }
2365         
2366         #if DEBUG_TRACE_SERVER
2367         printf("%i '%s'\n", retLen, ret);
2368         #endif
2369         
2370         return ret;
2371 }
2372
2373 int sendf(int Socket, const char *Format, ...)
2374 {
2375         va_list args;
2376          int    len;
2377         
2378         va_start(args, Format);
2379         len = vsnprintf(NULL, 0, Format, args);
2380         va_end(args);
2381         
2382         {
2383                 char    buf[len+1];
2384                 va_start(args, Format);
2385                 vsnprintf(buf, len+1, Format, args);
2386                 va_end(args);
2387                 
2388                 #if DEBUG_TRACE_SERVER
2389                 printf("sendf: %s", buf);
2390                 #endif
2391                 
2392                 return send(Socket, buf, len, 0);
2393         }
2394 }
2395
2396 char *trim(char *string)
2397 {
2398          int    i;
2399         
2400         while( isspace(*string) )
2401                 string ++;
2402         
2403         for( i = strlen(string); i--; )
2404         {
2405                 if( isspace(string[i]) )
2406                         string[i] = '\0';
2407                 else
2408                         break;
2409         }
2410         
2411         return string;
2412 }
2413
2414 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
2415 {
2416          int    ret;
2417         
2418         ret = regexec(regex, string, nMatches, matches, 0);
2419         if( ret && errorMessage ) {
2420                 size_t  len = regerror(ret, regex, NULL, 0);
2421                 char    errorStr[len];
2422                 regerror(ret, regex, errorStr, len);
2423                 printf("string = '%s'\n", string);
2424                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
2425                 exit(-1);
2426         }
2427         
2428         return ret;
2429 }
2430
2431 void CompileRegex(regex_t *regex, const char *pattern, int flags)
2432 {
2433          int    ret = regcomp(regex, pattern, flags);
2434         if( ret ) {
2435                 size_t  len = regerror(ret, regex, NULL, 0);
2436                 char    errorStr[len];
2437                 regerror(ret, regex, errorStr, len);
2438                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
2439                 exit(-1);
2440         }
2441 }

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