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

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