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

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