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

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