ad8a6bf6c2657b0c448a66a721f7396ffadef535
[tpg/opendispense2.git] / src / client / protocol.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  * - Dispense Client
5  *
6  * protocol.c
7  * - Client/Server communication
8  *
9  * This file is licenced under the 3-clause BSD Licence. See the file
10  * COPYING for full details.
11  */
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <netdb.h>      // gethostbyname
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <arpa/inet.h>
19 //#include <openssl/sha.h>      // SHA1
20 #include <pwd.h>        // getpwuids
21 #include <unistd.h>     // close/getuid
22 #include <limits.h>     // INT_MIN/INT_MAX
23 #include <stdarg.h>
24 #include "common.h"
25
26 // === PROTOTYPES ===
27 char    *ReadLine(int Socket);
28  int    sendf(int Socket, const char *Format, ...);
29
30 // ---------------------
31 // --- Coke Protocol ---
32 // ---------------------
33 int OpenConnection(const char *Host, int Port)
34 {
35         struct hostent  *host;
36         struct sockaddr_in      serverAddr;
37          int    sock;
38         
39         host = gethostbyname(Host);
40         if( !host ) {
41                 fprintf(stderr, "Unable to look up '%s'\n", Host);
42                 return -1;
43         }
44         
45         memset(&serverAddr, 0, sizeof(serverAddr));
46         
47         serverAddr.sin_family = AF_INET;        // IPv4
48         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
49         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
50         serverAddr.sin_port = htons(Port);
51         
52         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
53         if( sock < 0 ) {
54                 fprintf(stderr, "Failed to create socket\n");
55                 return -1;
56         }
57
58         if( geteuid() == 0 || getuid() == 0 )
59         {
60                  int    i;
61                 struct sockaddr_in      localAddr;
62                 memset(&localAddr, 0, sizeof(localAddr));
63                 localAddr.sin_family = AF_INET; // IPv4
64                 
65                 // Loop through all the top ports until one is avaliable
66                 for( i = 512; i < 1024; i ++)
67                 {
68                         localAddr.sin_port = htons(i);  // IPv4
69                         // Attempt to bind to low port for autoauth
70                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
71                                 break;
72                 }
73                 if( i == 1024 )
74                         printf("Warning: AUTOAUTH unavaliable\n");
75         }
76         
77         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
78                 fprintf(stderr, "Failed to connect to server\n");
79                 return -1;
80         }
81
82         // We're not authenticated if the connection has just opened
83         gbIsAuthenticated = 0;
84         
85         return sock;
86 }
87
88 int Authenticate_AutoAuth(int Socket, const char *Username)
89 {
90         char    *buf;
91          int    responseCode;
92          int    ret = -1;
93         
94         // Attempt automatic authentication
95         sendf(Socket, "AUTOAUTH %s\n", Username);
96         
97         // Check if it worked
98         buf = ReadLine(Socket);
99         
100         responseCode = atoi(buf);
101         switch( responseCode )
102         {
103         case 200:       // Autoauth succeeded, return
104                 ret = 0;
105                 break;
106         
107         case 401:       // Untrusted
108 //              fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
109                 ret = RV_PERMISSIONS;
110                 break;
111         case 404:       // Bad Username
112                 fprintf(stderr, "Bad Username '%s'\n", Username);
113                 ret = RV_INVALID_USER;
114                 break;
115         
116         default:
117                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
118                 printf("%s\n", buf);
119                 ret = RV_UNKNOWN_ERROR;
120                 break;;
121         }
122         
123         free(buf);
124         return ret;
125 }
126
127 int Authenticate_AuthIdent(int Socket)
128 {
129         char    *buf;
130          int    responseCode;
131          int    ret = -1;
132         
133         // Attempt automatic authentication
134         sendf(Socket, "AUTHIDENT\n");
135         
136         // Check if it worked
137         buf = ReadLine(Socket);
138         
139         responseCode = atoi(buf);
140         switch( responseCode )
141         {
142         case 200:       // Autoauth succeeded, return
143                 ret = 0;
144                 break;
145         
146         case 401:       // Untrusted
147 //              fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
148                 ret = RV_PERMISSIONS;
149                 break;
150         
151         default:
152                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
153                 printf("%s\n", buf);
154                 ret = RV_UNKNOWN_RESPONSE;
155                 break;
156         }
157         
158         free(buf);
159
160         return ret;
161 }
162
163 int Authenticate_Password(int Socket, const char *Username)
164 {
165         #if USE_PASSWORD_AUTH
166         char    *buf;
167          int    responseCode;   
168         char    salt[32];
169          int    i;
170         regmatch_t      matches[4];
171
172         sendf(Socket, "USER %s\n", Username);
173         printf("Using username %s\n", Username);
174         
175         buf = ReadLine(Socket);
176         
177         // TODO: Get Salt
178         // Expected format: 100 SALT <something> ...
179         // OR             : 100 User Set
180         RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
181         responseCode = atoi(buf);
182         if( responseCode != 100 ) {
183                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
184                 free(buf);
185                 return RV_UNKNOWN_ERROR;        // ERROR
186         }
187         
188         // Check for salt
189         if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
190                 // Store it for later
191                 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
192                 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
193         }
194         free(buf);
195         
196         // Give three attempts
197         for( i = 0; i < 3; i ++ )
198         {
199                  int    ofs = strlen(Username)+strlen(salt);
200                 char    tmpBuf[42];
201                 char    tmp[ofs+20];
202                 char    *pass = getpass("Password: ");
203                 uint8_t h[20];
204                 
205                 // Create hash string
206                 // <username><salt><hash>
207                 strcpy(tmp, Username);
208                 strcat(tmp, salt);
209                 SHA1( (unsigned char*)pass, strlen(pass), h );
210                 memcpy(tmp+ofs, h, 20);
211                 
212                 // Hash all that
213                 SHA1( (unsigned char*)tmp, ofs+20, h );
214                 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
215                         h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
216                         h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
217                         );
218         
219                 // Send password
220                 sendf(Socket, "PASS %s\n", tmpBuf);
221                 buf = ReadLine(Socket);
222         
223                 responseCode = atoi(buf);
224                 // Auth OK?
225                 if( responseCode == 200 )       break;
226                 // Bad username/password
227                 if( responseCode == 401 )       continue;
228                 
229                 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
230                 free(buf);
231                 return -1;
232         }
233         free(buf);
234         if( i == 3 )
235                 return RV_INVALID_USER; // 2 = Bad Password
236
237         return 0;
238         #else
239         return RV_INVALID_USER;
240         #endif
241 }
242
243 /**
244  * \brief Authenticate with the server
245  * \return Boolean Failure
246  */
247 int Authenticate(int Socket)
248 {
249         struct passwd   *pwd;
250         
251         if( gbIsAuthenticated ) return 0;
252         
253         // Get user name
254         pwd = getpwuid( getuid() );
255
256         // Attempt AUTOAUTH
257         if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
258                 ;
259         else if( Authenticate_AuthIdent(Socket) == 0 )
260                 ;
261         else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
262                 return RV_INVALID_USER;
263
264         // Set effective user
265         if( gsEffectiveUser ) {
266                 char    *buf;
267                  int    responseCode;
268                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
269                 
270                 buf = ReadLine(Socket);
271                 responseCode = atoi(buf);
272                 
273                 switch(responseCode)
274                 {
275                 case 200:
276                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
277                         break;
278                 
279                 case 403:
280                         printf("Only coke members can use `dispense -u`\n");
281                         free(buf);
282                         return RV_PERMISSIONS;
283                 
284                 case 404:
285                         printf("Invalid user selected\n");
286                         free(buf);
287                         return RV_INVALID_USER;
288                 
289                 default:
290                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
291                         printf("%s\n", buf);
292                         free(buf);
293                         return RV_UNKNOWN_ERROR;
294                 }
295                 
296                 free(buf);
297         }
298         
299         gbIsAuthenticated = 1;
300         
301         return 0;
302 }
303
304 int GetUserBalance(int Socket)
305 {
306         regmatch_t      matches[6];
307         struct passwd   *pwd;
308         char    *buf;
309          int    responseCode;
310         
311         if( !gsUserName )
312         {
313                 if( gsEffectiveUser ) {
314                         gsUserName = gsEffectiveUser;
315                 }
316                 else {
317                         pwd = getpwuid( getuid() );
318                         gsUserName = strdup(pwd->pw_name);
319                 }
320         }
321         
322         sendf(Socket, "USER_INFO %s\n", gsUserName);
323         buf = ReadLine(Socket);
324         responseCode = atoi(buf);
325         switch(responseCode)
326         {
327         case 202:       break;  // Ok
328         
329         case 404:
330                 printf("Invalid user? (USER_INFO failed)\n");
331                 free(buf);
332                 return RV_INVALID_USER;
333         
334         default:
335                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
336                 printf("%s\n", buf);
337                 free(buf);
338                 return RV_UNKNOWN_ERROR;
339         }
340
341         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
342         
343         giUserBalance = atoi( buf + matches[4].rm_so );
344         gsUserFlags = strdup( buf + matches[5].rm_so );
345         
346         free(buf);
347         
348         return 0;
349 }
350
351 /**
352  * \brief Read an item info response from the server
353  * \param Dest  Destination for the read item (strings will be on the heap)
354  */
355 int ReadItemInfo(int Socket, tItem *Dest)
356 {
357         char    *buf;
358          int    responseCode;
359         
360         regmatch_t      matches[8];
361         char    *statusStr;
362         
363         // Get item info
364         buf = ReadLine(Socket);
365         responseCode = atoi(buf);
366         
367         switch(responseCode)
368         {
369         case 202:       break;
370         
371         case 406:
372                 printf("Bad item name\n");
373                 free(buf);
374                 return RV_BAD_ITEM;
375         
376         default:
377                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
378                 free(buf);
379                 return RV_UNKNOWN_ERROR;
380         }
381         
382         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
383         
384         buf[ matches[3].rm_eo ] = '\0';
385         buf[ matches[5].rm_eo ] = '\0';
386         buf[ matches[7].rm_eo ] = '\0';
387         
388         statusStr = &buf[ matches[5].rm_so ];
389         
390         Dest->ID = atoi( buf + matches[4].rm_so );
391         
392         if( strcmp(statusStr, "avail") == 0 )
393                 Dest->Status = 0;
394         else if( strcmp(statusStr, "sold") == 0 )
395                 Dest->Status = 1;
396         else if( strcmp(statusStr, "error") == 0 )
397                 Dest->Status = -1;
398         else {
399                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
400                         statusStr);
401                 return RV_UNKNOWN_ERROR;
402         }
403         Dest->Price = atoi( buf + matches[6].rm_so );
404         
405         // Hack a little to reduce heap fragmentation
406         {
407                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
408                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
409                 strcpy(tmpType, buf + matches[3].rm_so);
410                 strcpy(tmpDesc, buf + matches[7].rm_so);
411                 free(buf);
412                 Dest->Type = strdup( tmpType );
413                 Dest->Desc = strdup( tmpDesc );
414         }
415         
416         return 0;
417 }
418
419 /**
420  * \brief Fill the item information structure
421  * \return Boolean Failure
422  */
423 void PopulateItemList(int Socket)
424 {
425         char    *buf;
426          int    responseCode;
427         
428         char    *arrayType;
429          int    count, i;
430         regmatch_t      matches[4];
431         
432         // Ask server for stock list
433         send(Socket, "ENUM_ITEMS\n", 11, 0);
434         buf = ReadLine(Socket);
435         
436         //printf("Output: %s\n", buf);
437         
438         responseCode = atoi(buf);
439         if( responseCode != 201 ) {
440                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
441                 exit(RV_UNKNOWN_ERROR);
442         }
443         
444         // - Get item list -
445         
446         // Expected format:
447         //  201 Items <count>
448         //  202 Item <count>
449         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
450                 
451         arrayType = &buf[ matches[2].rm_so ];   buf[ matches[2].rm_eo ] = '\0';
452         count = atoi( &buf[ matches[3].rm_so ] );
453                 
454         // Check array type
455         if( strcmp(arrayType, "Items") != 0 ) {
456                 // What the?!
457                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
458                         arrayType);
459                 exit(RV_UNKNOWN_ERROR);
460         }
461         free(buf);
462         
463         giNumItems = count;
464         gaItems = malloc( giNumItems * sizeof(tItem) );
465         
466         // Fetch item information
467         for( i = 0; i < giNumItems; i ++ )
468         {
469                 ReadItemInfo( Socket, &gaItems[i] );
470         }
471         
472         // Read end of list
473         buf = ReadLine(Socket);
474         responseCode = atoi(buf);
475                 
476         if( responseCode != 200 ) {
477                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
478                         responseCode, buf
479                         );
480                 exit(-1);
481         }
482         
483         free(buf);
484 }
485
486
487 /**
488  * \brief Get information on an item
489  * \return Boolean Failure
490  */
491 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
492 {
493         tItem   item;
494          int    ret;
495         
496         // Query
497         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
498         
499         ret = ReadItemInfo(Socket, &item);
500         if(ret) return ret;
501         
502         printf("%8s:%-2i %2i.%02i %s\n",
503                 item.Type, item.ID,
504                 item.Price/100, item.Price%100,
505                 item.Desc);
506         
507         free(item.Type);
508         free(item.Desc);
509         
510         return 0;
511 }
512
513 int DispenseCheckPin(int Socket, const char *Username, const char *Pin)
514 {
515          int    ret, responseCode;
516         char    *buf;
517         
518         if( strlen(Pin) != 4 ) {
519                 fprintf(stderr, "Pin format incorrect (not 4 characters long)\n");
520                 return RV_ARGUMENTS;
521         }
522                 
523         for( int i = 0; i < 4; i ++ )
524                 if( !isdigit(Pin[i]) ) {
525                         fprintf(stderr, "Pin format incorrect (character %i not a digit)\n", i);
526                         return RV_ARGUMENTS;
527                 }
528         }
529         
530         sendf(Socket, "CHECK_PIN %s %s\n", Username, Pin);
531         buf = ReadLine(Socket);
532         
533         responseCode = atoi(buf);
534         switch( responseCode )
535         {
536         case 200:       // Pin correct
537                 printf("Pin OK\n");
538                 ret = 0;
539                 break;
540         case 201:
541                 printf("Pin incorrect\n");
542                 ret = RV_INVALID_USER;
543                 break;
544         case 401:
545                 printf("Not authenticated\n");
546                 ret = RV_PERMISSIONS;
547                 break;
548         case 403:
549                 printf("Only coke members can check accounts other than their own\n");
550                 ret = RV_PERMISSIONS;
551                 break;
552         case 404:
553                 printf("User '%s' not found\n", Username);
554                 ret = RV_INVALID_USER;
555                 break;
556         case 407:
557                 printf("Rate limited or client-server disagree on pin format\n");
558                 ret = RV_SERVER_ERROR;
559                 break;
560         default:
561                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
562                 ret = RV_UNKNOWN_ERROR;
563                 break;
564         }
565         free(buf);
566         return ret;
567 }
568
569 /**
570  * \brief Dispense an item
571  * \return Boolean Failure
572  */
573 int DispenseItem(int Socket, const char *Type, int ID)
574 {
575          int    ret, responseCode;
576         char    *buf;
577         
578         // Check for a dry run
579         if( gbDryRun ) {
580                 printf("Dry Run - No action\n");
581                 return 0;
582         }
583         
584         // Dispense!
585         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
586         buf = ReadLine(Socket);
587         
588         responseCode = atoi(buf);
589         switch( responseCode )
590         {
591         case 200:
592                 printf("Dispense OK\n");
593                 ret = 0;
594                 break;
595         case 401:
596                 printf("Not authenticated\n");
597                 ret = RV_PERMISSIONS;
598                 break;
599         case 402:
600                 printf("Insufficient balance\n");
601                 ret = RV_BALANCE;
602                 break;
603         case 406:
604                 printf("Bad item name\n");
605                 ret = RV_BAD_ITEM;
606                 break;
607         case 500:
608                 printf("Item failed to dispense, is the slot empty?\n");
609                 ret = RV_SERVER_ERROR;
610                 break;
611         case 501:
612                 printf("Dispense not possible (slot empty/permissions)\n");
613                 ret = RV_SERVER_ERROR;
614                 break;
615         default:
616                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
617                 ret = RV_UNKNOWN_ERROR;
618                 break;
619         }
620         
621         free(buf);
622         return ret;
623 }
624
625 /**
626  * \brief Alter a user's balance
627  */
628 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
629 {
630         char    *buf;
631          int    responseCode, rv = -1;
632         
633         // Check for a dry run
634         if( gbDryRun ) {
635                 printf("Dry Run - No action\n");
636                 return 0;
637         }
638
639         // Sanity
640         if( Ammount == 0 ) {
641                 printf("An amount would be nice\n");
642                 return RV_ARGUMENTS;
643         }
644         
645         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
646         buf = ReadLine(Socket);
647         
648         responseCode = atoi(buf);
649         
650         switch(responseCode)
651         {
652         case 200:
653                 rv = 0; // OK
654                 break;
655         case 402:
656                 fprintf(stderr, "Insufficient balance\n");
657                 rv = RV_BAD_ITEM;
658                 break;
659         case 403:       // Not in coke
660                 fprintf(stderr, "You are not in coke (sucker)\n");
661                 rv = RV_PERMISSIONS;
662                 break;
663         case 404:       // Unknown user
664                 fprintf(stderr, "Unknown user '%s'\n", Username);
665                 rv = RV_INVALID_USER;
666                 break;
667         default:
668                 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
669                 rv = RV_UNKNOWN_RESPONSE;
670                 break;
671         }
672         free(buf);
673         
674         return rv;
675 }
676
677 /**
678  * \brief Set a user's balance
679  * \note Only avaliable to dispense admins
680  */
681 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
682 {
683         char    *buf;
684          int    responseCode;
685         
686         // Check for a dry run
687         if( gbDryRun ) {
688                 printf("Dry Run - No action\n");
689                 return 0;
690         }
691         
692         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
693         buf = ReadLine(Socket);
694         
695         responseCode = atoi(buf);
696         free(buf);
697         
698         switch(responseCode)
699         {
700         case 200:       return 0;       // OK
701         case 403:       // Not in coke
702                 fprintf(stderr, "You are not an admin\n");
703                 return RV_PERMISSIONS;
704         case 404:       // Unknown user
705                 fprintf(stderr, "Unknown user '%s'\n", Username);
706                 return RV_INVALID_USER;
707         default:
708                 fprintf(stderr, "Unknown response code %i\n", responseCode);
709                 return RV_UNKNOWN_RESPONSE;
710         }
711         
712         return -1;
713 }
714
715 /**
716  * \brief Give money to another user
717  */
718 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
719 {
720         char    *buf;
721          int    responseCode;
722         
723         if( Ammount < 0 ) {
724                 printf("Sorry, you can only give, you can't take.\n");
725                 return RV_ARGUMENTS;
726         }
727         
728         // Fast return on zero
729         if( Ammount == 0 ) {
730                 printf("Are you actually going to give any?\n");
731                 return RV_ARGUMENTS;
732         }
733         
734         // Check for a dry run
735         if( gbDryRun ) {
736                 printf("Dry Run - No action\n");
737                 return 0;
738         }
739         
740         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
741
742         buf = ReadLine(Socket);
743         responseCode = atoi(buf);
744         free(buf);      
745         switch(responseCode)
746         {
747         case 200:
748                 printf("Give succeeded\n");
749                 return RV_SUCCESS;      // OK
750         
751         case 402:       
752                 fprintf(stderr, "Insufficient balance\n");
753                 return RV_BALANCE;
754         
755         case 404:       // Unknown user
756                 fprintf(stderr, "Unknown user '%s'\n", Username);
757                 return RV_INVALID_USER;
758         
759         default:
760                 fprintf(stderr, "Unknown response code %i\n", responseCode);
761                 return RV_UNKNOWN_RESPONSE;
762         }
763         
764         return -1;
765 }
766
767 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
768 {
769         char    *buf;
770          int    responseCode, ret = -1;
771         
772         // Check item id
773         if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
774         {
775                 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
776                 return RV_ARGUMENTS;
777         }
778
779         // Check username (quick)
780         if( strchr(Username, ' ') || strchr(Username, '\n') )
781         {
782                 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
783                 return RV_ARGUMENTS;
784         }
785
786         // Send the query
787         sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
788
789         buf = ReadLine(Socket);
790         responseCode = atoi(buf);
791         switch(responseCode)
792         {
793         case 200:
794                 Dispense_ShowUser(Socket, Username);    // Show destination account
795                 ret = 0;
796                 break;
797         case 403:
798                 fprintf(stderr, "Refund access is only avaliable to coke members\n");
799                 ret = RV_PERMISSIONS;
800                 break;
801         case 404:
802                 fprintf(stderr, "Unknown user '%s' passed\n", Username);
803                 ret = RV_INVALID_USER;
804                 break;
805         case 406:
806                 fprintf(stderr, "Invalid item '%s' passed\n", Item);
807                 ret = RV_BAD_ITEM;
808                 break;
809         default:
810                 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
811                 ret = -1;
812                 break;
813         }
814         free(buf);
815         return ret;
816 }
817
818 /**
819  * \brief Donate money to the club
820  */
821 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
822 {
823         char    *buf;
824          int    responseCode;
825         
826         if( Ammount < 0 ) {
827                 printf("Sorry, you can only give, you can't take.\n");
828                 return -1;
829         }
830         
831         // Fast return on zero
832         if( Ammount == 0 ) {
833                 printf("Are you actually going to give any?\n");
834                 return 1;
835         }
836         
837         // Check for a dry run
838         if( gbDryRun ) {
839                 printf("Dry Run - No action\n");
840                 return 0;
841         }
842         
843         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
844         buf = ReadLine(Socket);
845         
846         responseCode = atoi(buf);
847         free(buf);
848         
849         switch(responseCode)
850         {
851         case 200:       return 0;       // OK
852         
853         case 402:       
854                 fprintf(stderr, "Insufficient balance\n");
855                 return 1;
856         
857         default:
858                 fprintf(stderr, "Unknown response code %i\n", responseCode);
859                 return -1;
860         }
861         
862         return -1;
863 }
864
865 /**
866  * \brief Enumerate users
867  */
868 int Dispense_EnumUsers(int Socket)
869 {
870         char    *buf;
871          int    responseCode;
872          int    nUsers;
873         regmatch_t      matches[4];
874         
875         if( giMinimumBalance != INT_MIN ) {
876                 if( giMaximumBalance != INT_MAX ) {
877                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
878                 }
879                 else {
880                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
881                 }
882         }
883         else {
884                 if( giMaximumBalance != INT_MAX ) {
885                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
886                 }
887                 else {
888                         sendf(Socket, "ENUM_USERS\n");
889                 }
890         }
891         buf = ReadLine(Socket);
892         responseCode = atoi(buf);
893         
894         switch(responseCode)
895         {
896         case 201:       break;  // Ok, length follows
897         
898         default:
899                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
900                 free(buf);
901                 return -1;
902         }
903         
904         // Get count (not actually used)
905         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
906         nUsers = atoi( buf + matches[3].rm_so );
907         printf("%i users returned\n", nUsers);
908         
909         // Free string
910         free(buf);
911         
912         // Read returned users
913         do {
914                 buf = ReadLine(Socket);
915                 responseCode = atoi(buf);
916                 
917                 if( responseCode != 202 )       break;
918                 
919                 _PrintUserLine(buf);
920                 free(buf);
921         } while(responseCode == 202);
922         
923         // Check final response
924         if( responseCode != 200 ) {
925                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
926                 free(buf);
927                 return -1;
928         }
929         
930         free(buf);
931         
932         return 0;
933 }
934
935 int Dispense_ShowUser(int Socket, const char *Username)
936 {
937         char    *buf;
938          int    responseCode, ret;
939         
940         sendf(Socket, "USER_INFO %s\n", Username);
941         buf = ReadLine(Socket);
942         
943         responseCode = atoi(buf);
944         
945         switch(responseCode)
946         {
947         case 202:
948                 _PrintUserLine(buf);
949                 ret = 0;
950                 break;
951         
952         case 404:
953                 printf("Unknown user '%s'\n", Username);
954                 ret = 1;
955                 break;
956         
957         default:
958                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
959                 ret = -1;
960                 break;
961         }
962         
963         free(buf);
964         
965         return ret;
966 }
967
968 void _PrintUserLine(const char *Line)
969 {
970         regmatch_t      matches[6];
971          int    bal;
972         
973         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
974         // 3: Username
975         // 4: Balance
976         // 5: Flags
977         {
978                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
979                 char    username[usernameLen + 1];
980                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
981                 char    flags[flagsLen + 1];
982                 
983                 memcpy(username, Line + matches[3].rm_so, usernameLen);
984                 username[usernameLen] = '\0';
985                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
986                 flags[flagsLen] = '\0';
987                 
988                 bal = atoi(Line + matches[4].rm_so);
989                 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
990         }
991 }
992
993 int Dispense_AddUser(int Socket, const char *Username)
994 {
995         char    *buf;
996          int    responseCode, ret;
997         
998         // Check for a dry run
999         if( gbDryRun ) {
1000                 printf("Dry Run - No action\n");
1001                 return 0;
1002         }
1003         
1004         sendf(Socket, "USER_ADD %s\n", Username);
1005         
1006         buf = ReadLine(Socket);
1007         responseCode = atoi(buf);
1008         
1009         switch(responseCode)
1010         {
1011         case 200:
1012                 printf("User '%s' added\n", Username);
1013                 ret = 0;
1014                 break;
1015                 
1016         case 403:
1017                 printf("Only wheel can add users\n");
1018                 ret = 1;
1019                 break;
1020                 
1021         case 404:
1022                 printf("User '%s' already exists\n", Username);
1023                 ret = 0;
1024                 break;
1025         
1026         default:
1027                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1028                 ret = -1;
1029                 break;
1030         }
1031         
1032         free(buf);
1033         
1034         return ret;
1035 }
1036
1037 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
1038 {
1039         char    *buf;
1040          int    responseCode, ret;
1041         
1042         // Check for a dry run
1043         if( gbDryRun ) {
1044                 printf("Dry Run - No action\n");
1045                 return 0;
1046         }
1047         
1048         // TODO: Pre-validate the string
1049         
1050         sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
1051         
1052         buf = ReadLine(Socket);
1053         responseCode = atoi(buf);
1054         
1055         switch(responseCode)
1056         {
1057         case 200:
1058                 printf("User '%s' updated\n", Username);
1059                 ret = 0;
1060                 break;
1061                 
1062         case 403:
1063                 printf("Only dispense admins can modify users\n");
1064                 ret = RV_PERMISSIONS;
1065                 break;
1066         
1067         case 404:
1068                 printf("User '%s' does not exist\n", Username);
1069                 ret = RV_INVALID_USER;
1070                 break;
1071         
1072         case 407:
1073                 printf("Flag string is invalid\n");
1074                 ret = RV_ARGUMENTS;
1075                 break;
1076         
1077         default:
1078                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1079                 ret = RV_UNKNOWN_RESPONSE;
1080                 break;
1081         }
1082         
1083         free(buf);
1084         
1085         return ret;
1086 }
1087
1088 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
1089 {
1090         char    *buf;
1091          int    responseCode, ret;
1092         
1093         // Check for a dry run
1094         if( gbDryRun ) {
1095                 printf("Dry Run - No action\n");
1096                 return 0;
1097         }
1098         
1099         sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
1100         
1101         buf = ReadLine(Socket);
1102         responseCode = atoi(buf);
1103         
1104         switch(responseCode)
1105         {
1106         case 200:
1107                 printf("Item %s:%i updated\n", Type, ID);
1108                 ret = 0;
1109                 break;
1110                 
1111         case 403:
1112                 printf("Only coke members can modify the slots\n");
1113                 ret = RV_PERMISSIONS;
1114                 break;
1115         
1116         case 406:
1117                 printf("Invalid item passed\n");
1118                 ret = RV_BAD_ITEM;
1119                 break;
1120         
1121         default:
1122                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1123                 ret = -1;
1124                 break;
1125         }
1126         
1127         free(buf);
1128         
1129         return ret;
1130 }
1131
1132 // ===
1133 // Helpers
1134 // ===
1135 char *ReadLine(int Socket)
1136 {
1137         static char     buf[BUFSIZ];
1138         static int      bufPos = 0;
1139         static int      bufValid = 0;
1140          int    len;
1141         char    *newline = NULL;
1142          int    retLen = 0;
1143         char    *ret = malloc(10);
1144         
1145         #if DEBUG_TRACE_SERVER
1146         printf("ReadLine: ");
1147         fflush(stdout);
1148         #endif
1149         
1150         ret[0] = '\0';
1151         
1152         while( !newline )
1153         {
1154                 if( bufValid ) {
1155                         len = bufValid;
1156                 }
1157                 else {
1158                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1159                         if( len <= 0 ) {
1160                                 free(ret);
1161                                 return strdup("599 Client Connection Error\n");
1162                         }
1163                 }
1164                 buf[bufPos+len] = '\0';
1165                 
1166                 newline = strchr( buf+bufPos, '\n' );
1167                 if( newline ) {
1168                         *newline = '\0';
1169                 }
1170                 
1171                 retLen += strlen(buf+bufPos);
1172                 ret = realloc(ret, retLen + 1);
1173                 strcat( ret, buf+bufPos );
1174                 
1175                 if( newline ) {
1176                          int    newLen = newline - (buf+bufPos) + 1;
1177                         bufValid = len - newLen;
1178                         len = newLen;
1179                 }
1180                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
1181                 else    bufPos += len;
1182         }
1183         
1184         #if DEBUG_TRACE_SERVER
1185         printf("%i '%s'\n", retLen, ret);
1186         #endif
1187         
1188         return ret;
1189 }
1190
1191 int sendf(int Socket, const char *Format, ...)
1192 {
1193         va_list args;
1194          int    len;
1195         
1196         va_start(args, Format);
1197         len = vsnprintf(NULL, 0, Format, args);
1198         va_end(args);
1199         
1200         {
1201                 char    buf[len+1];
1202                 va_start(args, Format);
1203                 vsnprintf(buf, len+1, Format, args);
1204                 va_end(args);
1205                 
1206                 #if DEBUG_TRACE_SERVER
1207                 printf("sendf: %s", buf);
1208                 #endif
1209                 
1210                 return send(Socket, buf, len, 0);
1211         }
1212 }
1213

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