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

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