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

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