Implemented `dispense acct <name> =`
[tpg/opendispense2.git] / src / server / server.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * server.c - Client Server Code
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file
8  * COPYING for full details.
9  */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include "common.h"
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16 #include <unistd.h>
17 #include <string.h>
18 #include <limits.h>
19 #include <stdarg.h>
20
21 #define DEBUG_TRACE_CLIENT      0
22
23 // Statistics
24 #define MAX_CONNECTION_QUEUE    5
25 #define INPUT_BUFFER_SIZE       256
26
27 #define HASH_TYPE       SHA1
28 #define HASH_LENGTH     20
29
30 #define MSG_STR_TOO_LONG        "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
31
32 // === TYPES ===
33 typedef struct sClient
34 {
35          int    Socket; // Client socket ID
36          int    ID;     // Client ID
37          
38          int    bIsTrusted;     // Is the connection from a trusted host/port
39         
40         char    *Username;
41         char    Salt[9];
42         
43          int    UID;
44          int    EffectiveUID;
45          int    bIsAuthed;
46 }       tClient;
47
48 // === PROTOTYPES ===
49 void    Server_Start(void);
50 void    Server_Cleanup(void);
51 void    Server_HandleClient(int Socket, int bTrusted);
52 void    Server_ParseClientCommand(tClient *Client, char *CommandString);
53 // --- Commands ---
54 void    Server_Cmd_USER(tClient *Client, char *Args);
55 void    Server_Cmd_PASS(tClient *Client, char *Args);
56 void    Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
57 void    Server_Cmd_SETEUSER(tClient *Client, char *Args);
58 void    Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
59 void    Server_Cmd_ITEMINFO(tClient *Client, char *Args);
60 void    Server_Cmd_DISPENSE(tClient *Client, char *Args);
61 void    Server_Cmd_GIVE(tClient *Client, char *Args);
62 void    Server_Cmd_DONATE(tClient *Client, char *Args);
63 void    Server_Cmd_ADD(tClient *Client, char *Args);
64 void    Server_Cmd_SET(tClient *Client, char *Args);
65 void    Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
66 void    Server_Cmd_USERINFO(tClient *Client, char *Args);
67 void    _SendUserInfo(tClient *Client, int UserID);
68 void    Server_Cmd_USERADD(tClient *Client, char *Args);
69 void    Server_Cmd_USERFLAGS(tClient *Client, char *Args);
70 // --- Helpers ---
71  int    Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value);
72  int    sendf(int Socket, const char *Format, ...);
73
74 // === CONSTANTS ===
75 // - Commands
76 const struct sClientCommand {
77         const char      *Name;
78         void    (*Function)(tClient *Client, char *Arguments);
79 }       gaServer_Commands[] = {
80         {"USER", Server_Cmd_USER},
81         {"PASS", Server_Cmd_PASS},
82         {"AUTOAUTH", Server_Cmd_AUTOAUTH},
83         {"SETEUSER", Server_Cmd_SETEUSER},
84         {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
85         {"ITEM_INFO", Server_Cmd_ITEMINFO},
86         {"DISPENSE", Server_Cmd_DISPENSE},
87         {"GIVE", Server_Cmd_GIVE},
88         {"DONATE", Server_Cmd_DONATE},
89         {"ADD", Server_Cmd_ADD},
90         {"SET", Server_Cmd_SET},
91         {"ENUM_USERS", Server_Cmd_ENUMUSERS},
92         {"USER_INFO", Server_Cmd_USERINFO},
93         {"USER_ADD", Server_Cmd_USERADD},
94         {"USER_FLAGS", Server_Cmd_USERFLAGS}
95 };
96 #define NUM_COMMANDS    ((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
97
98 // === GLOBALS ===
99  int    giServer_Port = 1020;
100  int    giServer_NextClientID = 1;
101  int    giServer_Socket;
102
103 // === CODE ===
104 /**
105  * \brief Open listenting socket and serve connections
106  */
107 void Server_Start(void)
108 {
109          int    client_socket;
110         struct sockaddr_in      server_addr, client_addr;
111
112         atexit(Server_Cleanup);
113
114         // Create Server
115         giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
116         if( giServer_Socket < 0 ) {
117                 fprintf(stderr, "ERROR: Unable to create server socket\n");
118                 return ;
119         }
120         
121         // Make listen address
122         memset(&server_addr, 0, sizeof(server_addr));
123         server_addr.sin_family = AF_INET;       // Internet Socket
124         server_addr.sin_addr.s_addr = htonl(INADDR_ANY);        // Listen on all interfaces
125         server_addr.sin_port = htons(giServer_Port);    // Port
126
127         // Bind
128         if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
129                 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
130                 perror("Binding");
131                 return ;
132         }
133         
134         // Listen
135         if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
136                 fprintf(stderr, "ERROR: Unable to listen to socket\n");
137                 perror("Listen");
138                 return ;
139         }
140         
141         printf("Listening on 0.0.0.0:%i\n", giServer_Port);
142         
143         for(;;)
144         {
145                 uint    len = sizeof(client_addr);
146                  int    bTrusted = 0;
147                 
148                 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
149                 if(client_socket < 0) {
150                         fprintf(stderr, "ERROR: Unable to accept client connection\n");
151                         return ;
152                 }
153                 
154                 if(giDebugLevel >= 2) {
155                         char    ipstr[INET_ADDRSTRLEN];
156                         inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
157                         printf("Client connection from %s:%i\n",
158                                 ipstr, ntohs(client_addr.sin_port));
159                 }
160                 
161                 // Trusted Connections
162                 if( ntohs(client_addr.sin_port) < 1024 )
163                 {
164                         // TODO: Make this runtime configurable
165                         switch( ntohl( client_addr.sin_addr.s_addr ) )
166                         {
167                         case 0x7F000001:        // 127.0.0.1    localhost
168                         //case 0x825E0D00:      // 130.95.13.0
169                         case 0x825E0D12:        // 130.95.13.18 mussel
170                         case 0x825E0D17:        // 130.95.13.23 martello
171                                 bTrusted = 1;
172                                 break;
173                         default:
174                                 break;
175                         }
176                 }
177                 
178                 // TODO: Multithread this?
179                 Server_HandleClient(client_socket, bTrusted);
180                 
181                 close(client_socket);
182         }
183 }
184
185 void Server_Cleanup(void)
186 {
187         printf("Close(%i)\n", giServer_Socket);
188         close(giServer_Socket);
189 }
190
191 /**
192  * \brief Reads from a client socket and parses the command strings
193  * \param Socket        Client socket number/handle
194  * \param bTrusted      Is the client trusted?
195  */
196 void Server_HandleClient(int Socket, int bTrusted)
197 {
198         char    inbuf[INPUT_BUFFER_SIZE];
199         char    *buf = inbuf;
200          int    remspace = INPUT_BUFFER_SIZE-1;
201          int    bytes = -1;
202         tClient clientInfo;
203         
204         memset(&clientInfo, 0, sizeof(clientInfo));
205         
206         // Initialise Client info
207         clientInfo.Socket = Socket;
208         clientInfo.ID = giServer_NextClientID ++;
209         clientInfo.bIsTrusted = bTrusted;
210         clientInfo.EffectiveUID = -1;
211         
212         // Read from client
213         /*
214          * Notes:
215          * - The `buf` and `remspace` variables allow a line to span several
216          *   calls to recv(), if a line is not completed in one recv() call
217          *   it is saved to the beginning of `inbuf` and `buf` is updated to
218          *   the end of it.
219          */
220         while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
221         {
222                 char    *eol, *start;
223                 buf[bytes] = '\0';      // Allow us to use stdlib string functions on it
224                 
225                 // Split by lines
226                 start = inbuf;
227                 while( (eol = strchr(start, '\n')) )
228                 {
229                         *eol = '\0';
230                         
231                         Server_ParseClientCommand(&clientInfo, start);
232                         
233                         start = eol + 1;
234                 }
235                 
236                 // Check if there was an incomplete line
237                 if( *start != '\0' ) {
238                          int    tailBytes = bytes - (start-buf);
239                         // Roll back in buffer
240                         memcpy(inbuf, start, tailBytes);
241                         remspace -= tailBytes;
242                         if(remspace == 0) {
243                                 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
244                                 buf = inbuf;
245                                 remspace = INPUT_BUFFER_SIZE - 1;
246                         }
247                 }
248                 else {
249                         buf = inbuf;
250                         remspace = INPUT_BUFFER_SIZE - 1;
251                 }
252         }
253         
254         // Check for errors
255         if( bytes < 0 ) {
256                 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
257                 return ;
258         }
259         
260         if(giDebugLevel >= 2) {
261                 printf("Client %i: Disconnected\n", clientInfo.ID);
262         }
263 }
264
265 /**
266  * \brief Parses a client command and calls the required helper function
267  * \param Client        Pointer to client state structure
268  * \param CommandString Command from client (single line of the command)
269  * \return Heap String to return to the client
270  */
271 void Server_ParseClientCommand(tClient *Client, char *CommandString)
272 {
273         char    *space, *args;
274          int    i;
275         #if 0
276         char    **argList;
277          int    numArgs = 0;
278         #endif
279         
280         // Split at first space
281         space = strchr(CommandString, ' ');
282         if(space == NULL) {
283                 args = NULL;
284         }
285         else {
286                 *space = '\0';
287                 args = space + 1;
288                 while( *space == ' ' )  space ++;
289                 
290                 #if 0
291                 // Count arguments
292                 numArgs = 1;
293                 for( i = 0; args[i]; )
294                 {
295                         while( CommandString[i] != ' ' ) {
296                                 if( CommandString[i] == '"' ) {
297                                         while( !(CommandString[i] != '\\' CommandString[i+1] == '"' ) )
298                                                 i ++;
299                                         i ++;
300                                 }
301                                 i ++;
302                         }
303                         numArgs ++;
304                         while( CommandString[i] == ' ' )        i ++;
305                 }
306                 #endif
307         }
308         
309         
310         // Find command
311         for( i = 0; i < NUM_COMMANDS; i++ )
312         {
313                 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) {
314                         gaServer_Commands[i].Function(Client, args);
315                         return ;
316                 }
317         }
318         
319         sendf(Client->Socket, "400 Unknown Command\n");
320 }
321
322 // ---
323 // Commands
324 // ---
325 /**
326  * \brief Set client username
327  * 
328  * Usage: USER <username>
329  */
330 void Server_Cmd_USER(tClient *Client, char *Args)
331 {
332         char    *space = strchr(Args, ' ');
333         if(space)       *space = '\0';  // Remove characters after the ' '
334         
335         // Debug!
336         if( giDebugLevel )
337                 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
338         
339         // Save username
340         if(Client->Username)
341                 free(Client->Username);
342         Client->Username = strdup(Args);
343         
344         #if USE_SALT
345         // Create a salt (that changes if the username is changed)
346         // Yes, I know, I'm a little paranoid, but who isn't?
347         Client->Salt[0] = 0x21 + (rand()&0x3F);
348         Client->Salt[1] = 0x21 + (rand()&0x3F);
349         Client->Salt[2] = 0x21 + (rand()&0x3F);
350         Client->Salt[3] = 0x21 + (rand()&0x3F);
351         Client->Salt[4] = 0x21 + (rand()&0x3F);
352         Client->Salt[5] = 0x21 + (rand()&0x3F);
353         Client->Salt[6] = 0x21 + (rand()&0x3F);
354         Client->Salt[7] = 0x21 + (rand()&0x3F);
355         
356         // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
357         sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
358         #else
359         sendf(Client->Socket, "100 User Set\n");
360         #endif
361 }
362
363 /**
364  * \brief Authenticate as a user
365  * 
366  * Usage: PASS <hash>
367  */
368 void Server_Cmd_PASS(tClient *Client, char *Args)
369 {
370         char    *space = strchr(Args, ' ');
371         if(space)       *space = '\0';  // Remove characters after the ' '
372         
373         // Pass on to cokebank
374         Client->UID = Bank_GetUserAuth(Client->Salt, Client->Username, Args);
375
376         if( Client->UID != -1 ) {
377                 Client->bIsAuthed = 1;
378                 sendf(Client->Socket, "200 Auth OK\n");
379                 return ;
380         }
381         
382         sendf(Client->Socket, "401 Auth Failure\n");
383 }
384
385 /**
386  * \brief Authenticate as a user without a password
387  * 
388  * Usage: AUTOAUTH <user>
389  */
390 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
391 {
392         char    *space = strchr(Args, ' ');
393         if(space)       *space = '\0';  // Remove characters after the ' '
394         
395         // Check if trusted
396         if( !Client->bIsTrusted ) {
397                 if(giDebugLevel)
398                         printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
399                 sendf(Client->Socket, "401 Untrusted\n");
400                 return ;
401         }
402         
403         // Get UID
404         Client->UID = Bank_GetAcctByName( Args );       
405         if( Client->UID < 0 ) {
406                 if(giDebugLevel)
407                         printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
408                 sendf(Client->Socket, "401 Auth Failure\n");
409                 return ;
410         }
411         
412         // You can't be an internal account
413         if( Bank_GetFlags(Client->UID) & USER_FLAG_INTERNAL ) {
414                 Client->UID = -1;
415                 sendf(Client->Socket, "401 Auth Failure\n");
416                 return ;
417         }
418         
419         if(giDebugLevel)
420                 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
421         
422         sendf(Client->Socket, "200 Auth OK\n");
423 }
424
425 /**
426  * \brief Set effective user
427  */
428 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
429 {
430         char    *space;
431         
432         space = strchr(Args, ' ');
433         
434         if(space)       *space = '\0';
435         
436         if( !strlen(Args) ) {
437                 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
438                 return ;
439         }
440
441         // Check user permissions
442         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_COKE) ) {
443                 sendf(Client->Socket, "403 Not in coke\n");
444                 return ;
445         }
446         
447         // Set id
448         Client->EffectiveUID = Bank_GetAcctByName(Args);
449         if( Client->EffectiveUID == -1 ) {
450                 sendf(Client->Socket, "404 User not found\n");
451                 return ;
452         }
453         
454         // You can't be an internal account
455         if( Bank_GetFlags(Client->EffectiveUID) & USER_FLAG_INTERNAL ) {
456                 Client->EffectiveUID = -1;
457                 sendf(Client->Socket, "404 User not found\n");
458                 return ;
459         }
460         
461         sendf(Client->Socket, "200 User set\n");
462 }
463
464 /**
465  * \brief Enumerate the items that the server knows about
466  */
467 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
468 {
469          int    i;
470
471         if( Args != NULL && strlen(Args) ) {
472                 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
473                 return ;
474         }
475
476         sendf(Client->Socket, "201 Items %i\n", giNumItems);
477
478         for( i = 0; i < giNumItems; i ++ ) {
479                 sendf(Client->Socket,
480                         "202 Item %s:%i %i %s\n",
481                          gaItems[i].Handler->Name, gaItems[i].ID, gaItems[i].Price, gaItems[i].Name
482                          );
483         }
484
485         sendf(Client->Socket, "200 List end\n");
486 }
487
488 tItem *_GetItemFromString(char *String)
489 {
490         tHandler        *handler;
491         char    *type = String;
492         char    *colon = strchr(String, ':');
493          int    num, i;
494         
495         if( !colon ) {
496                 return NULL;
497         }
498
499         num = atoi(colon+1);
500         *colon = '\0';
501
502         // Find handler
503         handler = NULL;
504         for( i = 0; i < giNumHandlers; i ++ )
505         {
506                 if( strcmp(gaHandlers[i]->Name, type) == 0) {
507                         handler = gaHandlers[i];
508                         break;
509                 }
510         }
511         if( !handler ) {
512                 return NULL;
513         }
514
515         // Find item
516         for( i = 0; i < giNumItems; i ++ )
517         {
518                 if( gaItems[i].Handler != handler )     continue;
519                 if( gaItems[i].ID != num )      continue;
520                 return &gaItems[i];
521         }
522         return NULL;
523 }
524
525 /**
526  * \brief Fetch information on a specific item
527  */
528 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
529 {
530         tItem   *item = _GetItemFromString(Args);
531         
532         if( !item ) {
533                 sendf(Client->Socket, "406 Bad Item ID\n");
534                 return ;
535         }
536         
537         sendf(Client->Socket,
538                 "202 Item %s:%i %i %s\n",
539                  item->Handler->Name, item->ID, item->Price, item->Name
540                  );
541 }
542
543 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
544 {
545         tItem   *item;
546          int    ret;
547          int    uid;
548          
549         if( !Client->bIsAuthed ) {
550                 sendf(Client->Socket, "401 Not Authenticated\n");
551                 return ;
552         }
553
554         item = _GetItemFromString(Args);
555         if( !item ) {
556                 sendf(Client->Socket, "406 Bad Item ID\n");
557                 return ;
558         }
559         
560         if( Client->EffectiveUID != -1 ) {
561                 uid = Client->EffectiveUID;
562         }
563         else {
564                 uid = Client->UID;
565         }
566
567         switch( ret = DispenseItem( Client->UID, uid, item ) )
568         {
569         case 0: sendf(Client->Socket, "200 Dispense OK\n");     return ;
570         case 1: sendf(Client->Socket, "501 Unable to dispense\n");      return ;
571         case 2: sendf(Client->Socket, "402 Poor You\n");        return ;
572         default:
573                 sendf(Client->Socket, "500 Dispense Error\n");
574                 return ;
575         }
576 }
577
578 void Server_Cmd_GIVE(tClient *Client, char *Args)
579 {
580         char    *recipient, *ammount, *reason;
581          int    uid, iAmmount;
582          int    thisUid;
583         
584         if( !Client->bIsAuthed ) {
585                 sendf(Client->Socket, "401 Not Authenticated\n");
586                 return ;
587         }
588
589         recipient = Args;
590
591         ammount = strchr(Args, ' ');
592         if( !ammount ) {
593                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
594                 return ;
595         }
596         *ammount = '\0';
597         ammount ++;
598
599         reason = strchr(ammount, ' ');
600         if( !reason ) {
601                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
602                 return ;
603         }
604         *reason = '\0';
605         reason ++;
606
607         // Get recipient
608         uid = Bank_GetAcctByName(recipient);
609         if( uid == -1 ) {
610                 sendf(Client->Socket, "404 Invalid target user\n");
611                 return ;
612         }
613         
614         // You can't alter an internal account
615         if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
616                 sendf(Client->Socket, "404 Invalid target user\n");
617                 return ;
618         }
619
620         // Parse ammount
621         iAmmount = atoi(ammount);
622         if( iAmmount <= 0 ) {
623                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
624                 return ;
625         }
626         
627         if( Client->EffectiveUID != -1 ) {
628                 thisUid = Client->EffectiveUID;
629         }
630         else {
631                 thisUid = Client->UID;
632         }
633
634         // Do give
635         switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
636         {
637         case 0:
638                 sendf(Client->Socket, "200 Give OK\n");
639                 return ;
640         case 2:
641                 sendf(Client->Socket, "402 Poor You\n");
642                 return ;
643         default:
644                 sendf(Client->Socket, "500 Unknown error\n");
645                 return ;
646         }
647 }
648
649 void Server_Cmd_DONATE(tClient *Client, char *Args)
650 {
651         char    *ammount, *reason;
652          int    iAmmount;
653          int    thisUid;
654         
655         if( !Client->bIsAuthed ) {
656                 sendf(Client->Socket, "401 Not Authenticated\n");
657                 return ;
658         }
659
660         ammount = Args;
661
662         // Get the start of the reason
663         reason = strchr(Args, ' ');
664         if( !ammount ) {
665                 sendf(Client->Socket, "407 Invalid Argument, expected 2 parameters, 1 encountered\n");
666                 return ;
667         }
668         *reason = '\0';
669         reason ++;
670         
671         // Check the end of the reason
672         if( strchr(reason, ' ') ) {
673                 sendf(Client->Socket, "407 Invalid Argument, expected 2 parameters, more encountered\n");
674                 return ;
675         }
676
677         // Parse ammount
678         iAmmount = atoi(ammount);
679         if( iAmmount <= 0 ) {
680                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
681                 return ;
682         }
683         
684         // Handle effective users
685         if( Client->EffectiveUID != -1 ) {
686                 thisUid = Client->EffectiveUID;
687         }
688         else {
689                 thisUid = Client->UID;
690         }
691
692         // Do give
693         switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
694         {
695         case 0:
696                 sendf(Client->Socket, "200 Give OK\n");
697                 return ;
698         case 2:
699                 sendf(Client->Socket, "402 Poor You\n");
700                 return ;
701         default:
702                 sendf(Client->Socket, "500 Unknown error\n");
703                 return ;
704         }
705 }
706
707 void Server_Cmd_ADD(tClient *Client, char *Args)
708 {
709         char    *user, *ammount, *reason;
710          int    uid, iAmmount;
711         
712         if( !Client->bIsAuthed ) {
713                 sendf(Client->Socket, "401 Not Authenticated\n");
714                 return ;
715         }
716
717         user = Args;
718
719         ammount = strchr(Args, ' ');
720         if( !ammount ) {
721                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
722                 return ;
723         }
724         *ammount = '\0';
725         ammount ++;
726
727         reason = strchr(ammount, ' ');
728         if( !reason ) {
729                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
730                 return ;
731         }
732         *reason = '\0';
733         reason ++;
734
735         // Check user permissions
736         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_COKE)  ) {
737                 sendf(Client->Socket, "403 Not in coke\n");
738                 return ;
739         }
740
741         // Get recipient
742         uid = Bank_GetAcctByName(user);
743         if( uid == -1 ) {
744                 sendf(Client->Socket, "404 Invalid user\n");
745                 return ;
746         }
747         
748         // You can't alter an internal account
749         if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
750                 sendf(Client->Socket, "404 Invalid user\n");
751                 return ;
752         }
753
754         // Parse ammount
755         iAmmount = atoi(ammount);
756         if( iAmmount == 0 && ammount[0] != '0' ) {
757                 sendf(Client->Socket, "407 Invalid Argument\n");
758                 return ;
759         }
760
761         // Do give
762         switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
763         {
764         case 0:
765                 sendf(Client->Socket, "200 Add OK\n");
766                 return ;
767         case 2:
768                 sendf(Client->Socket, "402 Poor Guy\n");
769                 return ;
770         default:
771                 sendf(Client->Socket, "500 Unknown error\n");
772                 return ;
773         }
774 }
775
776 void Server_Cmd_SET(tClient *Client, char *Args)
777 {
778         char    *user, *ammount, *reason;
779          int    uid, iAmmount;
780         
781         if( !Client->bIsAuthed ) {
782                 sendf(Client->Socket, "401 Not Authenticated\n");
783                 return ;
784         }
785
786         user = Args;
787
788         ammount = strchr(Args, ' ');
789         if( !ammount ) {
790                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
791                 return ;
792         }
793         *ammount = '\0';
794         ammount ++;
795
796         reason = strchr(ammount, ' ');
797         if( !reason ) {
798                 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
799                 return ;
800         }
801         *reason = '\0';
802         reason ++;
803
804         // Check user permissions
805         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN)  ) {
806                 sendf(Client->Socket, "403 Not an admin\n");
807                 return ;
808         }
809
810         // Get recipient
811         uid = Bank_GetAcctByName(user);
812         if( uid == -1 ) {
813                 sendf(Client->Socket, "404 Invalid user\n");
814                 return ;
815         }
816         
817         // You can't alter an internal account
818         if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
819                 sendf(Client->Socket, "404 Invalid user\n");
820                 return ;
821         }
822
823         // Parse ammount
824         iAmmount = atoi(ammount);
825         if( iAmmount == 0 && ammount[0] != '0' ) {
826                 sendf(Client->Socket, "407 Invalid Argument\n");
827                 return ;
828         }
829
830         // Do give
831         switch( DispenseSet(Client->UID, uid, iAmmount, reason) )
832         {
833         case 0:
834                 sendf(Client->Socket, "200 Add OK\n");
835                 return ;
836         case 2:
837                 sendf(Client->Socket, "402 Poor Guy\n");
838                 return ;
839         default:
840                 sendf(Client->Socket, "500 Unknown error\n");
841                 return ;
842         }
843 }
844
845 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
846 {
847          int    i, numRet = 0;
848         tAcctIterator   *it;
849          int    maxBal = INT_MAX, minBal = INT_MIN;
850          int    flagMask = 0, flagVal = 0;
851          int    sort = BANK_ITFLAG_SORT_NAME;
852         time_t  lastSeenAfter=0, lastSeenBefore=0;
853         
854          int    flags;  // Iterator flags
855          int    balValue;       // Balance value for iterator
856         time_t  timeValue;      // Time value for iterator
857         
858         // Parse arguments
859         if( Args && strlen(Args) )
860         {
861                 char    *space = Args, *type, *val;
862                 do
863                 {
864                         type = space;
865                         // Get next space
866                         space = strchr(space, ' ');
867                         if(space)       *space = '\0';
868                         
869                         // Get type
870                         val = strchr(type, ':');
871                         if( val ) {
872                                 *val = '\0';
873                                 val ++;
874                                 
875                                 // Types
876                                 // - Minium Balance
877                                 if( strcmp(type, "min_balance") == 0 ) {
878                                         minBal = atoi(val);
879                                 }
880                                 // - Maximum Balance
881                                 else if( strcmp(type, "max_balance") == 0 ) {
882                                         maxBal = atoi(val);
883                                 }
884                                 // - Flags
885                                 else if( strcmp(type, "flags") == 0 ) {
886                                         if( Server_int_ParseFlags(Client, val, &flagMask, &flagVal) )
887                                                 return ;
888                                 }
889                                 // - Last seen before timestamp
890                                 else if( strcmp(type, "last_seen_before") == 0 ) {
891                                         lastSeenAfter = atoll(val);
892                                 }
893                                 // - Last seen after timestamp
894                                 else if( strcmp(type, "last_seen_after") == 0 ) {
895                                         lastSeenAfter = atoll(val);
896                                 }
897                                 // - Sorting 
898                                 else if( strcmp(type, "sort") == 0 ) {
899                                         char    *dash = strchr(val, '-');
900                                         if( dash ) {
901                                                 *dash = '\0';
902                                                 dash ++;
903                                         }
904                                         if( strcmp(val, "name") == 0 ) {
905                                                 sort = BANK_ITFLAG_SORT_NAME;
906                                         }
907                                         else if( strcmp(val, "balance") == 0 ) {
908                                                 sort = BANK_ITFLAG_SORT_BAL;
909                                         }
910                                         else if( strcmp(val, "lastseen") == 0 ) {
911                                                 sort = BANK_ITFLAG_SORT_LASTSEEN;
912                                         }
913                                         else {
914                                                 sendf(Client->Socket, "407 Unknown sort field ('%s')\n", val);
915                                                 return ;
916                                         }
917                                         // Handle sort direction
918                                         if( dash ) {
919                                                 if( strcmp(dash, "desc") == 0 ) {
920                                                         sort |= BANK_ITFLAG_REVSORT;
921                                                 }
922                                                 else {
923                                                         sendf(Client->Socket, "407 Unknown sort direction '%s'\n", dash);
924                                                         return ;
925                                                 }
926                                                 dash[-1] = '-';
927                                         }
928                                 }
929                                 else {
930                                         sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s:%s'\n", type, val);
931                                         return ;
932                                 }
933                                 
934                                 val[-1] = ':';
935                         }
936                         else {
937                                 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s'\n", type);
938                                 return ;
939                         }
940                         
941                         // Eat whitespace
942                         if( space ) {
943                                 *space = ' ';   // Repair (to be nice)
944                                 space ++;
945                                 while(*space == ' ')    space ++;
946                         }
947                 }       while(space);
948         }
949         
950         // Create iterator
951         if( maxBal != INT_MAX ) {
952                 flags = sort|BANK_ITFLAG_MAXBALANCE;
953                 balValue = maxBal;
954         }
955         else if( minBal != INT_MIN ) {
956                 flags = sort|BANK_ITFLAG_MINBALANCE;
957                 balValue = minBal;
958         }
959         else {
960                 flags = sort;
961                 balValue = 0;
962         }
963         if( lastSeenBefore ) {
964                 timeValue = lastSeenBefore;
965                 flags |= BANK_ITFLAG_SEENBEFORE;
966         }
967         else if( lastSeenAfter ) {
968                 timeValue = lastSeenAfter;
969                 flags |= BANK_ITFLAG_SEENAFTER;
970         }
971         else {
972                 timeValue = 0;
973         }
974         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
975         
976         // Get return number
977         while( (i = Bank_IteratorNext(it)) != -1 )
978         {
979                 int bal = Bank_GetBalance(i);
980                 
981                 if( bal == INT_MIN )    continue;
982                 
983                 if( bal < minBal )      continue;
984                 if( bal > maxBal )      continue;
985                 
986                 numRet ++;
987         }
988         
989         Bank_DelIterator(it);
990         
991         // Send count
992         sendf(Client->Socket, "201 Users %i\n", numRet);
993         
994         
995         // Create iterator
996         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
997         
998         while( (i = Bank_IteratorNext(it)) != -1 )
999         {
1000                 int bal = Bank_GetBalance(i);
1001                 
1002                 if( bal == INT_MIN )    continue;
1003                 
1004                 if( bal < minBal )      continue;
1005                 if( bal > maxBal )      continue;
1006                 
1007                 _SendUserInfo(Client, i);
1008         }
1009         
1010         Bank_DelIterator(it);
1011         
1012         sendf(Client->Socket, "200 List End\n");
1013 }
1014
1015 void Server_Cmd_USERINFO(tClient *Client, char *Args)
1016 {
1017          int    uid;
1018         char    *user = Args;
1019         char    *space;
1020         
1021         space = strchr(user, ' ');
1022         if(space)       *space = '\0';
1023         
1024         // Get recipient
1025         uid = Bank_GetAcctByName(user);
1026         if( uid == -1 ) {
1027                 sendf(Client->Socket, "404 Invalid user");
1028                 return ;
1029         }
1030         
1031         _SendUserInfo(Client, uid);
1032 }
1033
1034 void _SendUserInfo(tClient *Client, int UserID)
1035 {
1036         char    *type, *disabled="", *door="";
1037          int    flags = Bank_GetFlags(UserID);
1038         
1039         if( flags & USER_FLAG_INTERNAL ) {
1040                 type = "internal";
1041         }
1042         else if( flags & USER_FLAG_COKE ) {
1043                 if( flags & USER_FLAG_ADMIN )
1044                         type = "coke,admin";
1045                 else
1046                         type = "coke";
1047         }
1048         else if( flags & USER_FLAG_ADMIN ) {
1049                 type = "admin";
1050         }
1051         else {
1052                 type = "user";
1053         }
1054         
1055         if( flags & USER_FLAG_DISABLED )
1056                 disabled = ",disabled";
1057         if( flags & USER_FLAG_DOORGROUP )
1058                 door = ",door";
1059         
1060         // TODO: User flags/type
1061         sendf(
1062                 Client->Socket, "202 User %s %i %s%s\n",
1063                 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
1064                 type, disabled
1065                 );
1066 }
1067
1068 void Server_Cmd_USERADD(tClient *Client, char *Args)
1069 {
1070         char    *username, *space;
1071         
1072         // Check permissions
1073         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1074                 sendf(Client->Socket, "403 Not a coke admin\n");
1075                 return ;
1076         }
1077         
1078         // Read arguments
1079         username = Args;
1080         while( *username == ' ' )       username ++;
1081         space = strchr(username, ' ');
1082         if(space)       *space = '\0';
1083         
1084         // Try to create user
1085         if( Bank_CreateAcct(username) == -1 ) {
1086                 sendf(Client->Socket, "404 User exists\n");
1087                 return ;
1088         }
1089         
1090         {
1091                 char    *thisName = Bank_GetAcctName(Client->UID);
1092                 Log_Info("Account '%s' created by '%s'", username, thisName);
1093                 free(thisName);
1094         }
1095         
1096         sendf(Client->Socket, "200 User Added\n");
1097 }
1098
1099 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
1100 {
1101         char    *username, *flags;
1102         char    *space;
1103          int    mask=0, value=0;
1104          int    uid;
1105         
1106         // Check permissions
1107         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1108                 sendf(Client->Socket, "403 Not a coke admin\n");
1109                 return ;
1110         }
1111         
1112         // Read arguments
1113         // - Username
1114         username = Args;
1115         while( *username == ' ' )       username ++;
1116         space = strchr(username, ' ');
1117         if(!space) {
1118                 sendf(Client->Socket, "407 USER_FLAGS requires 2 arguments, 1 given\n");
1119                 return ;
1120         }
1121         *space = '\0';
1122         // - Flags
1123         flags = space + 1;
1124         while( *flags == ' ' )  flags ++;
1125         space = strchr(flags, ' ');
1126         if(space)       *space = '\0';
1127         
1128         // Get UID
1129         uid = Bank_GetAcctByName(username);
1130         if( uid == -1 ) {
1131                 sendf(Client->Socket, "404 User '%s' not found\n", username);
1132                 return ;
1133         }
1134         
1135         // Parse flags
1136         if( Server_int_ParseFlags(Client, flags, &mask, &value) )
1137                 return ;
1138         
1139         // Apply flags
1140         Bank_SetFlags(uid, mask, value);
1141         
1142         // Return OK
1143         sendf(Client->Socket, "200 User Updated\n");
1144 }
1145
1146 // --- INTERNAL HELPERS ---
1147 int sendf(int Socket, const char *Format, ...)
1148 {
1149         va_list args;
1150          int    len;
1151         
1152         va_start(args, Format);
1153         len = vsnprintf(NULL, 0, Format, args);
1154         va_end(args);
1155         
1156         {
1157                 char    buf[len+1];
1158                 va_start(args, Format);
1159                 vsnprintf(buf, len+1, Format, args);
1160                 va_end(args);
1161                 
1162                 #if DEBUG_TRACE_CLIENT
1163                 printf("sendf: %s", buf);
1164                 #endif
1165                 
1166                 return send(Socket, buf, len, 0);
1167         }
1168 }
1169
1170 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1171 {
1172         struct {
1173                 const char      *Name;
1174                  int    Mask;
1175                  int    Value;
1176         }       cFLAGS[] = {
1177                  {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1178                 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1179                 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1180                 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1181                 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1182         };
1183         const int       ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1184         
1185         char    *space;
1186         
1187         *Mask = 0;
1188         *Value = 0;
1189         
1190         do {
1191                  int    bRemove = 0;
1192                  int    i;
1193                  int    len;
1194                 
1195                 while( *Str == ' ' )    Str ++; // Eat whitespace
1196                 space = strchr(Str, ',');       // Find the end of the flag
1197                 if(space)
1198                         len = space - Str;
1199                 else
1200                         len = strlen(Str);
1201                 
1202                 // Check for inversion/removal
1203                 if( *Str == '!' || *Str == '-' ) {
1204                         bRemove = 1;
1205                         Str ++;
1206                 }
1207                 else if( *Str == '+' ) {
1208                         Str ++;
1209                 }
1210                 
1211                 // Check flag values
1212                 for( i = 0; i < ciNumFlags; i ++ )
1213                 {
1214                         if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1215                                 *Mask |= cFLAGS[i].Mask;
1216                                 *Value &= ~cFLAGS[i].Mask;
1217                                 if( !bRemove )
1218                                         *Value |= cFLAGS[i].Value;
1219                                 break;
1220                         }
1221                 }
1222                 
1223                 // Error check
1224                 if( i == ciNumFlags ) {
1225                         char    val[len+1];
1226                         strncpy(val, Str, len+1);
1227                         sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);
1228                         return -1;
1229                 }
1230                 
1231                 Str = space + 1;
1232         } while(space);
1233         
1234         return 0;
1235 }

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