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

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