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

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