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

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