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

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