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

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