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

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