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

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