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

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