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

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