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

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