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

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