Implemented limits on `dispense acct` and in ENUM_USERS
[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
31 // === TYPES ===
32 typedef struct sItem {
33         char    *Ident;
34         char    *Desc;
35          int    Price;
36 }       tItem;
37
38 // === PROTOTYPES ===
39  int    main(int argc, char *argv[]);
40 void    ShowUsage(void);
41 // --- GUI ---
42  int    ShowNCursesUI(void);
43 void    ShowItemAt(int Row, int Col, int Width, int Index);
44 void    PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
45 // --- Coke Server Communication ---
46  int    OpenConnection(const char *Host, int Port);
47  int    Authenticate(int Socket);
48 void    PopulateItemList(int Socket);
49  int    DispenseItem(int Socket, int ItemID);
50  int    Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
51  int    Dispense_SetBalance(int Socket, const char *Username, int Ammount, const char *Reason);
52  int    Dispense_EnumUsers(int Socket);
53  int    Dispense_ShowUser(int Socket, const char *Username);
54 void    _PrintUserLine(const char *Line);
55 // --- Helpers ---
56 char    *ReadLine(int Socket);
57  int    sendf(int Socket, const char *Format, ...);
58 char    *trim(char *string);
59  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
60 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
61
62 // === GLOBALS ===
63 char    *gsDispenseServer = "localhost";
64  int    giDispensePort = 11020;
65
66 tItem   *gaItems;
67  int    giNumItems;
68 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex;
69  int    gbIsAuthenticated = 0;
70
71 char    *gsOverrideUser;        //!< '-u' Dispense as another user
72  int    gbUseNCurses = 0;       //!< '-G' Use the NCurses GUI?
73  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
74  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
75
76 // === CODE ===
77 int main(int argc, char *argv[])
78 {
79          int    sock;
80          int    i;
81         char    buffer[BUFSIZ];
82         
83         // -- Create regular expressions
84         // > Code Type Count ...
85         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
86         // > Code Type Ident Price Desc
87         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z0-9:]+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
88         // > Code 'SALT' salt
89         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
90         // > Code 'User' Username Balance Flags
91         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
92
93         // Parse Arguments
94         for( i = 1; i < argc; i ++ )
95         {
96                 char    *arg = argv[i];
97                 
98                 if( arg[0] == '-' )
99                 {                       
100                         switch(arg[1])
101                         {
102                         case 'h':
103                         case '?':
104                                 ShowUsage();
105                                 return 0;
106                         
107                         case 'm':       // Minimum balance
108                                 giMinimumBalance = atoi(argv[++i]);
109                                 break;
110                         case 'M':       // Maximum balance
111                                 giMaximumBalance = atoi(argv[++i]);
112                                 break;
113                         
114                         case 'u':       // Override User
115                                 gsOverrideUser = argv[++i];
116                                 break;
117                         
118                         case 'G':       // Use GUI
119                                 gbUseNCurses = 1;
120                                 break;
121                         }
122
123                         continue;
124                 }
125                 
126                 if( strcmp(arg, "acct") == 0 )
127                 {
128
129                         // Connect to server
130                         sock = OpenConnection(gsDispenseServer, giDispensePort);
131                         if( sock < 0 )  return -1;
132
133                         // List accounts?
134                         if( i + 1 == argc ) {
135                                 Dispense_EnumUsers(sock);
136                                 return 0;
137                         }
138                         
139                         // argv[i+1]: Username
140                         
141                         // Alter account?
142                         if( i + 2 < argc )
143                         {       
144                                 if( i + 3 >= argc ) {
145                                         fprintf(stderr, "Error: `dispense acct' needs a reason\n");
146                                         exit(1);
147                                 }
148                                 
149                                 // Authentication required
150                                 Authenticate(sock);
151                                 
152                                 // argv[i+1]: Username
153                                 // argv[i+2]: Ammount
154                                 // argv[i+3]: Reason
155                                 
156                                 if( argv[i+2][0] == '=' ) {
157                                         // Set balance
158                                         Dispense_SetBalance(sock, argv[i+1], atoi(argv[i+2] + 1), argv[i+3]);
159                                 }
160                                 else {
161                                         // Alter balance
162                                         Dispense_AlterBalance(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]);
163                                 }
164                         }
165                         
166                         Dispense_ShowUser(sock, argv[i+1]);
167                         
168                         close(sock);
169                         return 0;
170                 }
171                 else {
172                         // Item name / pattern
173                         break;
174                 }
175         }
176         
177         // Connect to server
178         sock = OpenConnection(gsDispenseServer, giDispensePort);
179         if( sock < 0 )  return -1;
180         
181         // Authenticate
182         Authenticate(sock);
183
184         // Get items
185         PopulateItemList(sock);
186         
187         if( gbUseNCurses )
188         {
189                 i = ShowNCursesUI();
190         }
191         else
192         {
193                 for( i = 0; i < giNumItems; i ++ ) {            
194                         printf("%2i %s\t%3i %s\n", i, gaItems[i].Ident, gaItems[i].Price, gaItems[i].Desc);
195                 }
196                 printf(" q Quit\n");
197                 for(;;)
198                 {
199                         char    *buf;
200                         
201                         i = -1;
202                         
203                         fgets(buffer, BUFSIZ, stdin);
204                         
205                         buf = trim(buffer);
206                         
207                         if( buf[0] == 'q' )     break;
208                         
209                         i = atoi(buf);
210                         
211                         if( i != 0 || buf[0] == '0' )
212                         {
213                                 if( i < 0 || i >= giNumItems ) {
214                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
215                                         continue;
216                                 }
217                                 break;
218                         }
219                 }
220         }
221         
222         // Check for a valid item ID
223         if( i >= 0 )
224                 DispenseItem(sock, i);
225
226         close(sock);
227
228         return 0;
229 }
230
231 void ShowUsage(void)
232 {
233         printf(
234                 "Usage:\n"
235                 "\tdispense\n"
236                 "\t\tShow interactive list\n"
237                 "\tdispense <item>\n"
238                 "\t\tDispense named item\n"
239                 "\tdispense give <user> <ammount> \"<reason>\"\n"
240                 "\t\tGive some of your money away\n"
241                 "\tdispense acct [<user>]\n"
242                 "\t\tShow user balances\n"
243                 "\tdispense acct <user> [+-=]<ammount> \"<reason>\"\n"
244                 "\t\tAlter a account value (Coke members only)\n"
245                 "\n"
246                 "General Options:\n"
247                 "\t-u <username>\n"
248                 "\t\tSet a different user (Coke members only)\n"
249                 "\t-h / -?\n"
250                 "\t\tShow help text\n"
251                 "\t-G\n"
252                 "\t\tUse alternate GUI\n"
253                 "\t-m <min balance>\n"
254                 "\t-M <max balance>\n"
255                 "\t\tSet the Maximum/Minimum balances shown in `dispense acct`\n"
256                 );
257 }
258
259 // -------------------
260 // --- NCurses GUI ---
261 // -------------------
262 /**
263  * \brief Render the NCurses UI
264  */
265 int ShowNCursesUI(void)
266 {
267         // TODO: ncurses interface (with separation between item classes)
268         // - Hmm... that would require standardising the item ID to be <class>:<index>
269         // Oh, why not :)
270          int    ch;
271          int    i, times;
272          int    xBase, yBase;
273         const int       displayMinWidth = 40;
274         const int       displayMinItems = 8;
275         char    *titleString = "Dispense";
276          int    itemCount = displayMinItems;
277          int    itemBase = 0;
278          int    currentItem = 0;
279          int    ret = -2;       // -2: Used for marking "no return yet"
280          
281          int    height, width;
282          
283         // Enter curses mode
284         initscr();
285         raw(); noecho();
286         
287         // Get item count
288         // - 6: randomly chosen (Need at least 3)
289         itemCount = LINES - 6;
290         if( itemCount > giNumItems )
291                 itemCount = giNumItems;
292         
293         // Get dimensions
294         height = itemCount + 3;
295         width = displayMinWidth;
296         
297         // Get positions
298         xBase = COLS/2 - width/2;
299         yBase = LINES/2 - height/2;
300         
301         for( ;; )
302         {
303                 // Header
304                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
305                 
306                 // Items
307                 for( i = 0; i < itemCount; i ++ )
308                 {
309                         move( yBase + 1 + i, xBase );
310                         
311                         if( currentItem == itemBase + i ) {
312                                 printw("| -> ");
313                         }
314                         else {
315                                 printw("|    ");
316                         }
317                         
318                         // Check for ... row
319                         // - Oh god, magic numbers!
320                         if( i == 0 && itemBase > 0 ) {
321                                 printw("   ...");
322                                 times = width-1 - 8 - 3;
323                                 while(times--)  addch(' ');
324                         }
325                         else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
326                                 printw("   ...");
327                                 times = width-1 - 8 - 3;
328                                 while(times--)  addch(' ');
329                         }
330                         // Show an item
331                         else {
332                                 ShowItemAt( yBase + 1 + i, xBase + 5, width - 7, itemBase + i);
333                                 addch(' ');
334                         }
335                         
336                         // Scrollbar (if needed)
337                         if( giNumItems > itemCount ) {
338                                 if( i == 0 ) {
339                                         addch('A');
340                                 }
341                                 else if( i == itemCount - 1 ) {
342                                         addch('V');
343                                 }
344                                 else {
345                                          int    percentage = itemBase * 100 / (giNumItems-itemCount);
346                                         if( i-1 == percentage*(itemCount-3)/100 ) {
347                                                 addch('#');
348                                         }
349                                         else {
350                                                 addch('|');
351                                         }
352                                 }
353                         }
354                         else {
355                                 addch('|');
356                         }
357                 }
358                 
359                 // Footer
360                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
361                 
362                 // Get input
363                 ch = getch();
364                 
365                 if( ch == '\x1B' ) {
366                         ch = getch();
367                         if( ch == '[' ) {
368                                 ch = getch();
369                                 
370                                 switch(ch)
371                                 {
372                                 case 'B':
373                                         //if( itemBase < giNumItems - (itemCount) )
374                                         //      itemBase ++;
375                                         if( currentItem < giNumItems - 1 )
376                                                 currentItem ++;
377                                         if( itemBase + itemCount - 1 <= currentItem && itemBase + itemCount < giNumItems )
378                                                 itemBase ++;
379                                         break;
380                                 case 'A':
381                                         //if( itemBase > 0 )
382                                         //      itemBase --;
383                                         if( currentItem > 0 )
384                                                 currentItem --;
385                                         if( itemBase + 1 > currentItem && itemBase > 0 )
386                                                 itemBase --;
387                                         break;
388                                 }
389                         }
390                         else {
391                                 
392                         }
393                 }
394                 else {
395                         switch(ch)
396                         {
397                         case '\n':
398                                 ret = currentItem;
399                                 break;
400                         case 'q':
401                                 ret = -1;       // -1: Return with no dispense
402                                 break;
403                         }
404                         
405                         // Check if the return value was changed
406                         if( ret != -2 ) break;
407                 }
408                 
409         }
410         
411         
412         // Leave
413         endwin();
414         return ret;
415 }
416
417 /**
418  * \brief Show item \a Index at (\a Col, \a Row)
419  * \note Part of the NCurses UI
420  */
421 void ShowItemAt(int Row, int Col, int Width, int Index)
422 {
423          int    _x, _y, times;
424         char    *name;
425          int    price;
426         
427         move( Row, Col );
428         
429         if( Index < 0 || Index >= giNumItems ) {
430                 name = "OOR";
431                 price = 0;
432         }
433         else {
434                 name = gaItems[Index].Desc;
435                 price = gaItems[Index].Price;
436         }
437
438         printw("%02i %s", Index, name);
439         
440         getyx(stdscr, _y, _x);
441         // Assumes max 4 digit prices
442         times = Width - 4 - (_x - Col); // TODO: Better handling for large prices
443         while(times--)  addch(' ');
444         printw("%4i", price);
445 }
446
447 /**
448  * \brief Print a three-part string at the specified position (formatted)
449  * \note NCurses UI Helper
450  * 
451  * Prints \a Left on the left of the area, \a Right on the righthand side
452  * and \a Mid in the middle of the area. These are padded with \a Pad1
453  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
454  * 
455  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
456  * and the arguments to these are read in that order.
457  */
458 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
459         const char *Mid, char Pad2, const char *Right, ...)
460 {
461          int    lLen, mLen, rLen;
462          int    times;
463         
464         va_list args;
465         
466         // Get the length of the strings
467         va_start(args, Right);
468         lLen = vsnprintf(NULL, 0, Left, args);
469         mLen = vsnprintf(NULL, 0, Mid, args);
470         rLen = vsnprintf(NULL, 0, Right, args);
471         va_end(args);
472         
473         // Sanity check
474         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
475                 return ;        // TODO: What to do?
476         }
477         
478         move(Row, Col);
479         
480         // Render strings
481         va_start(args, Right);
482         // - Left
483         {
484                 char    tmp[lLen+1];
485                 vsnprintf(tmp, lLen+1, Left, args);
486                 addstr(tmp);
487         }
488         // - Left padding
489         times = Width/2 - mLen/2 - lLen;
490         while(times--)  addch(Pad1);
491         // - Middle
492         {
493                 char    tmp[mLen+1];
494                 vsnprintf(tmp, mLen+1, Mid, args);
495                 addstr(tmp);
496         }
497         // - Right Padding
498         times = Width/2 - mLen/2 - rLen;
499         while(times--)  addch(Pad2);
500         // - Right
501         {
502                 char    tmp[rLen+1];
503                 vsnprintf(tmp, rLen+1, Right, args);
504                 addstr(tmp);
505         }
506 }
507
508 // ---------------------
509 // --- Coke Protocol ---
510 // ---------------------
511 int OpenConnection(const char *Host, int Port)
512 {
513         struct hostent  *host;
514         struct sockaddr_in      serverAddr;
515          int    sock;
516         
517         host = gethostbyname(Host);
518         if( !host ) {
519                 fprintf(stderr, "Unable to look up '%s'\n", Host);
520                 return -1;
521         }
522         
523         memset(&serverAddr, 0, sizeof(serverAddr));
524         
525         serverAddr.sin_family = AF_INET;        // IPv4
526         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
527         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
528         serverAddr.sin_port = htons(Port);
529         
530         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
531         if( sock < 0 ) {
532                 fprintf(stderr, "Failed to create socket\n");
533                 return -1;
534         }
535         
536         #if USE_AUTOAUTH
537         {
538                 struct sockaddr_in      localAddr;
539                 memset(&localAddr, 0, sizeof(localAddr));
540                 localAddr.sin_family = AF_INET; // IPv4
541                 localAddr.sin_port = 1023;      // IPv4
542                 // Attempt to bind to low port for autoauth
543                 bind(sock, &localAddr, sizeof(localAddr));
544         }
545         #endif
546         
547         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
548                 fprintf(stderr, "Failed to connect to server\n");
549                 return -1;
550         }
551         
552         return sock;
553 }
554
555 /**
556  * \brief Authenticate with the server
557  * \return Boolean Failure
558  */
559 int Authenticate(int Socket)
560 {
561         struct passwd   *pwd;
562         char    *buf;
563          int    responseCode;
564         char    salt[32];
565          int    i;
566         regmatch_t      matches[4];
567         
568         if( gbIsAuthenticated ) return 0;
569         
570         // Get user name
571         pwd = getpwuid( getuid() );
572         
573         // Attempt automatic authentication
574         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
575         
576         // Check if it worked
577         buf = ReadLine(Socket);
578         
579         responseCode = atoi(buf);
580         switch( responseCode )
581         {
582         
583         case 200:       // Authenticated, return :)
584                 gbIsAuthenticated = 1;
585                 free(buf);
586                 return 0;
587         
588         case 401:       // Untrusted, attempt password authentication
589                 free(buf);
590                 
591                 sendf(Socket, "USER %s\n", pwd->pw_name);
592                 printf("Using username %s\n", pwd->pw_name);
593                 
594                 buf = ReadLine(Socket);
595                 
596                 // TODO: Get Salt
597                 // Expected format: 100 SALT <something> ...
598                 // OR             : 100 User Set
599                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
600                 responseCode = atoi(buf);
601                 if( responseCode != 100 ) {
602                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
603                         free(buf);
604                         return -1;      // ERROR
605                 }
606                 
607                 // Check for salt
608                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
609                         // Store it for later
610                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
611                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
612                 }
613                 free(buf);
614                 
615                 // Give three attempts
616                 for( i = 0; i < 3; i ++ )
617                 {
618                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
619                         char    tmpBuf[42];
620                         char    tmp[ofs+20];
621                         char    *pass = getpass("Password: ");
622                         uint8_t h[20];
623                         
624                         // Create hash string
625                         // <username><salt><hash>
626                         strcpy(tmp, pwd->pw_name);
627                         strcat(tmp, salt);
628                         SHA1( (unsigned char*)pass, strlen(pass), h );
629                         memcpy(tmp+ofs, h, 20);
630                         
631                         // Hash all that
632                         SHA1( (unsigned char*)tmp, ofs+20, h );
633                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
634                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
635                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
636                                 );
637                 
638                         // Send password
639                         sendf(Socket, "PASS %s\n", tmpBuf);
640                         buf = ReadLine(Socket);
641                 
642                         responseCode = atoi(buf);
643                         // Auth OK?
644                         if( responseCode == 200 )       break;
645                         // Bad username/password
646                         if( responseCode == 401 )       continue;
647                         
648                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
649                         free(buf);
650                         return -1;
651                 }
652                 free(buf);
653                 if( i < 3 ) {
654                         gbIsAuthenticated = 1;
655                         return 0;
656                 }
657                 else
658                         return 2;       // 2 = Bad Password
659         
660         case 404:       // Bad Username
661                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
662                 free(buf);
663                 return 1;
664         
665         default:
666                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
667                 printf("%s\n", buf);
668                 free(buf);
669                 return -1;
670         }
671 }
672
673
674 /**
675  * \brief Fill the item information structure
676  * \return Boolean Failure
677  */
678 void PopulateItemList(int Socket)
679 {
680         char    *buf;
681          int    responseCode;
682         
683         char    *itemType, *itemStart;
684          int    count, i;
685         regmatch_t      matches[4];
686         
687         // Ask server for stock list
688         send(Socket, "ENUM_ITEMS\n", 11, 0);
689         buf = ReadLine(Socket);
690         
691         //printf("Output: %s\n", buf);
692         
693         responseCode = atoi(buf);
694         if( responseCode != 201 ) {
695                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
696                 exit(-1);
697         }
698         
699         // - Get item list -
700         
701         // Expected format:
702         //  201 Items <count>
703         //  202 Item <count>
704         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
705                 
706         itemType = &buf[ matches[2].rm_so ];    buf[ matches[2].rm_eo ] = '\0';
707         count = atoi( &buf[ matches[3].rm_so ] );
708                 
709         // Check array type
710         if( strcmp(itemType, "Items") != 0 ) {
711                 // What the?!
712                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
713                         itemType);
714                 exit(-1);
715         }
716                 
717         itemStart = &buf[ matches[3].rm_eo ];
718         
719         free(buf);
720         
721         giNumItems = count;
722         gaItems = malloc( giNumItems * sizeof(tItem) );
723         
724         // Fetch item information
725         for( i = 0; i < giNumItems; i ++ )
726         {
727                 regmatch_t      matches[6];
728                 
729                 // Get item info
730                 buf = ReadLine(Socket);
731                 responseCode = atoi(buf);
732                 
733                 if( responseCode != 202 ) {
734                         fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
735                         exit(-1);
736                 }
737                 
738                 RunRegex(&gItemRegex, buf, 6, matches, "Malformed server response");
739                 
740                 buf[ matches[3].rm_eo ] = '\0';
741                 
742                 gaItems[i].Ident = strdup( buf + matches[3].rm_so );
743                 gaItems[i].Price = atoi( buf + matches[4].rm_so );
744                 gaItems[i].Desc = strdup( buf + matches[5].rm_so );
745                 
746                 free(buf);
747         }
748         
749         // Read end of list
750         buf = ReadLine(Socket);
751         responseCode = atoi(buf);
752                 
753         if( responseCode != 200 ) {
754                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
755                         responseCode, buf
756                         );
757                 exit(-1);
758         }
759         
760         free(buf);
761 }
762
763 /**
764  * \brief Dispense an item
765  * \return Boolean Failure
766  */
767 int DispenseItem(int Socket, int ItemID)
768 {
769          int    ret, responseCode;
770         char    *buf;
771         
772         if( ItemID < 0 || ItemID > giNumItems ) return -1;
773         
774         // Dispense!
775         sendf(Socket, "DISPENSE %s\n", gaItems[ItemID].Ident);
776         buf = ReadLine(Socket);
777         
778         responseCode = atoi(buf);
779         switch( responseCode )
780         {
781         case 200:
782                 printf("Dispense OK\n");
783                 ret = 0;
784                 break;
785         case 401:
786                 printf("Not authenticated\n");
787                 ret = 1;
788                 break;
789         case 402:
790                 printf("Insufficient balance\n");
791                 ret = 1;
792                 break;
793         case 406:
794                 printf("Bad item name, bug report\n");
795                 ret = 1;
796                 break;
797         case 500:
798                 printf("Item failed to dispense, is the slot empty?\n");
799                 ret = 1;
800                 break;
801         case 501:
802                 printf("Dispense not possible (slot empty/permissions)\n");
803                 ret = 1;
804                 break;
805         default:
806                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
807                 ret = -2;
808                 break;
809         }
810         
811         free(buf);
812         return ret;
813 }
814
815 /**
816  * \brief Alter a user's balance
817  */
818 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
819 {
820         char    *buf;
821          int    responseCode;
822         
823         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
824         buf = ReadLine(Socket);
825         
826         responseCode = atoi(buf);
827         free(buf);
828         
829         switch(responseCode)
830         {
831         case 200:       return 0;       // OK
832         case 403:       // Not in coke
833                 fprintf(stderr, "You are not in coke (sucker)\n");
834                 return 1;
835         case 404:       // Unknown user
836                 fprintf(stderr, "Unknown user '%s'\n", Username);
837                 return 2;
838         default:
839                 fprintf(stderr, "Unknown response code %i\n", responseCode);
840                 return -1;
841         }
842         
843         return -1;
844 }
845
846 /**
847  * \brief Alter a user's balance
848  */
849 int Dispense_SetBalance(int Socket, const char *Username, int Ammount, const char *Reason)
850 {
851         char    *buf;
852          int    responseCode;
853         
854         sendf(Socket, "SET %s %i %s\n", Username, Ammount, Reason);
855         buf = ReadLine(Socket);
856         
857         responseCode = atoi(buf);
858         free(buf);
859         
860         switch(responseCode)
861         {
862         case 200:       return 0;       // OK
863         case 403:       // Not in coke
864                 fprintf(stderr, "You are not in coke (sucker)\n");
865                 return 1;
866         case 404:       // Unknown user
867                 fprintf(stderr, "Unknown user '%s'\n", Username);
868                 return 2;
869         default:
870                 fprintf(stderr, "Unknown response code %i\n", responseCode);
871                 return -1;
872         }
873         
874         return -1;
875 }
876
877 int Dispense_EnumUsers(int Socket)
878 {
879         char    *buf;
880          int    responseCode;
881          int    nUsers;
882         regmatch_t      matches[4];
883         
884         if( giMinimumBalance != INT_MIN ) {
885                 if( giMaximumBalance != INT_MAX ) {
886                         sendf(Socket, "ENUM_USERS %i %i\n", giMinimumBalance, giMaximumBalance);
887                 }
888                 else {
889                         sendf(Socket, "ENUM_USERS %i\n", giMinimumBalance);
890                 }
891         }
892         else {
893                 if( giMaximumBalance != INT_MAX ) {
894                         sendf(Socket, "ENUM_USERS - %i\n", giMaximumBalance);
895                 }
896                 else {
897                         sendf(Socket, "ENUM_USERS\n");
898                 }
899         }
900         buf = ReadLine(Socket);
901         responseCode = atoi(buf);
902         
903         switch(responseCode)
904         {
905         case 201:       break;  // Ok, length follows
906         
907         default:
908                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
909                 free(buf);
910                 return -1;
911         }
912         
913         // Get count (not actually used)
914         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
915         nUsers = atoi( buf + matches[3].rm_so );
916         printf("%i users returned\n", nUsers);
917         
918         // Free string
919         free(buf);
920         
921         // Read returned users
922         do {
923                 buf = ReadLine(Socket);
924                 responseCode = atoi(buf);
925                 
926                 if( responseCode != 202 )       break;
927                 
928                 _PrintUserLine(buf);
929                 free(buf);
930         } while(responseCode == 202);
931         
932         // Check final response
933         if( responseCode != 200 ) {
934                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
935                 free(buf);
936                 return -1;
937         }
938         
939         free(buf);
940         
941         return 0;
942 }
943
944 int Dispense_ShowUser(int Socket, const char *Username)
945 {
946         char    *buf;
947          int    responseCode, ret;
948         
949         sendf(Socket, "USER_INFO %s\n", Username);
950         buf = ReadLine(Socket);
951         
952         responseCode = atoi(buf);
953         
954         switch(responseCode)
955         {
956         case 202:
957                 _PrintUserLine(buf);
958                 ret = 0;
959                 break;
960         
961         case 404:
962                 printf("Unknown user '%s'\n", Username);
963                 ret = 1;
964                 break;
965         
966         default:
967                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
968                 ret = -1;
969                 break;
970         }
971         
972         free(buf);
973         
974         return ret;
975 }
976
977 void _PrintUserLine(const char *Line)
978 {
979         regmatch_t      matches[6];
980          int    bal;
981         
982         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
983         // 3: Username
984         // 4: Balance
985         // 5: Flags
986         {
987                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
988                 char    username[usernameLen + 1];
989                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
990                 char    flags[flagsLen + 1];
991                 
992                 memcpy(username, Line + matches[3].rm_so, usernameLen);
993                 username[usernameLen] = '\0';
994                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
995                 flags[flagsLen] = '\0';
996                 
997                 bal = atoi(Line + matches[4].rm_so);
998                 printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, bal%100, flags);
999         }
1000 }
1001
1002 // ---------------
1003 // --- Helpers ---
1004 // ---------------
1005 char *ReadLine(int Socket)
1006 {
1007         static char     buf[BUFSIZ];
1008         static int      bufPos = 0;
1009         static int      bufValid = 0;
1010          int    len;
1011         char    *newline = NULL;
1012          int    retLen = 0;
1013         char    *ret = malloc(10);
1014         
1015         #if DEBUG_TRACE_SERVER
1016         printf("ReadLine: ");
1017         #endif
1018         fflush(stdout);
1019         
1020         ret[0] = '\0';
1021         
1022         while( !newline )
1023         {
1024                 if( bufValid ) {
1025                         len = bufValid;
1026                 }
1027                 else {
1028                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1029                         buf[bufPos+len] = '\0';
1030                 }
1031                 
1032                 newline = strchr( buf+bufPos, '\n' );
1033                 if( newline ) {
1034                         *newline = '\0';
1035                 }
1036                 
1037                 retLen += strlen(buf+bufPos);
1038                 ret = realloc(ret, retLen + 1);
1039                 strcat( ret, buf+bufPos );
1040                 
1041                 if( newline ) {
1042                          int    newLen = newline - (buf+bufPos) + 1;
1043                         bufValid = len - newLen;
1044                         bufPos += newLen;
1045                 }
1046                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
1047         }
1048         
1049         #if DEBUG_TRACE_SERVER
1050         printf("%i '%s'\n", retLen, ret);
1051         #endif
1052         
1053         return ret;
1054 }
1055
1056 int sendf(int Socket, const char *Format, ...)
1057 {
1058         va_list args;
1059          int    len;
1060         
1061         va_start(args, Format);
1062         len = vsnprintf(NULL, 0, Format, args);
1063         va_end(args);
1064         
1065         {
1066                 char    buf[len+1];
1067                 va_start(args, Format);
1068                 vsnprintf(buf, len+1, Format, args);
1069                 va_end(args);
1070                 
1071                 #if DEBUG_TRACE_SERVER
1072                 printf("sendf: %s", buf);
1073                 #endif
1074                 
1075                 return send(Socket, buf, len, 0);
1076         }
1077 }
1078
1079 char *trim(char *string)
1080 {
1081          int    i;
1082         
1083         while( isspace(*string) )
1084                 string ++;
1085         
1086         for( i = strlen(string); i--; )
1087         {
1088                 if( isspace(string[i]) )
1089                         string[i] = '\0';
1090                 else
1091                         break;
1092         }
1093         
1094         return string;
1095 }
1096
1097 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
1098 {
1099          int    ret;
1100         
1101         ret = regexec(regex, string, nMatches, matches, 0);
1102         if( ret ) {
1103                 size_t  len = regerror(ret, regex, NULL, 0);
1104                 char    errorStr[len];
1105                 regerror(ret, regex, errorStr, len);
1106                 printf("string = '%s'\n", string);
1107                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
1108                 exit(-1);
1109         }
1110         
1111         return ret;
1112 }
1113
1114 void CompileRegex(regex_t *regex, const char *pattern, int flags)
1115 {
1116          int    ret = regcomp(regex, pattern, flags);
1117         if( ret ) {
1118                 size_t  len = regerror(ret, regex, NULL, 0);
1119                 char    errorStr[len];
1120                 regerror(ret, regex, errorStr, len);
1121                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
1122                 exit(-1);
1123         }
1124 }

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