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

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