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

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