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

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