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

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