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

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