59643648e95310f6acc0a1a9712861ac0066c06f
[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    4       // 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;
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 != 2 ) {
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 != 3 ) {
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 != 2 ) {
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                 DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
521                 close(sock);
522         }
523
524         return 0;
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         if( geteuid() == 0 )
968         {
969                  int    i;
970                 struct sockaddr_in      localAddr;
971                 memset(&localAddr, 0, sizeof(localAddr));
972                 localAddr.sin_family = AF_INET; // IPv4
973                 
974                 // Loop through all the top ports until one is avaliable
975                 for( i = 1001; i < 1024; i ++)
976                 {
977                         localAddr.sin_port = htons(i);  // IPv4
978                         // Attempt to bind to low port for autoauth
979                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
980                                 break;
981                 }
982                 if( i == 1024 )
983                         printf("Warning: AUTOAUTH unavaliable\n");
984         }
985         
986         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
987                 fprintf(stderr, "Failed to connect to server\n");
988                 return -1;
989         }
990         
991         return sock;
992 }
993
994 /**
995  * \brief Authenticate with the server
996  * \return Boolean Failure
997  */
998 int Authenticate(int Socket)
999 {
1000         struct passwd   *pwd;
1001         char    *buf;
1002          int    responseCode;
1003         char    salt[32];
1004          int    i;
1005         regmatch_t      matches[4];
1006         
1007         if( gbIsAuthenticated ) return 0;
1008         
1009         // Get user name
1010         pwd = getpwuid( getuid() );
1011         
1012         // Attempt automatic authentication
1013         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
1014         
1015         // Check if it worked
1016         buf = ReadLine(Socket);
1017         
1018         responseCode = atoi(buf);
1019         switch( responseCode )
1020         {
1021         case 200:       // Autoauth succeeded, return
1022                 free(buf);
1023                 break;
1024         
1025         case 401:       // Untrusted, attempt password authentication
1026                 free(buf);
1027                 
1028                 sendf(Socket, "USER %s\n", pwd->pw_name);
1029                 printf("Using username %s\n", pwd->pw_name);
1030                 
1031                 buf = ReadLine(Socket);
1032                 
1033                 // TODO: Get Salt
1034                 // Expected format: 100 SALT <something> ...
1035                 // OR             : 100 User Set
1036                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
1037                 responseCode = atoi(buf);
1038                 if( responseCode != 100 ) {
1039                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1040                         free(buf);
1041                         return -1;      // ERROR
1042                 }
1043                 
1044                 // Check for salt
1045                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
1046                         // Store it for later
1047                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
1048                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
1049                 }
1050                 free(buf);
1051                 
1052                 // Give three attempts
1053                 for( i = 0; i < 3; i ++ )
1054                 {
1055                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
1056                         char    tmpBuf[42];
1057                         char    tmp[ofs+20];
1058                         char    *pass = getpass("Password: ");
1059                         uint8_t h[20];
1060                         
1061                         // Create hash string
1062                         // <username><salt><hash>
1063                         strcpy(tmp, pwd->pw_name);
1064                         strcat(tmp, salt);
1065                         SHA1( (unsigned char*)pass, strlen(pass), h );
1066                         memcpy(tmp+ofs, h, 20);
1067                         
1068                         // Hash all that
1069                         SHA1( (unsigned char*)tmp, ofs+20, h );
1070                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1071                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
1072                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
1073                                 );
1074                 
1075                         // Send password
1076                         sendf(Socket, "PASS %s\n", tmpBuf);
1077                         buf = ReadLine(Socket);
1078                 
1079                         responseCode = atoi(buf);
1080                         // Auth OK?
1081                         if( responseCode == 200 )       break;
1082                         // Bad username/password
1083                         if( responseCode == 401 )       continue;
1084                         
1085                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1086                         free(buf);
1087                         return -1;
1088                 }
1089                 free(buf);
1090                 if( i == 3 )
1091                         return 2;       // 2 = Bad Password
1092                 break;
1093         
1094         case 404:       // Bad Username
1095                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
1096                 free(buf);
1097                 return 1;
1098         
1099         default:
1100                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1101                 printf("%s\n", buf);
1102                 free(buf);
1103                 return -1;
1104         }
1105         
1106         // Set effective user
1107         if( gsEffectiveUser ) {
1108                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
1109                 
1110                 buf = ReadLine(Socket);
1111                 responseCode = atoi(buf);
1112                 
1113                 switch(responseCode)
1114                 {
1115                 case 200:
1116                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
1117                         break;
1118                 
1119                 case 403:
1120                         printf("Only coke members can use `dispense -u`\n");
1121                         free(buf);
1122                         return -1;
1123                 
1124                 case 404:
1125                         printf("Invalid user selected\n");
1126                         free(buf);
1127                         return -1;
1128                 
1129                 default:
1130                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1131                         printf("%s\n", buf);
1132                         free(buf);
1133                         exit(-1);
1134                 }
1135                 
1136                 free(buf);
1137         }
1138         
1139         gbIsAuthenticated = 1;
1140         
1141         return 0;
1142 }
1143
1144 int GetUserBalance(int Socket)
1145 {
1146         regmatch_t      matches[6];
1147         struct passwd   *pwd;
1148         char    *buf;
1149          int    responseCode;
1150         
1151         if( !gsUserName )
1152         {
1153                 if( gsEffectiveUser ) {
1154                         gsUserName = gsEffectiveUser;
1155                 }
1156                 else {
1157                         pwd = getpwuid( getuid() );
1158                         gsUserName = strdup(pwd->pw_name);
1159                 }
1160         }
1161         
1162         sendf(Socket, "USER_INFO %s\n", gsUserName);
1163         buf = ReadLine(Socket);
1164         responseCode = atoi(buf);
1165         switch(responseCode)
1166         {
1167         case 202:       break;  // Ok
1168         
1169         case 404:
1170                 printf("Invalid user? (USER_INFO failed)\n");
1171                 free(buf);
1172                 return -1;
1173         
1174         default:
1175                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1176                 printf("%s\n", buf);
1177                 free(buf);
1178                 exit(-1);
1179         }
1180
1181         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
1182         
1183         giUserBalance = atoi( buf + matches[4].rm_so );
1184         gsUserFlags = strdup( buf + matches[5].rm_so );
1185         
1186         free(buf);
1187         
1188         return 0;
1189 }
1190
1191 /**
1192  * \brief Read an item info response from the server
1193  * \param Dest  Destination for the read item (strings will be on the heap)
1194  */
1195 int ReadItemInfo(int Socket, tItem *Dest)
1196 {
1197         char    *buf;
1198          int    responseCode;
1199         
1200         regmatch_t      matches[8];
1201         char    *statusStr;
1202         
1203         // Get item info
1204         buf = ReadLine(Socket);
1205         responseCode = atoi(buf);
1206         
1207         switch(responseCode)
1208         {
1209         case 202:       break;
1210         
1211         case 406:
1212                 printf("Bad item name\n");
1213                 free(buf);
1214                 return 1;
1215         
1216         default:
1217                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
1218                 exit(-1);
1219         }
1220         
1221         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
1222         
1223         buf[ matches[3].rm_eo ] = '\0';
1224         buf[ matches[5].rm_eo ] = '\0';
1225         buf[ matches[7].rm_eo ] = '\0';
1226         
1227         statusStr = &buf[ matches[5].rm_so ];
1228         
1229         Dest->ID = atoi( buf + matches[4].rm_so );
1230         
1231         if( strcmp(statusStr, "avail") == 0 )
1232                 Dest->Status = 0;
1233         else if( strcmp(statusStr, "sold") == 0 )
1234                 Dest->Status = 1;
1235         else if( strcmp(statusStr, "error") == 0 )
1236                 Dest->Status = -1;
1237         else {
1238                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
1239                         statusStr);
1240                 return 1;
1241         }
1242         Dest->Price = atoi( buf + matches[6].rm_so );
1243         
1244         // Hack a little to reduce heap fragmentation
1245         {
1246                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
1247                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
1248                 strcpy(tmpType, buf + matches[3].rm_so);
1249                 strcpy(tmpDesc, buf + matches[7].rm_so);
1250                 free(buf);
1251                 Dest->Type = strdup( tmpType );
1252                 Dest->Desc = strdup( tmpDesc );
1253         }
1254         
1255         return 0;
1256 }
1257
1258 /**
1259  * \brief Fill the item information structure
1260  * \return Boolean Failure
1261  */
1262 void PopulateItemList(int Socket)
1263 {
1264         char    *buf;
1265          int    responseCode;
1266         
1267         char    *itemType, *itemStart;
1268          int    count, i;
1269         regmatch_t      matches[4];
1270         
1271         // Ask server for stock list
1272         send(Socket, "ENUM_ITEMS\n", 11, 0);
1273         buf = ReadLine(Socket);
1274         
1275         //printf("Output: %s\n", buf);
1276         
1277         responseCode = atoi(buf);
1278         if( responseCode != 201 ) {
1279                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
1280                 exit(-1);
1281         }
1282         
1283         // - Get item list -
1284         
1285         // Expected format:
1286         //  201 Items <count>
1287         //  202 Item <count>
1288         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1289                 
1290         itemType = &buf[ matches[2].rm_so ];    buf[ matches[2].rm_eo ] = '\0';
1291         count = atoi( &buf[ matches[3].rm_so ] );
1292                 
1293         // Check array type
1294         if( strcmp(itemType, "Items") != 0 ) {
1295                 // What the?!
1296                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
1297                         itemType);
1298                 exit(-1);
1299         }
1300                 
1301         itemStart = &buf[ matches[3].rm_eo ];
1302         
1303         free(buf);
1304         
1305         giNumItems = count;
1306         gaItems = malloc( giNumItems * sizeof(tItem) );
1307         
1308         // Fetch item information
1309         for( i = 0; i < giNumItems; i ++ )
1310         {
1311                 ReadItemInfo( Socket, &gaItems[i] );
1312         }
1313         
1314         // Read end of list
1315         buf = ReadLine(Socket);
1316         responseCode = atoi(buf);
1317                 
1318         if( responseCode != 200 ) {
1319                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
1320                         responseCode, buf
1321                         );
1322                 exit(-1);
1323         }
1324         
1325         free(buf);
1326 }
1327
1328
1329 /**
1330  * \brief Get information on an item
1331  * \return Boolean Failure
1332  */
1333 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
1334 {
1335         tItem   item;
1336         
1337         // Query
1338         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
1339         
1340         if( ReadItemInfo(Socket, &item) )
1341         {
1342                 return -1;
1343         }
1344         
1345         printf("%8s:%-2i %2i.%02i %s\n",
1346                 item.Type, item.ID,
1347                 item.Price/100, item.Price%100,
1348                 item.Desc);
1349         
1350         free(item.Type);
1351         free(item.Desc);
1352         
1353         return 0;
1354 }
1355
1356 /**
1357  * \brief Dispense an item
1358  * \return Boolean Failure
1359  */
1360 int DispenseItem(int Socket, const char *Type, int ID)
1361 {
1362          int    ret, responseCode;
1363         char    *buf;
1364         
1365         // Check for a dry run
1366         if( gbDryRun ) {
1367                 printf("Dry Run - No action\n");
1368                 return 0;
1369         }
1370         
1371         // Dispense!
1372         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
1373         buf = ReadLine(Socket);
1374         
1375         responseCode = atoi(buf);
1376         switch( responseCode )
1377         {
1378         case 200:
1379                 printf("Dispense OK\n");
1380                 ret = 0;
1381                 break;
1382         case 401:
1383                 printf("Not authenticated\n");
1384                 ret = 1;
1385                 break;
1386         case 402:
1387                 printf("Insufficient balance\n");
1388                 ret = 1;
1389                 break;
1390         case 406:
1391                 printf("Bad item name\n");
1392                 ret = 1;
1393                 break;
1394         case 500:
1395                 printf("Item failed to dispense, is the slot empty?\n");
1396                 ret = 1;
1397                 break;
1398         case 501:
1399                 printf("Dispense not possible (slot empty/permissions)\n");
1400                 ret = 1;
1401                 break;
1402         default:
1403                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
1404                 ret = -2;
1405                 break;
1406         }
1407         
1408         free(buf);
1409         return ret;
1410 }
1411
1412 /**
1413  * \brief Alter a user's balance
1414  */
1415 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
1416 {
1417         char    *buf;
1418          int    responseCode;
1419         
1420         // Check for a dry run
1421         if( gbDryRun ) {
1422                 printf("Dry Run - No action\n");
1423                 return 0;
1424         }
1425         
1426         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
1427         buf = ReadLine(Socket);
1428         
1429         responseCode = atoi(buf);
1430         free(buf);
1431         
1432         switch(responseCode)
1433         {
1434         case 200:       return 0;       // OK
1435         case 402:
1436                 fprintf(stderr, "Insufficient balance\n");
1437                 return 1;
1438         case 403:       // Not in coke
1439                 fprintf(stderr, "You are not in coke (sucker)\n");
1440                 return 1;
1441         case 404:       // Unknown user
1442                 fprintf(stderr, "Unknown user '%s'\n", Username);
1443                 return 2;
1444         default:
1445                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1446                 return -1;
1447         }
1448         
1449         return -1;
1450 }
1451
1452 /**
1453  * \brief Set a user's balance
1454  * \note Only avaliable to dispense admins
1455  */
1456 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1457 {
1458         char    *buf;
1459          int    responseCode;
1460         
1461         // Check for a dry run
1462         if( gbDryRun ) {
1463                 printf("Dry Run - No action\n");
1464                 return 0;
1465         }
1466         
1467         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1468         buf = ReadLine(Socket);
1469         
1470         responseCode = atoi(buf);
1471         free(buf);
1472         
1473         switch(responseCode)
1474         {
1475         case 200:       return 0;       // OK
1476         case 403:       // Not in coke
1477                 fprintf(stderr, "You are not an admin\n");
1478                 return 1;
1479         case 404:       // Unknown user
1480                 fprintf(stderr, "Unknown user '%s'\n", Username);
1481                 return 2;
1482         default:
1483                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1484                 return -1;
1485         }
1486         
1487         return -1;
1488 }
1489
1490 /**
1491  * \brief Give money to another user
1492  */
1493 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1494 {
1495         char    *buf;
1496          int    responseCode;
1497         
1498         if( Ammount < 0 ) {
1499                 printf("Sorry, you can only give, you can't take.\n");
1500                 return -1;
1501         }
1502         
1503         // Fast return on zero
1504         if( Ammount == 0 ) {
1505                 printf("Are you actually going to give any?\n");
1506                 return 0;
1507         }
1508         
1509         // Check for a dry run
1510         if( gbDryRun ) {
1511                 printf("Dry Run - No action\n");
1512                 return 0;
1513         }
1514         
1515         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1516         buf = ReadLine(Socket);
1517         
1518         responseCode = atoi(buf);
1519         free(buf);
1520         
1521         switch(responseCode)
1522         {
1523         case 200:       return 0;       // OK
1524         
1525         case 402:       
1526                 fprintf(stderr, "Insufficient balance\n");
1527                 return 1;
1528         
1529         case 404:       // Unknown user
1530                 fprintf(stderr, "Unknown user '%s'\n", Username);
1531                 return 2;
1532         
1533         default:
1534                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1535                 return -1;
1536         }
1537         
1538         return -1;
1539 }
1540
1541
1542 /**
1543  * \brief Donate money to the club
1544  */
1545 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
1546 {
1547         char    *buf;
1548          int    responseCode;
1549         
1550         if( Ammount < 0 ) {
1551                 printf("Sorry, you can only give, you can't take.\n");
1552                 return -1;
1553         }
1554         
1555         // Fast return on zero
1556         if( Ammount == 0 ) {
1557                 printf("Are you actually going to give any?\n");
1558                 return 0;
1559         }
1560         
1561         // Check for a dry run
1562         if( gbDryRun ) {
1563                 printf("Dry Run - No action\n");
1564                 return 0;
1565         }
1566         
1567         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
1568         buf = ReadLine(Socket);
1569         
1570         responseCode = atoi(buf);
1571         free(buf);
1572         
1573         switch(responseCode)
1574         {
1575         case 200:       return 0;       // OK
1576         
1577         case 402:       
1578                 fprintf(stderr, "Insufficient balance\n");
1579                 return 1;
1580         
1581         default:
1582                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1583                 return -1;
1584         }
1585         
1586         return -1;
1587 }
1588
1589 /**
1590  * \brief Enumerate users
1591  */
1592 int Dispense_EnumUsers(int Socket)
1593 {
1594         char    *buf;
1595          int    responseCode;
1596          int    nUsers;
1597         regmatch_t      matches[4];
1598         
1599         if( giMinimumBalance != INT_MIN ) {
1600                 if( giMaximumBalance != INT_MAX ) {
1601                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
1602                 }
1603                 else {
1604                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
1605                 }
1606         }
1607         else {
1608                 if( giMaximumBalance != INT_MAX ) {
1609                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
1610                 }
1611                 else {
1612                         sendf(Socket, "ENUM_USERS\n");
1613                 }
1614         }
1615         buf = ReadLine(Socket);
1616         responseCode = atoi(buf);
1617         
1618         switch(responseCode)
1619         {
1620         case 201:       break;  // Ok, length follows
1621         
1622         default:
1623                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1624                 free(buf);
1625                 return -1;
1626         }
1627         
1628         // Get count (not actually used)
1629         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1630         nUsers = atoi( buf + matches[3].rm_so );
1631         printf("%i users returned\n", nUsers);
1632         
1633         // Free string
1634         free(buf);
1635         
1636         // Read returned users
1637         do {
1638                 buf = ReadLine(Socket);
1639                 responseCode = atoi(buf);
1640                 
1641                 if( responseCode != 202 )       break;
1642                 
1643                 _PrintUserLine(buf);
1644                 free(buf);
1645         } while(responseCode == 202);
1646         
1647         // Check final response
1648         if( responseCode != 200 ) {
1649                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1650                 free(buf);
1651                 return -1;
1652         }
1653         
1654         free(buf);
1655         
1656         return 0;
1657 }
1658
1659 int Dispense_ShowUser(int Socket, const char *Username)
1660 {
1661         char    *buf;
1662          int    responseCode, ret;
1663         
1664         sendf(Socket, "USER_INFO %s\n", Username);
1665         buf = ReadLine(Socket);
1666         
1667         responseCode = atoi(buf);
1668         
1669         switch(responseCode)
1670         {
1671         case 202:
1672                 _PrintUserLine(buf);
1673                 ret = 0;
1674                 break;
1675         
1676         case 404:
1677                 printf("Unknown user '%s'\n", Username);
1678                 ret = 1;
1679                 break;
1680         
1681         default:
1682                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1683                 ret = -1;
1684                 break;
1685         }
1686         
1687         free(buf);
1688         
1689         return ret;
1690 }
1691
1692 void _PrintUserLine(const char *Line)
1693 {
1694         regmatch_t      matches[6];
1695          int    bal;
1696         
1697         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
1698         // 3: Username
1699         // 4: Balance
1700         // 5: Flags
1701         {
1702                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
1703                 char    username[usernameLen + 1];
1704                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
1705                 char    flags[flagsLen + 1];
1706                 
1707                 memcpy(username, Line + matches[3].rm_so, usernameLen);
1708                 username[usernameLen] = '\0';
1709                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
1710                 flags[flagsLen] = '\0';
1711                 
1712                 bal = atoi(Line + matches[4].rm_so);
1713                 printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, abs(bal)%100, flags);
1714         }
1715 }
1716
1717 int Dispense_AddUser(int Socket, const char *Username)
1718 {
1719         char    *buf;
1720          int    responseCode, ret;
1721         
1722         // Check for a dry run
1723         if( gbDryRun ) {
1724                 printf("Dry Run - No action\n");
1725                 return 0;
1726         }
1727         
1728         sendf(Socket, "USER_ADD %s\n", Username);
1729         
1730         buf = ReadLine(Socket);
1731         responseCode = atoi(buf);
1732         
1733         switch(responseCode)
1734         {
1735         case 200:
1736                 printf("User '%s' added\n", Username);
1737                 ret = 0;
1738                 break;
1739                 
1740         case 403:
1741                 printf("Only wheel can add users\n");
1742                 ret = 1;
1743                 break;
1744                 
1745         case 404:
1746                 printf("User '%s' already exists\n", Username);
1747                 ret = 0;
1748                 break;
1749         
1750         default:
1751                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1752                 ret = -1;
1753                 break;
1754         }
1755         
1756         free(buf);
1757         
1758         return ret;
1759 }
1760
1761 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString)
1762 {
1763         char    *buf;
1764          int    responseCode, ret;
1765         
1766         // Check for a dry run
1767         if( gbDryRun ) {
1768                 printf("Dry Run - No action\n");
1769                 return 0;
1770         }
1771         
1772         // TODO: Pre-validate the string
1773         
1774         sendf(Socket, "USER_FLAGS %s %s\n", Username, TypeString);
1775         
1776         buf = ReadLine(Socket);
1777         responseCode = atoi(buf);
1778         
1779         switch(responseCode)
1780         {
1781         case 200:
1782                 printf("User '%s' updated\n", Username);
1783                 ret = 0;
1784                 break;
1785                 
1786         case 403:
1787                 printf("Only wheel can modify users\n");
1788                 ret = 1;
1789                 break;
1790         
1791         case 404:
1792                 printf("User '%s' does not exist\n", Username);
1793                 ret = 0;
1794                 break;
1795         
1796         case 407:
1797                 printf("Flag string is invalid\n");
1798                 ret = 0;
1799                 break;
1800         
1801         default:
1802                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1803                 ret = -1;
1804                 break;
1805         }
1806         
1807         free(buf);
1808         
1809         return ret;
1810 }
1811
1812 // ---------------
1813 // --- Helpers ---
1814 // ---------------
1815 char *ReadLine(int Socket)
1816 {
1817         static char     buf[BUFSIZ];
1818         static int      bufPos = 0;
1819         static int      bufValid = 0;
1820          int    len;
1821         char    *newline = NULL;
1822          int    retLen = 0;
1823         char    *ret = malloc(10);
1824         
1825         #if DEBUG_TRACE_SERVER
1826         printf("ReadLine: ");
1827         #endif
1828         fflush(stdout);
1829         
1830         ret[0] = '\0';
1831         
1832         while( !newline )
1833         {
1834                 if( bufValid ) {
1835                         len = bufValid;
1836                 }
1837                 else {
1838                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1839                         buf[bufPos+len] = '\0';
1840                 }
1841                 
1842                 newline = strchr( buf+bufPos, '\n' );
1843                 if( newline ) {
1844                         *newline = '\0';
1845                 }
1846                 
1847                 retLen += strlen(buf+bufPos);
1848                 ret = realloc(ret, retLen + 1);
1849                 strcat( ret, buf+bufPos );
1850                 
1851                 if( newline ) {
1852                          int    newLen = newline - (buf+bufPos) + 1;
1853                         bufValid = len - newLen;
1854                         bufPos += newLen;
1855                 }
1856                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
1857         }
1858         
1859         #if DEBUG_TRACE_SERVER
1860         printf("%i '%s'\n", retLen, ret);
1861         #endif
1862         
1863         return ret;
1864 }
1865
1866 int sendf(int Socket, const char *Format, ...)
1867 {
1868         va_list args;
1869          int    len;
1870         
1871         va_start(args, Format);
1872         len = vsnprintf(NULL, 0, Format, args);
1873         va_end(args);
1874         
1875         {
1876                 char    buf[len+1];
1877                 va_start(args, Format);
1878                 vsnprintf(buf, len+1, Format, args);
1879                 va_end(args);
1880                 
1881                 #if DEBUG_TRACE_SERVER
1882                 printf("sendf: %s", buf);
1883                 #endif
1884                 
1885                 return send(Socket, buf, len, 0);
1886         }
1887 }
1888
1889 char *trim(char *string)
1890 {
1891          int    i;
1892         
1893         while( isspace(*string) )
1894                 string ++;
1895         
1896         for( i = strlen(string); i--; )
1897         {
1898                 if( isspace(string[i]) )
1899                         string[i] = '\0';
1900                 else
1901                         break;
1902         }
1903         
1904         return string;
1905 }
1906
1907 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
1908 {
1909          int    ret;
1910         
1911         ret = regexec(regex, string, nMatches, matches, 0);
1912         if( ret && errorMessage ) {
1913                 size_t  len = regerror(ret, regex, NULL, 0);
1914                 char    errorStr[len];
1915                 regerror(ret, regex, errorStr, len);
1916                 printf("string = '%s'\n", string);
1917                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
1918                 exit(-1);
1919         }
1920         
1921         return ret;
1922 }
1923
1924 void CompileRegex(regex_t *regex, const char *pattern, int flags)
1925 {
1926          int    ret = regcomp(regex, pattern, flags);
1927         if( ret ) {
1928                 size_t  len = regerror(ret, regex, NULL, 0);
1929                 char    errorStr[len];
1930                 regerror(ret, regex, errorStr, len);
1931                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
1932                 exit(-1);
1933         }
1934 }

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