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

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