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

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