Removed debug from server, cleaning 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
19 #include <unistd.h>     // close
20 #include <netdb.h>      // gethostbyname
21 #include <pwd.h>        // getpwuids
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <openssl/sha.h>        // SHA1
26
27 #define USE_NCURSES_INTERFACE   0
28
29 // === TYPES ===
30 typedef struct sItem {
31         char    *Ident;
32         char    *Desc;
33          int    Price;
34 }       tItem;
35
36 // === PROTOTYPES ===
37  int    main(int argc, char *argv[]);
38 void    ShowUsage(void);
39 // --- GUI ---
40  int    ShowNCursesUI(void);
41 void    ShowItemAt(int Row, int Col, int Width, int Index);
42 void    PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
43 // --- Coke Server Communication ---
44  int    OpenConnection(const char *Host, int Port);
45  int    Authenticate(int Socket);
46 void    PopulateItemList(int Socket);
47  int    DispenseItem(int Socket, int ItemID);
48  int    Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
49  int    Dispense_SetBalance(int Socket, const char *Username, int Ammount, const char *Reason);
50  int    Dispense_EnumUsers(int Socket);
51  int    Dispense_ShowUser(int Socket, const char *Username);
52 void    _PrintUserLine(const char *Line);
53 // --- Helpers ---
54 char    *ReadLine(int Socket);
55  int    sendf(int Socket, const char *Format, ...);
56 char    *trim(char *string);
57  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
58 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
59
60 // === GLOBALS ===
61 char    *gsDispenseServer = "localhost";
62  int    giDispensePort = 11020;
63
64 tItem   *gaItems;
65  int    giNumItems;
66 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex;
67  int    gbIsAuthenticated = 0;
68
69 char    *gsOverrideUser;        //!< '-u' argument (dispense as another user)
70  int    gbUseNCurses = 0;       //!< '-G' Use the NCurses GUI?
71  int    giSocket = -1;
72
73 // === CODE ===
74 int main(int argc, char *argv[])
75 {
76          int    sock;
77          int    i;
78         char    buffer[BUFSIZ];
79         
80         // -- Create regular expressions
81         // > Code Type Count ...
82         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
83         // > Code Type Ident Price Desc
84         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z0-9:]+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
85         // > Code 'SALT' salt
86         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
87         // > Code 'User' Username Balance Flags
88         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
89
90         // Parse Arguments
91         for( i = 1; i < argc; i ++ )
92         {
93                 char    *arg = argv[i];
94                 
95                 if( arg[0] == '-' )
96                 {                       
97                         switch(arg[1])
98                         {
99                         case 'h':
100                         case '?':
101                                 ShowUsage();
102                                 return 0;
103                         
104                         case 'u':       // Override User
105                                 gsOverrideUser = argv[++i];
106                                 break;
107                         
108                         case 'G':       // Use GUI
109                                 gbUseNCurses = 1;
110                                 break;
111                         }
112
113                         continue;
114                 }
115                 
116                 if( strcmp(arg, "acct") == 0 )
117                 {
118
119                         // Connect to server
120                         sock = OpenConnection(gsDispenseServer, giDispensePort);
121                         if( sock < 0 )  return -1;
122
123                         // List accounts?
124                         if( i + 1 == argc ) {
125                                 Dispense_EnumUsers(sock);
126                                 return 0;
127                         }
128                         
129                         // argv[i+1]: Username
130                         
131                         // Alter account?
132                         if( i + 2 < argc ) {
133                                 
134                                 if( i + 3 >= argc ) {
135                                         fprintf(stderr, "Error: `dispense acct' needs a reason\n");
136                                         exit(1);
137                                 }
138                                 
139                                 // Authentication required
140                                 Authenticate(sock);
141                                 
142                                 // argv[i+1]: Username
143                                 // argv[i+2]: Ammount
144                                 // argv[i+3]: Reason
145                                 
146                                 if( argv[i+2][0] == '=' ) {
147                                         // Set balance
148                                         Dispense_SetBalance(sock, argv[i+1], atoi(argv[i+2] + 1), argv[i+3]);
149                                 }
150                                 else {
151                                         // Alter balance
152                                         Dispense_AlterBalance(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]);
153                                 }
154                         }
155                         
156                         Dispense_ShowUser(sock, argv[i+1]);
157                         
158                         close(sock);
159                         return 0;
160                 }
161                 else {
162                         // Item name / pattern
163                         break;
164                 }
165         }
166         
167         // Connect to server
168         sock = OpenConnection(gsDispenseServer, giDispensePort);
169         if( sock < 0 )  return -1;
170         
171         // Authenticate
172         Authenticate(sock);
173
174         // Get items
175         PopulateItemList(sock);
176         
177         if( gbUseNCurses )
178         {
179                 i = ShowNCursesUI();
180         }
181         else
182         {
183                 for( i = 0; i < giNumItems; i ++ ) {            
184                         printf("%2i %s\t%3i %s\n", i, gaItems[i].Ident, gaItems[i].Price, gaItems[i].Desc);
185                 }
186                 printf(" q Quit\n");
187                 for(;;)
188                 {
189                         char    *buf;
190                         
191                         i = -1;
192                         
193                         fgets(buffer, BUFSIZ, stdin);
194                         
195                         buf = trim(buffer);
196                         
197                         if( buf[0] == 'q' )     break;
198                         
199                         i = atoi(buf);
200                         
201                         if( i != 0 || buf[0] == '0' )
202                         {
203                                 if( i < 0 || i >= giNumItems ) {
204                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
205                                         continue;
206                                 }
207                                 break;
208                         }
209                 }
210         }
211         
212         // Check for a valid item ID
213         if( i >= 0 )
214                 DispenseItem(sock, i);
215
216         close(sock);
217
218         return 0;
219 }
220
221 void ShowUsage(void)
222 {
223         printf(
224                 "Usage:\n"
225                 "\tdispense\n"
226                 "\t\tShow interactive list\n"
227                 "\tdispense <item>\n"
228                 "\t\tDispense named item\n"
229                 "\tdispense give <user> <ammount> \"<reason>\"\n"
230                 "\t\tGive some of your money away\n"
231                 "\tdispense acct [<user>]\n"
232                 "\t\tShow user balances\n"
233                 "\tdispense acct <user> [+-=]<ammount> \"<reason>\"\n"
234                 "\t\tAlter a account value (Coke members only)\n"
235                 "\n"
236                 "General Options:\n"
237                 "\t-u <username>\n"
238                 "\t\tSet a different user (Coke members only)\n"
239                 "\t-h / -?\n"
240                 "\t\tShow help text\n"
241                 "\t-G\n"
242                 "\t\tUse alternate GUI\n"
243                 );
244 }
245
246 // -------------------
247 // --- NCurses GUI ---
248 // -------------------
249 /**
250  * \brief Render the NCurses UI
251  */
252 int ShowNCursesUI(void)
253 {
254         // TODO: ncurses interface (with separation between item classes)
255         // - Hmm... that would require standardising the item ID to be <class>:<index>
256         // Oh, why not :)
257          int    ch;
258          int    i, times;
259          int    xBase, yBase;
260         const int       displayMinWidth = 40;
261         const int       displayMinItems = 8;
262         char    *titleString = "Dispense";
263          int    itemCount = displayMinItems;
264          int    itemBase = 0;
265          int    currentItem = 0;
266          int    ret = -2;       // -2: Used for marking "no return yet"
267          
268          int    height, width;
269          
270         // Enter curses mode
271         initscr();
272         raw(); noecho();
273         
274         // Get item count
275         // - 6: randomly chosen (Need at least 3)
276         itemCount = LINES - 6;
277         if( itemCount > giNumItems )
278                 itemCount = giNumItems;
279         
280         // Get dimensions
281         height = itemCount + 3;
282         width = displayMinWidth;
283         
284         // Get positions
285         xBase = COLS/2 - width/2;
286         yBase = LINES/2 - height/2;
287         
288         for( ;; )
289         {
290                 // Header
291                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
292                 
293                 // Items
294                 for( i = 0; i < itemCount; i ++ )
295                 {
296                         move( yBase + 1 + i, xBase );
297                         
298                         if( currentItem == itemBase + i ) {
299                                 printw("| -> ");
300                         }
301                         else {
302                                 printw("|    ");
303                         }
304                         
305                         // Check for ... row
306                         // - Oh god, magic numbers!
307                         if( i == 0 && itemBase > 0 ) {
308                                 printw("   ...");
309                                 times = width-1 - 8 - 3;
310                                 while(times--)  addch(' ');
311                         }
312                         else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
313                                 printw("   ...");
314                                 times = width-1 - 8 - 3;
315                                 while(times--)  addch(' ');
316                         }
317                         // Show an item
318                         else {
319                                 ShowItemAt( yBase + 1 + i, xBase + 5, width - 7, itemBase + i);
320                                 addch(' ');
321                         }
322                         
323                         // Scrollbar (if needed)
324                         if( giNumItems > itemCount ) {
325                                 if( i == 0 ) {
326                                         addch('A');
327                                 }
328                                 else if( i == itemCount - 1 ) {
329                                         addch('V');
330                                 }
331                                 else {
332                                          int    percentage = itemBase * 100 / (giNumItems-itemCount);
333                                         if( i-1 == percentage*(itemCount-3)/100 ) {
334                                                 addch('#');
335                                         }
336                                         else {
337                                                 addch('|');
338                                         }
339                                 }
340                         }
341                         else {
342                                 addch('|');
343                         }
344                 }
345                 
346                 // Footer
347                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
348                 
349                 // Get input
350                 ch = getch();
351                 
352                 if( ch == '\x1B' ) {
353                         ch = getch();
354                         if( ch == '[' ) {
355                                 ch = getch();
356                                 
357                                 switch(ch)
358                                 {
359                                 case 'B':
360                                         //if( itemBase < giNumItems - (itemCount) )
361                                         //      itemBase ++;
362                                         if( currentItem < giNumItems - 1 )
363                                                 currentItem ++;
364                                         if( itemBase + itemCount - 1 <= currentItem && itemBase + itemCount < giNumItems )
365                                                 itemBase ++;
366                                         break;
367                                 case 'A':
368                                         //if( itemBase > 0 )
369                                         //      itemBase --;
370                                         if( currentItem > 0 )
371                                                 currentItem --;
372                                         if( itemBase + 1 > currentItem && itemBase > 0 )
373                                                 itemBase --;
374                                         break;
375                                 }
376                         }
377                         else {
378                                 
379                         }
380                 }
381                 else {
382                         switch(ch)
383                         {
384                         case '\n':
385                                 ret = currentItem;
386                                 break;
387                         case 'q':
388                                 ret = -1;       // -1: Return with no dispense
389                                 break;
390                         }
391                         
392                         // Check if the return value was changed
393                         if( ret != -2 ) break;
394                 }
395                 
396         }
397         
398         
399         // Leave
400         endwin();
401         return ret;
402 }
403
404 /**
405  * \brief Show item \a Index at (\a Col, \a Row)
406  * \note Part of the NCurses UI
407  */
408 void ShowItemAt(int Row, int Col, int Width, int Index)
409 {
410          int    _x, _y, times;
411         char    *name;
412          int    price;
413         
414         move( Row, Col );
415         
416         if( Index < 0 || Index >= giNumItems ) {
417                 name = "OOR";
418                 price = 0;
419         }
420         else {
421                 name = gaItems[Index].Desc;
422                 price = gaItems[Index].Price;
423         }
424
425         printw("%02i %s", Index, name);
426         
427         getyx(stdscr, _y, _x);
428         // Assumes max 4 digit prices
429         times = Width - 4 - (_x - Col); // TODO: Better handling for large prices
430         while(times--)  addch(' ');
431         printw("%4i", price);
432 }
433
434 /**
435  * \brief Print a three-part string at the specified position (formatted)
436  * \note NCurses UI Helper
437  * 
438  * Prints \a Left on the left of the area, \a Right on the righthand side
439  * and \a Mid in the middle of the area. These are padded with \a Pad1
440  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
441  * 
442  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
443  * and the arguments to these are read in that order.
444  */
445 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
446         const char *Mid, char Pad2, const char *Right, ...)
447 {
448          int    lLen, mLen, rLen;
449          int    times;
450         
451         va_list args;
452         
453         // Get the length of the strings
454         va_start(args, Right);
455         lLen = vsnprintf(NULL, 0, Left, args);
456         mLen = vsnprintf(NULL, 0, Mid, args);
457         rLen = vsnprintf(NULL, 0, Right, args);
458         va_end(args);
459         
460         // Sanity check
461         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
462                 return ;        // TODO: What to do?
463         }
464         
465         move(Row, Col);
466         
467         // Render strings
468         va_start(args, Right);
469         // - Left
470         {
471                 char    tmp[lLen+1];
472                 vsnprintf(tmp, lLen+1, Left, args);
473                 addstr(tmp);
474         }
475         // - Left padding
476         times = Width/2 - mLen/2 - lLen;
477         while(times--)  addch(Pad1);
478         // - Middle
479         {
480                 char    tmp[mLen+1];
481                 vsnprintf(tmp, mLen+1, Mid, args);
482                 addstr(tmp);
483         }
484         // - Right Padding
485         times = Width/2 - mLen/2 - rLen;
486         while(times--)  addch(Pad2);
487         // - Right
488         {
489                 char    tmp[rLen+1];
490                 vsnprintf(tmp, rLen+1, Right, args);
491                 addstr(tmp);
492         }
493 }
494
495 // ---------------------
496 // --- Coke Protocol ---
497 // ---------------------
498 int OpenConnection(const char *Host, int Port)
499 {
500         struct hostent  *host;
501         struct sockaddr_in      serverAddr;
502          int    sock;
503         
504         host = gethostbyname(Host);
505         if( !host ) {
506                 fprintf(stderr, "Unable to look up '%s'\n", Host);
507                 return -1;
508         }
509         
510         memset(&serverAddr, 0, sizeof(serverAddr));
511         
512         serverAddr.sin_family = AF_INET;        // IPv4
513         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
514         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
515         serverAddr.sin_port = htons(Port);
516         
517         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
518         if( sock < 0 ) {
519                 fprintf(stderr, "Failed to create socket\n");
520                 return -1;
521         }
522         
523         #if USE_AUTOAUTH
524         {
525                 struct sockaddr_in      localAddr;
526                 memset(&localAddr, 0, sizeof(localAddr));
527                 localAddr.sin_family = AF_INET; // IPv4
528                 localAddr.sin_port = 1023;      // IPv4
529                 // Attempt to bind to low port for autoauth
530                 bind(sock, &localAddr, sizeof(localAddr));
531         }
532         #endif
533         
534         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
535                 fprintf(stderr, "Failed to connect to server\n");
536                 return -1;
537         }
538         
539         return sock;
540 }
541
542 /**
543  * \brief Authenticate with the server
544  * \return Boolean Failure
545  */
546 int Authenticate(int Socket)
547 {
548         struct passwd   *pwd;
549         char    *buf;
550          int    responseCode;
551         char    salt[32];
552          int    i;
553         regmatch_t      matches[4];
554         
555         if( gbIsAuthenticated ) return 0;
556         
557         // Get user name
558         pwd = getpwuid( getuid() );
559         
560         // Attempt automatic authentication
561         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
562         
563         // Check if it worked
564         buf = ReadLine(Socket);
565         
566         responseCode = atoi(buf);
567         switch( responseCode )
568         {
569         
570         case 200:       // Authenticated, return :)
571                 gbIsAuthenticated = 1;
572                 free(buf);
573                 return 0;
574         
575         case 401:       // Untrusted, attempt password authentication
576                 free(buf);
577                 
578                 sendf(Socket, "USER %s\n", pwd->pw_name);
579                 printf("Using username %s\n", pwd->pw_name);
580                 
581                 buf = ReadLine(Socket);
582                 
583                 // TODO: Get Salt
584                 // Expected format: 100 SALT <something> ...
585                 // OR             : 100 User Set
586                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
587                 responseCode = atoi(buf);
588                 if( responseCode != 100 ) {
589                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
590                         free(buf);
591                         return -1;      // ERROR
592                 }
593                 
594                 // Check for salt
595                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
596                         // Store it for later
597                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
598                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
599                 }
600                 free(buf);
601                 
602                 // Give three attempts
603                 for( i = 0; i < 3; i ++ )
604                 {
605                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
606                         char    tmpBuf[42];
607                         char    tmp[ofs+20];
608                         char    *pass = getpass("Password: ");
609                         uint8_t h[20];
610                         
611                         // Create hash string
612                         // <username><salt><hash>
613                         strcpy(tmp, pwd->pw_name);
614                         strcat(tmp, salt);
615                         SHA1( (unsigned char*)pass, strlen(pass), h );
616                         memcpy(tmp+ofs, h, 20);
617                         
618                         // Hash all that
619                         SHA1( (unsigned char*)tmp, ofs+20, h );
620                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
621                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
622                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
623                                 );
624                 
625                         // Send password
626                         sendf(Socket, "PASS %s\n", tmpBuf);
627                         buf = ReadLine(Socket);
628                 
629                         responseCode = atoi(buf);
630                         // Auth OK?
631                         if( responseCode == 200 )       break;
632                         // Bad username/password
633                         if( responseCode == 401 )       continue;
634                         
635                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
636                         free(buf);
637                         return -1;
638                 }
639                 free(buf);
640                 if( i < 3 ) {
641                         gbIsAuthenticated = 1;
642                         return 0;
643                 }
644                 else
645                         return 2;       // 2 = Bad Password
646         
647         case 404:       // Bad Username
648                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
649                 free(buf);
650                 return 1;
651         
652         default:
653                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
654                 printf("%s\n", buf);
655                 free(buf);
656                 return -1;
657         }
658 }
659
660
661 /**
662  * \brief Fill the item information structure
663  * \return Boolean Failure
664  */
665 void PopulateItemList(int Socket)
666 {
667         char    *buf;
668          int    responseCode;
669         
670         char    *itemType, *itemStart;
671          int    count, i;
672         regmatch_t      matches[4];
673         
674         // Ask server for stock list
675         send(Socket, "ENUM_ITEMS\n", 11, 0);
676         buf = ReadLine(Socket);
677         
678         //printf("Output: %s\n", buf);
679         
680         responseCode = atoi(buf);
681         if( responseCode != 201 ) {
682                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
683                 exit(-1);
684         }
685         
686         // - Get item list -
687         
688         // Expected format: 201 Items <count> <item1> <item2> ...
689         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
690                 
691         itemType = &buf[ matches[2].rm_so ];    buf[ matches[2].rm_eo ] = '\0';
692         count = atoi( &buf[ matches[3].rm_so ] );
693                 
694         // Check array type
695         if( strcmp(itemType, "Items") != 0 ) {
696                 // What the?!
697                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
698                         itemType);
699                 exit(-1);
700         }
701                 
702         itemStart = &buf[ matches[3].rm_eo ];
703                 
704         gaItems = malloc( count * sizeof(tItem) );
705                 
706         for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
707         {
708                 char    *next = strchr( ++itemStart, ' ' );
709                 if( next )      *next = '\0';
710                 gaItems[giNumItems].Ident = strdup(itemStart);
711                 itemStart = next;
712         }
713         
714         free(buf);
715         
716         // Fetch item information
717         for( i = 0; i < giNumItems; i ++ )
718         {
719                 regmatch_t      matches[6];
720                 
721                 // Get item info
722                 sendf(Socket, "ITEM_INFO %s\n", gaItems[i].Ident);
723                 buf = ReadLine(Socket);
724                 
725                 responseCode = atoi(buf);
726                 if( responseCode != 202 ) {
727                         fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
728                         exit(-1);
729                 }
730                 
731                 RunRegex(&gItemRegex, buf, 6, matches, "Malformed server response");
732                 
733                 buf[ matches[3].rm_eo ] = '\0';
734                 
735                 gaItems[i].Price = atoi( buf + matches[4].rm_so );
736                 gaItems[i].Desc = strdup( buf + matches[5].rm_so );
737                 
738                 free(buf);
739         }
740 }
741
742 /**
743  * \brief Dispense an item
744  * \return Boolean Failure
745  */
746 int DispenseItem(int Socket, int ItemID)
747 {
748          int    ret, responseCode;
749         char    *buf;
750         
751         if( ItemID < 0 || ItemID > giNumItems ) return -1;
752         
753         // Dispense!
754         sendf(Socket, "DISPENSE %s\n", gaItems[ItemID].Ident);
755         buf = ReadLine(Socket);
756         
757         responseCode = atoi(buf);
758         switch( responseCode )
759         {
760         case 200:
761                 printf("Dispense OK\n");
762                 ret = 0;
763                 break;
764         case 401:
765                 printf("Not authenticated\n");
766                 ret = 1;
767                 break;
768         case 402:
769                 printf("Insufficient balance\n");
770                 ret = 1;
771                 break;
772         case 406:
773                 printf("Bad item name, bug report\n");
774                 ret = 1;
775                 break;
776         case 500:
777                 printf("Item failed to dispense, is the slot empty?\n");
778                 ret = 1;
779                 break;
780         case 501:
781                 printf("Dispense not possible (slot empty/permissions)\n");
782                 ret = 1;
783                 break;
784         default:
785                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
786                 ret = -2;
787                 break;
788         }
789         
790         free(buf);
791         return ret;
792 }
793
794 /**
795  * \brief Alter a user's balance
796  */
797 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
798 {
799         char    *buf;
800          int    responseCode;
801         
802         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
803         buf = ReadLine(Socket);
804         
805         responseCode = atoi(buf);
806         free(buf);
807         
808         switch(responseCode)
809         {
810         case 200:       return 0;       // OK
811         case 403:       // Not in coke
812                 fprintf(stderr, "You are not in coke (sucker)\n");
813                 return 1;
814         case 404:       // Unknown user
815                 fprintf(stderr, "Unknown user '%s'\n", Username);
816                 return 2;
817         default:
818                 fprintf(stderr, "Unknown response code %i\n", responseCode);
819                 return -1;
820         }
821         
822         return -1;
823 }
824
825 /**
826  * \brief Alter a user's balance
827  */
828 int Dispense_SetBalance(int Socket, const char *Username, int Ammount, const char *Reason)
829 {
830         char    *buf;
831          int    responseCode;
832         
833         sendf(Socket, "SET %s %i %s\n", Username, Ammount, Reason);
834         buf = ReadLine(Socket);
835         
836         responseCode = atoi(buf);
837         free(buf);
838         
839         switch(responseCode)
840         {
841         case 200:       return 0;       // OK
842         case 403:       // Not in coke
843                 fprintf(stderr, "You are not in coke (sucker)\n");
844                 return 1;
845         case 404:       // Unknown user
846                 fprintf(stderr, "Unknown user '%s'\n", Username);
847                 return 2;
848         default:
849                 fprintf(stderr, "Unknown response code %i\n", responseCode);
850                 return -1;
851         }
852         
853         return -1;
854 }
855
856 int Dispense_EnumUsers(int Socket)
857 {
858         printf("TODO: Dispense_EnumUsers\n");
859         return -1;
860 }
861
862 int Dispense_ShowUser(int Socket, const char *Username)
863 {
864         char    *buf;
865          int    responseCode, ret;
866         
867         sendf(Socket, "USER_INFO %s\n", Username);
868         buf = ReadLine(Socket);
869         
870         responseCode = atoi(buf);
871         
872         switch(responseCode)
873         {
874         case 202:
875                 _PrintUserLine(buf);
876                 ret = 0;
877                 break;
878         
879         case 404:
880                 printf("Unknown user '%s'\n", Username);
881                 ret = 1;
882                 break;
883         
884         default:
885                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
886                 ret = -1;
887                 break;
888         }
889         
890         free(buf);
891         
892         return ret;
893 }
894
895 void _PrintUserLine(const char *Line)
896 {
897         regmatch_t      matches[6];
898          int    bal;
899         
900         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
901         // 3: Username
902         // 4: Balance
903         // 5: Flags
904         {
905                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
906                 char    username[usernameLen + 1];
907                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
908                 char    flags[flagsLen + 1];
909                 
910                 memcpy(username, Line + matches[3].rm_so, usernameLen);
911                 username[usernameLen] = '\0';
912                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
913                 flags[flagsLen] = '\0';
914                 
915                 bal = atoi(Line + matches[4].rm_so);
916                 printf("%-15s: $%i.%02i (%s)\n", username, bal/100, bal%100, flags);
917         }
918 }
919
920 // ---------------
921 // --- Helpers ---
922 // ---------------
923 char *ReadLine(int Socket)
924 {
925         static char     buf[BUFSIZ];
926         static int      bufPos = 0;
927          int    len;
928         char    *newline = NULL;
929          int    retLen = 0;
930         char    *ret = malloc(10);
931         
932         #if DBG_TRACE_SERVER
933         printf("ReadLine: ");
934         #endif
935         fflush(stdout);
936         
937         ret[0] = '\0';
938         
939         while( !newline )
940         {
941                 len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
942                 buf[bufPos+len] = '\0';
943                 
944                 newline = strchr( buf+bufPos, '\n' );
945                 if( newline ) {
946                         *newline = '\0';
947                 }
948                 
949                 retLen += strlen(buf+bufPos);
950                 ret = realloc(ret, retLen + 1);
951                 strcat( ret, buf+bufPos );
952                 
953                 if( newline ) {
954                         bufPos += newline - (buf+bufPos) + 1;
955                 }
956                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
957         }
958         
959         #if DBG_TRACE_SERVER
960         printf("%i '%s'\n", retLen, ret);
961         #endif
962         
963         return ret;
964 }
965
966 int sendf(int Socket, const char *Format, ...)
967 {
968         va_list args;
969          int    len;
970         
971         va_start(args, Format);
972         len = vsnprintf(NULL, 0, Format, args);
973         va_end(args);
974         
975         {
976                 char    buf[len+1];
977                 va_start(args, Format);
978                 vsnprintf(buf, len+1, Format, args);
979                 va_end(args);
980                 
981                 #if DBG_TRACE_SERVER
982                 printf("sendf: %s", buf);
983                 #endif
984                 
985                 return send(Socket, buf, len, 0);
986         }
987 }
988
989 char *trim(char *string)
990 {
991          int    i;
992         
993         while( isspace(*string) )
994                 string ++;
995         
996         for( i = strlen(string); i--; )
997         {
998                 if( isspace(string[i]) )
999                         string[i] = '\0';
1000                 else
1001                         break;
1002         }
1003         
1004         return string;
1005 }
1006
1007 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
1008 {
1009          int    ret;
1010         
1011         ret = regexec(regex, string, nMatches, matches, 0);
1012         if( ret ) {
1013                 size_t  len = regerror(ret, regex, NULL, 0);
1014                 char    errorStr[len];
1015                 regerror(ret, regex, errorStr, len);
1016                 printf("string = '%s'\n", string);
1017                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
1018                 exit(-1);
1019         }
1020         
1021         return ret;
1022 }
1023
1024 void CompileRegex(regex_t *regex, const char *pattern, int flags)
1025 {
1026          int    ret = regcomp(regex, pattern, flags);
1027         if( ret ) {
1028                 size_t  len = regerror(ret, regex, NULL, 0);
1029                 char    errorStr[len];
1030                 regerror(ret, regex, errorStr, len);
1031                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
1032                 exit(-1);
1033         }
1034 }

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