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

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