Fixed client error, added dead snack catch, fixing server io redirection
[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 <fcntl.h>      // O_*
18 #include <string.h>
19 #include <limits.h>
20 #include <stdarg.h>
21 #include <signal.h>
22
23 #define DEBUG_TRACE_CLIENT      0
24 #define HACK_NO_REFUNDS 1
25
26 // Statistics
27 #define MAX_CONNECTION_QUEUE    5
28 #define INPUT_BUFFER_SIZE       256
29 #define CLIENT_TIMEOUT  10      // Seconds
30
31 #define HASH_TYPE       SHA1
32 #define HASH_LENGTH     20
33
34 #define MSG_STR_TOO_LONG        "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
35
36 // === TYPES ===
37 typedef struct sClient
38 {
39          int    Socket; // Client socket ID
40          int    ID;     // Client ID
41          
42          int    bIsTrusted;     // Is the connection from a trusted host/port
43         
44         char    *Username;
45         char    Salt[9];
46         
47          int    UID;
48          int    EffectiveUID;
49          int    bIsAuthed;
50 }       tClient;
51
52 // === PROTOTYPES ===
53 void    Server_Start(void);
54 void    Server_Cleanup(void);
55 void    Server_HandleClient(int Socket, int bTrusted);
56 void    Server_ParseClientCommand(tClient *Client, char *CommandString);
57 // --- Commands ---
58 void    Server_Cmd_USER(tClient *Client, char *Args);
59 void    Server_Cmd_PASS(tClient *Client, char *Args);
60 void    Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
61 void    Server_Cmd_SETEUSER(tClient *Client, char *Args);
62 void    Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
63 void    Server_Cmd_ITEMINFO(tClient *Client, char *Args);
64 void    Server_Cmd_DISPENSE(tClient *Client, char *Args);
65 void    Server_Cmd_REFUND(tClient *Client, char *Args);
66 void    Server_Cmd_GIVE(tClient *Client, char *Args);
67 void    Server_Cmd_DONATE(tClient *Client, char *Args);
68 void    Server_Cmd_ADD(tClient *Client, char *Args);
69 void    Server_Cmd_SET(tClient *Client, char *Args);
70 void    Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
71 void    Server_Cmd_USERINFO(tClient *Client, char *Args);
72 void    _SendUserInfo(tClient *Client, int UserID);
73 void    Server_Cmd_USERADD(tClient *Client, char *Args);
74 void    Server_Cmd_USERFLAGS(tClient *Client, char *Args);
75 void    Server_Cmd_UPDATEITEM(tClient *Client, char *Args);
76 // --- Helpers ---
77 void    Debug(tClient *Client, const char *Format, ...);
78  int    sendf(int Socket, const char *Format, ...);
79  int    Server_int_ParseArgs(int bUseLongArg, char *ArgStr, ...);
80  int    Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value);
81
82 // === CONSTANTS ===
83 // - Commands
84 const struct sClientCommand {
85         const char      *Name;
86         void    (*Function)(tClient *Client, char *Arguments);
87 }       gaServer_Commands[] = {
88         {"USER", Server_Cmd_USER},
89         {"PASS", Server_Cmd_PASS},
90         {"AUTOAUTH", Server_Cmd_AUTOAUTH},
91         {"SETEUSER", Server_Cmd_SETEUSER},
92         {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
93         {"ITEM_INFO", Server_Cmd_ITEMINFO},
94         {"DISPENSE", Server_Cmd_DISPENSE},
95         {"REFUND", Server_Cmd_REFUND},
96         {"GIVE", Server_Cmd_GIVE},
97         {"DONATE", Server_Cmd_DONATE},
98         {"ADD", Server_Cmd_ADD},
99         {"SET", Server_Cmd_SET},
100         {"ENUM_USERS", Server_Cmd_ENUMUSERS},
101         {"USER_INFO", Server_Cmd_USERINFO},
102         {"USER_ADD", Server_Cmd_USERADD},
103         {"USER_FLAGS", Server_Cmd_USERFLAGS},
104         {"UPDATE_ITEM", Server_Cmd_UPDATEITEM}
105 };
106 #define NUM_COMMANDS    ((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
107
108 // === GLOBALS ===
109 // - Configuration
110  int    giServer_Port = 11020;
111  int    gbServer_RunInBackground = 0;
112 char    *gsServer_LogFile = "/var/log/dispsrv.log";
113 char    *gsServer_ErrorLog = "/var/log/dispsrv.err";
114 // - State variables
115  int    giServer_Socket;        // Server socket
116  int    giServer_NextClientID = 1;      // Debug client ID
117  
118
119 // === CODE ===
120 /**
121  * \brief Open listenting socket and serve connections
122  */
123 void Server_Start(void)
124 {
125          int    client_socket;
126         struct sockaddr_in      server_addr, client_addr;
127
128         atexit(Server_Cleanup);
129         // Ignore SIGPIPE (stops crashes when the client exits early)
130         signal(SIGPIPE, SIG_IGN);
131
132         // Create Server
133         giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
134         if( giServer_Socket < 0 ) {
135                 fprintf(stderr, "ERROR: Unable to create server socket\n");
136                 return ;
137         }
138         
139         // Make listen address
140         memset(&server_addr, 0, sizeof(server_addr));
141         server_addr.sin_family = AF_INET;       // Internet Socket
142         server_addr.sin_addr.s_addr = htonl(INADDR_ANY);        // Listen on all interfaces
143         server_addr.sin_port = htons(giServer_Port);    // Port
144
145         // Bind
146         if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
147                 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
148                 perror("Binding");
149                 return ;
150         }
151
152         // Fork into background
153         if( gbServer_RunInBackground )
154         {
155                 int pid = fork();
156                 if( pid == -1 ) {
157                         fprintf(stderr, "ERROR: Unable to fork\n");
158                         perror("fork background");
159                         exit(-1);
160                 }
161                 if( pid != 0 ) {
162                         // Parent, quit
163                         exit(0);
164                 }
165                 // In child
166                 // - Sort out stdin/stdout
167                 #if 0
168                 dup2( open("/dev/null", O_RDONLY, 0644), STDIN_FILENO );
169                 dup2( open(gsServer_LogFile, O_CREAT|O_APPEND, 0644), STDOUT_FILENO );
170                 dup2( open(gsServer_ErrorLog, O_CREAT|O_APPEND, 0644), STDERR_FILENO );
171                 #else
172                 freopen("/dev/null", "r", stdin);
173                 freopen(gsServer_LogFile, "a", stdout);
174                 freopen(gsServer_ErrorLog, "a", stderr);
175                 #endif
176         }
177
178         // Start the helper thread
179         StartPeriodicThread();
180         
181         // Listen
182         if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
183                 fprintf(stderr, "ERROR: Unable to listen to socket\n");
184                 perror("Listen");
185                 return ;
186         }
187         
188         printf("Listening on 0.0.0.0:%i\n", giServer_Port);
189         
190         // write pidfile
191         {
192                 FILE *fp = fopen("/var/run/dispsrv.pid", "w");
193                 if( fp ) {
194                         fprintf(fp, "%i", getpid());
195                         fclose(fp);
196                 }
197         }
198
199         for(;;)
200         {
201                 uint    len = sizeof(client_addr);
202                  int    bTrusted = 0;
203                 
204                 // Accept a connection
205                 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
206                 if(client_socket < 0) {
207                         fprintf(stderr, "ERROR: Unable to accept client connection\n");
208                         return ;
209                 }
210                 
211                 // Set a timeout on the user conneciton
212                 {
213                         struct timeval tv;
214                         tv.tv_sec = CLIENT_TIMEOUT;
215                         tv.tv_usec = 0;
216                         if( setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) )
217                         {
218                                 perror("setsockopt");
219                                 return ;
220                         }
221                 }
222                 
223                 // Debug: Print the connection string
224                 if(giDebugLevel >= 2) {
225                         char    ipstr[INET_ADDRSTRLEN];
226                         inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
227                         printf("Client connection from %s:%i\n",
228                                 ipstr, ntohs(client_addr.sin_port));
229                 }
230                 
231                 // Doesn't matter what, localhost is trusted
232                 if( ntohl( client_addr.sin_addr.s_addr ) == 0x7F000001 )
233                         bTrusted = 1;
234                 
235                 // Trusted Connections
236                 if( ntohs(client_addr.sin_port) < 1024 )
237                 {
238                         // TODO: Make this runtime configurable
239                         switch( ntohl( client_addr.sin_addr.s_addr ) )
240                         {
241                         case 0x7F000001:        // 127.0.0.1    localhost
242                 //      case 0x825F0D00:        // 130.95.13.0
243                         case 0x825F0D04:        // 130.95.13.4  merlo
244                         case 0x825F0D05:        // 130.95.13.5  heathred (MR)
245                         case 0x825F0D07:        // 130.95.13.7  motsugo
246                         case 0x825F0D11:        // 130.95.13.17 mermaid
247                         case 0x825F0D12:        // 130.95.13.18 mussel
248                         case 0x825F0D17:        // 130.95.13.23 martello
249                         case 0x825F0D2A:        // 130.95.13.42 meersau
250                         case 0x825F0D42:        // 130.95.13.66 heathred (Clubroom)
251                                 bTrusted = 1;
252                                 break;
253                         default:
254                                 break;
255                         }
256                 }
257                 
258                 // TODO: Multithread this?
259                 Server_HandleClient(client_socket, bTrusted);
260                 
261                 close(client_socket);
262         }
263 }
264
265 void Server_Cleanup(void)
266 {
267         printf("\nClose(%i)\n", giServer_Socket);
268         close(giServer_Socket);
269         unlink("/var/run/dispsrv.pid");
270 }
271
272 /**
273  * \brief Reads from a client socket and parses the command strings
274  * \param Socket        Client socket number/handle
275  * \param bTrusted      Is the client trusted?
276  */
277 void Server_HandleClient(int Socket, int bTrusted)
278 {
279         char    inbuf[INPUT_BUFFER_SIZE];
280         char    *buf = inbuf;
281          int    remspace = INPUT_BUFFER_SIZE-1;
282          int    bytes = -1;
283         tClient clientInfo;
284         
285         memset(&clientInfo, 0, sizeof(clientInfo));
286         
287         // Initialise Client info
288         clientInfo.Socket = Socket;
289         clientInfo.ID = giServer_NextClientID ++;
290         clientInfo.bIsTrusted = bTrusted;
291         clientInfo.EffectiveUID = -1;
292         
293         // Read from client
294         /*
295          * Notes:
296          * - The `buf` and `remspace` variables allow a line to span several
297          *   calls to recv(), if a line is not completed in one recv() call
298          *   it is saved to the beginning of `inbuf` and `buf` is updated to
299          *   the end of it.
300          */
301         // TODO: Use select() instead (to give a timeout)
302         while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
303         {
304                 char    *eol, *start;
305                 buf[bytes] = '\0';      // Allow us to use stdlib string functions on it
306                 
307                 // Split by lines
308                 start = inbuf;
309                 while( (eol = strchr(start, '\n')) )
310                 {
311                         *eol = '\0';
312                         
313                         Server_ParseClientCommand(&clientInfo, start);
314                         
315                         start = eol + 1;
316                 }
317                 
318                 // Check if there was an incomplete line
319                 if( *start != '\0' ) {
320                          int    tailBytes = bytes - (start-buf);
321                         // Roll back in buffer
322                         memcpy(inbuf, start, tailBytes);
323                         remspace -= tailBytes;
324                         if(remspace == 0) {
325                                 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
326                                 buf = inbuf;
327                                 remspace = INPUT_BUFFER_SIZE - 1;
328                         }
329                 }
330                 else {
331                         buf = inbuf;
332                         remspace = INPUT_BUFFER_SIZE - 1;
333                 }
334         }
335         
336         // Check for errors
337         if( bytes < 0 ) {
338                 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
339                 return ;
340         }
341         
342         if(giDebugLevel >= 2) {
343                 printf("Client %i: Disconnected\n", clientInfo.ID);
344         }
345 }
346
347 /**
348  * \brief Parses a client command and calls the required helper function
349  * \param Client        Pointer to client state structure
350  * \param CommandString Command from client (single line of the command)
351  * \return Heap String to return to the client
352  */
353 void Server_ParseClientCommand(tClient *Client, char *CommandString)
354 {
355         char    *command, *args;
356          int    i;
357         
358         if( giDebugLevel >= 2 )
359                 Debug(Client, "Server_ParseClientCommand: (CommandString = '%s')", CommandString);
360         
361         if( Server_int_ParseArgs(1, CommandString, &command, &args, NULL) )
362         {
363                 if( command == NULL )   return ;
364                 // Is this an error? (just ignore for now)
365         }
366         
367         
368         // Find command
369         for( i = 0; i < NUM_COMMANDS; i++ )
370         {
371                 if(strcmp(command, gaServer_Commands[i].Name) == 0) {
372                         if( giDebugLevel >= 2 )
373                                 Debug(Client, "CMD %s - \"%s\"", command, args);
374                         gaServer_Commands[i].Function(Client, args);
375                         return ;
376                 }
377         }
378         
379         sendf(Client->Socket, "400 Unknown Command\n");
380 }
381
382 // ---
383 // Commands
384 // ---
385 /**
386  * \brief Set client username
387  * 
388  * Usage: USER <username>
389  */
390 void Server_Cmd_USER(tClient *Client, char *Args)
391 {
392         char    *username;
393         
394         if( Server_int_ParseArgs(0, Args, &username, NULL) )
395         {
396                 sendf(Client->Socket, "407 USER takes 1 argument\n");
397                 return ;
398         }
399         
400         // Debug!
401         if( giDebugLevel )
402                 Debug(Client, "Authenticating as '%s'", username);
403         
404         // Save username
405         if(Client->Username)
406                 free(Client->Username);
407         Client->Username = strdup(username);
408         
409         #if USE_SALT
410         // Create a salt (that changes if the username is changed)
411         // Yes, I know, I'm a little paranoid, but who isn't?
412         Client->Salt[0] = 0x21 + (rand()&0x3F);
413         Client->Salt[1] = 0x21 + (rand()&0x3F);
414         Client->Salt[2] = 0x21 + (rand()&0x3F);
415         Client->Salt[3] = 0x21 + (rand()&0x3F);
416         Client->Salt[4] = 0x21 + (rand()&0x3F);
417         Client->Salt[5] = 0x21 + (rand()&0x3F);
418         Client->Salt[6] = 0x21 + (rand()&0x3F);
419         Client->Salt[7] = 0x21 + (rand()&0x3F);
420         
421         // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
422         sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
423         #else
424         sendf(Client->Socket, "100 User Set\n");
425         #endif
426 }
427
428 /**
429  * \brief Authenticate as a user
430  * 
431  * Usage: PASS <hash>
432  */
433 void Server_Cmd_PASS(tClient *Client, char *Args)
434 {
435         char    *passhash;
436          int    flags;
437
438         if( Server_int_ParseArgs(0, Args, &passhash, NULL) )
439         {
440                 sendf(Client->Socket, "407 PASS takes 1 argument\n");
441                 return ;
442         }
443         
444         // Pass on to cokebank
445         Client->UID = Bank_GetUserAuth(Client->Salt, Client->Username, passhash);
446
447         if( Client->UID == -1 ) {
448                 sendf(Client->Socket, "401 Auth Failure\n");
449                 return ;
450         }
451
452         flags = Bank_GetFlags(Client->UID);
453         if( flags & USER_FLAG_DISABLED ) {
454                 Client->UID = -1;
455                 sendf(Client->Socket, "403 Account Disabled\n");
456                 return ;
457         }
458         if( flags & USER_FLAG_INTERNAL ) {
459                 Client->UID = -1;
460                 sendf(Client->Socket, "403 Internal account\n");
461                 return ;
462         }
463         
464         Client->bIsAuthed = 1;
465         sendf(Client->Socket, "200 Auth OK\n");
466 }
467
468 /**
469  * \brief Authenticate as a user without a password
470  * 
471  * Usage: AUTOAUTH <user>
472  */
473 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
474 {
475         char    *username;
476          int    userflags;
477         
478         if( Server_int_ParseArgs(0, Args, &username, NULL) )
479         {
480                 sendf(Client->Socket, "407 AUTOAUTH takes 1 argument\n");
481                 return ;
482         }
483         
484         // Check if trusted
485         if( !Client->bIsTrusted ) {
486                 if(giDebugLevel)
487                         Debug(Client, "Untrusted client attempting to AUTOAUTH");
488                 sendf(Client->Socket, "401 Untrusted\n");
489                 return ;
490         }
491         
492         // Get UID
493         Client->UID = Bank_GetAcctByName( username, 0 );        
494         if( Client->UID < 0 ) {
495                 if(giDebugLevel)
496                         Debug(Client, "Unknown user '%s'", username);
497                 sendf(Client->Socket, "403 Auth Failure\n");
498                 return ;
499         }
500         
501         userflags = Bank_GetFlags(Client->UID);
502         // You can't be an internal account
503         if( userflags & USER_FLAG_INTERNAL ) {
504                 if(giDebugLevel)
505                         Debug(Client, "Autoauth as '%s', not allowed", username);
506                 Client->UID = -1;
507                 sendf(Client->Socket, "403 Account is internal\n");
508                 return ;
509         }
510
511         // Disabled accounts
512         if( userflags & USER_FLAG_DISABLED ) {
513                 Client->UID = -1;
514                 sendf(Client->Socket, "403 Account disabled\n");
515                 return ;
516         }
517
518         Client->bIsAuthed = 1;
519         
520         if(giDebugLevel)
521                 Debug(Client, "Auto authenticated as '%s' (%i)", username, Client->UID);
522         
523         sendf(Client->Socket, "200 Auth OK\n");
524 }
525
526 /**
527  * \brief Set effective user
528  */
529 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
530 {
531         char    *username;
532          int    eUserFlags, userFlags;
533         
534         if( Server_int_ParseArgs(0, Args, &username, NULL) )
535         {
536                 sendf(Client->Socket, "407 SETEUSER takes 1 argument\n");
537                 return ;
538         }
539         
540         if( !strlen(Args) ) {
541                 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
542                 return ;
543         }
544         
545         // Check authentication
546         if( !Client->bIsAuthed ) {
547                 sendf(Client->Socket, "401 Not Authenticated\n");
548                 return ;
549         }
550
551         // Check user permissions
552         userFlags = Bank_GetFlags(Client->UID);
553         if( !(userFlags & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
554                 sendf(Client->Socket, "403 Not in coke\n");
555                 return ;
556         }
557         
558         // Set id
559         Client->EffectiveUID = Bank_GetAcctByName(username, 0);
560         if( Client->EffectiveUID == -1 ) {
561                 sendf(Client->Socket, "404 User not found\n");
562                 return ;
563         }
564         
565         // You can't be an internal account
566         if( !(userFlags & USER_FLAG_ADMIN) )
567         {
568                 eUserFlags = Bank_GetFlags(Client->EffectiveUID);
569                 if( eUserFlags & USER_FLAG_INTERNAL ) {
570                         Client->EffectiveUID = -1;
571                         sendf(Client->Socket, "404 User not found\n");
572                         return ;
573                 }
574                 // Disabled only avaliable to admins
575                 if( eUserFlags & USER_FLAG_DISABLED ) {
576                         Client->EffectiveUID = -1;
577                         sendf(Client->Socket, "403 Account disabled\n");
578                         return ;
579                 }
580         }
581         
582         sendf(Client->Socket, "200 User set\n");
583 }
584
585 /**
586  * \brief Send an item status to the client
587  * \param Client        Who to?
588  * \param Item  Item to send
589  */
590 void Server_int_SendItem(tClient *Client, tItem *Item)
591 {
592         char    *status = "avail";
593         
594         if( Item->Handler->CanDispense )
595         {
596                 switch(Item->Handler->CanDispense(Client->UID, Item->ID))
597                 {
598                 case  0:        status = "avail";       break;
599                 case  1:        status = "sold";        break;
600                 default:
601                 case -1:        status = "error";       break;
602                 }
603         }
604         
605         // KNOWN HACK: Naming a slot 'dead' disables it
606         if( strcmp(Item->Name, "dead") == 0 )
607                 status = "sold";        // Another status?
608         
609         sendf(Client->Socket,
610                 "202 Item %s:%i %s %i %s\n",
611                 Item->Handler->Name, Item->ID, status, Item->Price, Item->Name
612                 );
613 }
614
615 /**
616  * \brief Enumerate the items that the server knows about
617  */
618 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
619 {
620          int    i, count;
621
622         if( Args != NULL && strlen(Args) ) {
623                 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
624                 return ;
625         }
626         
627         // Count shown items
628         count = 0;
629         for( i = 0; i < giNumItems; i ++ ) {
630                 if( gaItems[i].bHidden )        continue;
631                 count ++;
632         }
633
634         sendf(Client->Socket, "201 Items %i\n", count);
635
636         for( i = 0; i < giNumItems; i ++ ) {
637                 if( gaItems[i].bHidden )        continue;
638                 Server_int_SendItem( Client, &gaItems[i] );
639         }
640
641         sendf(Client->Socket, "200 List end\n");
642 }
643
644 tItem *_GetItemFromString(char *String)
645 {
646         tHandler        *handler;
647         char    *type = String;
648         char    *colon = strchr(String, ':');
649          int    num, i;
650         
651         if( !colon ) {
652                 return NULL;
653         }
654
655         num = atoi(colon+1);
656         *colon = '\0';
657
658         // Find handler
659         handler = NULL;
660         for( i = 0; i < giNumHandlers; i ++ )
661         {
662                 if( strcmp(gaHandlers[i]->Name, type) == 0) {
663                         handler = gaHandlers[i];
664                         break;
665                 }
666         }
667         if( !handler ) {
668                 return NULL;
669         }
670
671         // Find item
672         for( i = 0; i < giNumItems; i ++ )
673         {
674                 if( gaItems[i].Handler != handler )     continue;
675                 if( gaItems[i].ID != num )      continue;
676                 return &gaItems[i];
677         }
678         return NULL;
679 }
680
681 /**
682  * \brief Fetch information on a specific item
683  */
684 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
685 {
686         tItem   *item;
687         char    *itemname;
688         
689         if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
690                 sendf(Client->Socket, "407 ITEMINFO takes 1 argument\n");
691                 return ;
692         }
693         item = _GetItemFromString(Args);
694         
695         if( !item ) {
696                 sendf(Client->Socket, "406 Bad Item ID\n");
697                 return ;
698         }
699         
700         Server_int_SendItem( Client, item );
701 }
702
703 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
704 {
705         tItem   *item;
706          int    ret;
707          int    uid;
708         char    *itemname;
709         
710         if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
711                 sendf(Client->Socket, "407 DISPENSE takes only 1 argument\n");
712                 return ;
713         }
714          
715         if( !Client->bIsAuthed ) {
716                 sendf(Client->Socket, "401 Not Authenticated\n");
717                 return ;
718         }
719
720         item = _GetItemFromString(itemname);
721         if( !item ) {
722                 sendf(Client->Socket, "406 Bad Item ID\n");
723                 return ;
724         }
725         
726         if( Client->EffectiveUID != -1 ) {
727                 uid = Client->EffectiveUID;
728         }
729         else {
730                 uid = Client->UID;
731         }
732
733         switch( ret = DispenseItem( Client->UID, uid, item ) )
734         {
735         case 0: sendf(Client->Socket, "200 Dispense OK\n");     return ;
736         case 1: sendf(Client->Socket, "501 Unable to dispense\n");      return ;
737         case 2: sendf(Client->Socket, "402 Poor You\n");        return ;
738         default:
739                 sendf(Client->Socket, "500 Dispense Error (%i)\n", ret);
740                 return ;
741         }
742 }
743
744 void Server_Cmd_REFUND(tClient *Client, char *Args)
745 {
746         tItem   *item;
747          int    uid, price_override = 0;
748         char    *username, *itemname, *price_str;
749
750         if( Server_int_ParseArgs(0, Args, &username, &itemname, &price_str, NULL) ) {
751                 if( !itemname || price_str ) {
752                         sendf(Client->Socket, "407 REFUND takes 2 or 3 arguments\n");
753                         return ;
754                 }
755         }
756
757         if( !Client->bIsAuthed ) {
758                 sendf(Client->Socket, "401 Not Authenticated\n");
759                 return ;
760         }
761
762         // Check user permissions
763         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
764                 sendf(Client->Socket, "403 Not in coke\n");
765                 return ;
766         }
767
768         uid = Bank_GetAcctByName(username, 0);
769         if( uid == -1 ) {
770                 sendf(Client->Socket, "404 Unknown user\n");
771                 return ;
772         }
773         
774         item = _GetItemFromString(itemname);
775         if( !item ) {
776                 sendf(Client->Socket, "406 Bad Item ID\n");
777                 return ;
778         }
779
780         if( price_str )
781                 price_override = atoi(price_str);
782
783         switch( DispenseRefund( Client->UID, uid, item, price_override ) )
784         {
785         case 0: sendf(Client->Socket, "200 Item Refunded\n");   return ;
786         default:
787                 sendf(Client->Socket, "500 Dispense Error\n");
788                 return;
789         }
790 }
791
792 void Server_Cmd_GIVE(tClient *Client, char *Args)
793 {
794         char    *recipient, *ammount, *reason;
795          int    uid, iAmmount;
796          int    thisUid;
797         
798         // Parse arguments
799         if( Server_int_ParseArgs(1, Args, &recipient, &ammount, &reason, NULL) ) {
800                 sendf(Client->Socket, "407 GIVE takes only 3 arguments\n");
801                 return ;
802         }
803         
804         // Check for authed
805         if( !Client->bIsAuthed ) {
806                 sendf(Client->Socket, "401 Not Authenticated\n");
807                 return ;
808         }
809
810         // Get recipient
811         uid = Bank_GetAcctByName(recipient, 0);
812         if( uid == -1 ) {
813                 sendf(Client->Socket, "404 Invalid target 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 target user\n");
820 //              return ;
821 //      }
822
823         // Parse ammount
824         iAmmount = atoi(ammount);
825         if( iAmmount <= 0 ) {
826                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
827                 return ;
828         }
829         
830         if( Client->EffectiveUID != -1 ) {
831                 thisUid = Client->EffectiveUID;
832         }
833         else {
834                 thisUid = Client->UID;
835         }
836
837         // Do give
838         switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
839         {
840         case 0:
841                 sendf(Client->Socket, "200 Give OK\n");
842                 return ;
843         case 2:
844                 sendf(Client->Socket, "402 Poor You\n");
845                 return ;
846         default:
847                 sendf(Client->Socket, "500 Unknown error\n");
848                 return ;
849         }
850 }
851
852 void Server_Cmd_DONATE(tClient *Client, char *Args)
853 {
854         char    *ammount, *reason;
855          int    iAmmount;
856          int    thisUid;
857         
858         // Parse arguments
859         if( Server_int_ParseArgs(1, Args, &ammount, &reason, NULL) ) {
860                 sendf(Client->Socket, "407 DONATE takes 2 arguments\n");
861                 return ;
862         }
863         
864         if( !Client->bIsAuthed ) {
865                 sendf(Client->Socket, "401 Not Authenticated\n");
866                 return ;
867         }
868
869         // Parse ammount
870         iAmmount = atoi(ammount);
871         if( iAmmount <= 0 ) {
872                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
873                 return ;
874         }
875         
876         // Handle effective users
877         if( Client->EffectiveUID != -1 ) {
878                 thisUid = Client->EffectiveUID;
879         }
880         else {
881                 thisUid = Client->UID;
882         }
883
884         // Do give
885         switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
886         {
887         case 0:
888                 sendf(Client->Socket, "200 Give OK\n");
889                 return ;
890         case 2:
891                 sendf(Client->Socket, "402 Poor You\n");
892                 return ;
893         default:
894                 sendf(Client->Socket, "500 Unknown error\n");
895                 return ;
896         }
897 }
898
899 void Server_Cmd_ADD(tClient *Client, char *Args)
900 {
901         char    *user, *ammount, *reason;
902          int    uid, iAmmount;
903         
904         // Parse arguments
905         if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
906                 sendf(Client->Socket, "407 ADD takes 3 arguments\n");
907                 return ;
908         }
909         
910         if( !Client->bIsAuthed ) {
911                 sendf(Client->Socket, "401 Not Authenticated\n");
912                 return ;
913         }
914
915         // Check user permissions
916         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
917                 sendf(Client->Socket, "403 Not in coke\n");
918                 return ;
919         }
920
921         #if HACK_NO_REFUNDS
922         if( strstr(reason, "refund") != NULL || strstr(reason, "misdispense") != NULL )
923         {
924                 sendf(Client->Socket, "499 Don't use `dispense acct` for refunds, use `dispense refund` (and `dispense -G` to get item IDs)\n");
925                 return ;
926         }
927         #endif
928
929         // Get recipient
930         uid = Bank_GetAcctByName(user, 0);
931         if( uid == -1 ) {
932                 sendf(Client->Socket, "404 Invalid user\n");
933                 return ;
934         }
935         
936         // You can't alter an internal account
937         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) )
938         {
939                 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
940                         sendf(Client->Socket, "404 Invalid user\n");
941                         return ;
942                 }
943                 // TODO: Maybe disallow changes to disabled?
944         }
945
946         // Parse ammount
947         iAmmount = atoi(ammount);
948         if( iAmmount == 0 && ammount[0] != '0' ) {
949                 sendf(Client->Socket, "407 Invalid Argument\n");
950                 return ;
951         }
952
953         // Do give
954         switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
955         {
956         case 0:
957                 sendf(Client->Socket, "200 Add OK\n");
958                 return ;
959         case 2:
960                 sendf(Client->Socket, "402 Poor Guy\n");
961                 return ;
962         default:
963                 sendf(Client->Socket, "500 Unknown error\n");
964                 return ;
965         }
966 }
967
968 void Server_Cmd_SET(tClient *Client, char *Args)
969 {
970         char    *user, *ammount, *reason;
971          int    uid, iAmmount;
972         
973         // Parse arguments
974         if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
975                 sendf(Client->Socket, "407 SET takes 3 arguments\n");
976                 return ;
977         }
978         
979         if( !Client->bIsAuthed ) {
980                 sendf(Client->Socket, "401 Not Authenticated\n");
981                 return ;
982         }
983
984         // Check user permissions
985         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN)  ) {
986                 sendf(Client->Socket, "403 Not an admin\n");
987                 return ;
988         }
989
990         // Get recipient
991         uid = Bank_GetAcctByName(user, 0);
992         if( uid == -1 ) {
993                 sendf(Client->Socket, "404 Invalid user\n");
994                 return ;
995         }
996
997         // Parse ammount
998         iAmmount = atoi(ammount);
999         if( iAmmount == 0 && ammount[0] != '0' ) {
1000                 sendf(Client->Socket, "407 Invalid Argument\n");
1001                 return ;
1002         }
1003
1004         // Do give
1005         switch( DispenseSet(Client->UID, uid, iAmmount, reason) )
1006         {
1007         case 0:
1008                 sendf(Client->Socket, "200 Add OK\n");
1009                 return ;
1010         case 2:
1011                 sendf(Client->Socket, "402 Poor Guy\n");
1012                 return ;
1013         default:
1014                 sendf(Client->Socket, "500 Unknown error\n");
1015                 return ;
1016         }
1017 }
1018
1019 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
1020 {
1021          int    i, numRet = 0;
1022         tAcctIterator   *it;
1023          int    maxBal = INT_MAX, minBal = INT_MIN;
1024          int    flagMask = 0, flagVal = 0;
1025          int    sort = BANK_ITFLAG_SORT_NAME;
1026         time_t  lastSeenAfter=0, lastSeenBefore=0;
1027         
1028          int    flags;  // Iterator flags
1029          int    balValue;       // Balance value for iterator
1030         time_t  timeValue;      // Time value for iterator
1031         
1032         // Parse arguments
1033         if( Args && strlen(Args) )
1034         {
1035                 char    *space = Args, *type, *val;
1036                 do
1037                 {
1038                         type = space;
1039                         while(*type == ' ')     type ++;
1040                         // Get next space
1041                         space = strchr(space, ' ');
1042                         if(space)       *space = '\0';
1043                         
1044                         // Get type
1045                         val = strchr(type, ':');
1046                         if( val ) {
1047                                 *val = '\0';
1048                                 val ++;
1049                                 
1050                                 // Types
1051                                 // - Minium Balance
1052                                 if( strcmp(type, "min_balance") == 0 ) {
1053                                         minBal = atoi(val);
1054                                 }
1055                                 // - Maximum Balance
1056                                 else if( strcmp(type, "max_balance") == 0 ) {
1057                                         maxBal = atoi(val);
1058                                 }
1059                                 // - Flags
1060                                 else if( strcmp(type, "flags") == 0 ) {
1061                                         if( Server_int_ParseFlags(Client, val, &flagMask, &flagVal) )
1062                                                 return ;
1063                                 }
1064                                 // - Last seen before timestamp
1065                                 else if( strcmp(type, "last_seen_before") == 0 ) {
1066                                         lastSeenAfter = atoll(val);
1067                                 }
1068                                 // - Last seen after timestamp
1069                                 else if( strcmp(type, "last_seen_after") == 0 ) {
1070                                         lastSeenAfter = atoll(val);
1071                                 }
1072                                 // - Sorting 
1073                                 else if( strcmp(type, "sort") == 0 ) {
1074                                         char    *dash = strchr(val, '-');
1075                                         if( dash ) {
1076                                                 *dash = '\0';
1077                                                 dash ++;
1078                                         }
1079                                         if( strcmp(val, "name") == 0 ) {
1080                                                 sort = BANK_ITFLAG_SORT_NAME;
1081                                         }
1082                                         else if( strcmp(val, "balance") == 0 ) {
1083                                                 sort = BANK_ITFLAG_SORT_BAL;
1084                                         }
1085                                         else if( strcmp(val, "lastseen") == 0 ) {
1086                                                 sort = BANK_ITFLAG_SORT_LASTSEEN;
1087                                         }
1088                                         else {
1089                                                 sendf(Client->Socket, "407 Unknown sort field ('%s')\n", val);
1090                                                 return ;
1091                                         }
1092                                         // Handle sort direction
1093                                         if( dash ) {
1094                                                 if( strcmp(dash, "desc") == 0 ) {
1095                                                         sort |= BANK_ITFLAG_REVSORT;
1096                                                 }
1097                                                 else {
1098                                                         sendf(Client->Socket, "407 Unknown sort direction '%s'\n", dash);
1099                                                         return ;
1100                                                 }
1101                                                 dash[-1] = '-';
1102                                         }
1103                                 }
1104                                 else {
1105                                         sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s:%s'\n", type, val);
1106                                         return ;
1107                                 }
1108                                 
1109                                 val[-1] = ':';
1110                         }
1111                         else {
1112                                 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s'\n", type);
1113                                 return ;
1114                         }
1115                         
1116                         // Eat whitespace
1117                         if( space ) {
1118                                 *space = ' ';   // Repair (to be nice)
1119                                 space ++;
1120                                 while(*space == ' ')    space ++;
1121                         }
1122                 }       while(space);
1123         }
1124         
1125         // Create iterator
1126         if( maxBal != INT_MAX ) {
1127                 flags = sort|BANK_ITFLAG_MAXBALANCE;
1128                 balValue = maxBal;
1129         }
1130         else if( minBal != INT_MIN ) {
1131                 flags = sort|BANK_ITFLAG_MINBALANCE;
1132                 balValue = minBal;
1133         }
1134         else {
1135                 flags = sort;
1136                 balValue = 0;
1137         }
1138         if( lastSeenBefore ) {
1139                 timeValue = lastSeenBefore;
1140                 flags |= BANK_ITFLAG_SEENBEFORE;
1141         }
1142         else if( lastSeenAfter ) {
1143                 timeValue = lastSeenAfter;
1144                 flags |= BANK_ITFLAG_SEENAFTER;
1145         }
1146         else {
1147                 timeValue = 0;
1148         }
1149         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1150         
1151         // Get return number
1152         while( (i = Bank_IteratorNext(it)) != -1 )
1153         {
1154                 int bal = Bank_GetBalance(i);
1155                 
1156                 if( bal == INT_MIN )    continue;
1157                 
1158                 if( bal < minBal )      continue;
1159                 if( bal > maxBal )      continue;
1160                 
1161                 numRet ++;
1162         }
1163         
1164         Bank_DelIterator(it);
1165         
1166         // Send count
1167         sendf(Client->Socket, "201 Users %i\n", numRet);
1168         
1169         
1170         // Create iterator
1171         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1172         
1173         while( (i = Bank_IteratorNext(it)) != -1 )
1174         {
1175                 int bal = Bank_GetBalance(i);
1176                 
1177                 if( bal == INT_MIN )    continue;
1178                 
1179                 if( bal < minBal )      continue;
1180                 if( bal > maxBal )      continue;
1181                 
1182                 _SendUserInfo(Client, i);
1183         }
1184         
1185         Bank_DelIterator(it);
1186         
1187         sendf(Client->Socket, "200 List End\n");
1188 }
1189
1190 void Server_Cmd_USERINFO(tClient *Client, char *Args)
1191 {
1192          int    uid;
1193         char    *user;
1194         
1195         // Parse arguments
1196         if( Server_int_ParseArgs(0, Args, &user, NULL) ) {
1197                 sendf(Client->Socket, "407 USER_INFO takes 1 argument\n");
1198                 return ;
1199         }
1200         
1201         if( giDebugLevel )      Debug(Client, "User Info '%s'", user);
1202         
1203         // Get recipient
1204         uid = Bank_GetAcctByName(user, 0);
1205         
1206         if( giDebugLevel >= 2 ) Debug(Client, "uid = %i", uid);
1207         if( uid == -1 ) {
1208                 sendf(Client->Socket, "404 Invalid user\n");
1209                 return ;
1210         }
1211         
1212         _SendUserInfo(Client, uid);
1213 }
1214
1215 void _SendUserInfo(tClient *Client, int UserID)
1216 {
1217         char    *type, *disabled="", *door="";
1218          int    flags = Bank_GetFlags(UserID);
1219         
1220         if( flags & USER_FLAG_INTERNAL ) {
1221                 type = "internal";
1222         }
1223         else if( flags & USER_FLAG_COKE ) {
1224                 if( flags & USER_FLAG_ADMIN )
1225                         type = "coke,admin";
1226                 else
1227                         type = "coke";
1228         }
1229         else if( flags & USER_FLAG_ADMIN ) {
1230                 type = "admin";
1231         }
1232         else {
1233                 type = "user";
1234         }
1235         
1236         if( flags & USER_FLAG_DISABLED )
1237                 disabled = ",disabled";
1238         if( flags & USER_FLAG_DOORGROUP )
1239                 door = ",door";
1240         
1241         // TODO: User flags/type
1242         sendf(
1243                 Client->Socket, "202 User %s %i %s%s%s\n",
1244                 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
1245                 type, disabled, door
1246                 );
1247 }
1248
1249 void Server_Cmd_USERADD(tClient *Client, char *Args)
1250 {
1251         char    *username;
1252         
1253         // Parse arguments
1254         if( Server_int_ParseArgs(0, Args, &username, NULL) ) {
1255                 sendf(Client->Socket, "407 USER_ADD takes 1 argument\n");
1256                 return ;
1257         }
1258         
1259         // Check authentication
1260         if( !Client->bIsAuthed ) {
1261                 sendf(Client->Socket, "401 Not Authenticated\n");
1262                 return ;
1263         }
1264         
1265         // Check permissions
1266         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1267                 sendf(Client->Socket, "403 Not a coke admin\n");
1268                 return ;
1269         }
1270         
1271         // Try to create user
1272         if( Bank_CreateAcct(username) == -1 ) {
1273                 sendf(Client->Socket, "404 User exists\n");
1274                 return ;
1275         }
1276         
1277         {
1278                 char    *thisName = Bank_GetAcctName(Client->UID);
1279                 Log_Info("Account '%s' created by '%s'", username, thisName);
1280                 free(thisName);
1281         }
1282         
1283         sendf(Client->Socket, "200 User Added\n");
1284 }
1285
1286 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
1287 {
1288         char    *username, *flags, *reason=NULL;
1289          int    mask=0, value=0;
1290          int    uid;
1291         
1292         // Parse arguments
1293         if( Server_int_ParseArgs(1, Args, &username, &flags, &reason, NULL) ) {
1294                 if( !flags ) {
1295                         sendf(Client->Socket, "407 USER_FLAGS takes at least 2 arguments\n");
1296                         return ;
1297                 }
1298                 reason = "";
1299         }
1300         
1301         // Check authentication
1302         if( !Client->bIsAuthed ) {
1303                 sendf(Client->Socket, "401 Not Authenticated\n");
1304                 return ;
1305         }
1306         
1307         // Check permissions
1308         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1309                 sendf(Client->Socket, "403 Not a coke admin\n");
1310                 return ;
1311         }
1312         
1313         // Get UID
1314         uid = Bank_GetAcctByName(username, 0);
1315         if( uid == -1 ) {
1316                 sendf(Client->Socket, "404 User '%s' not found\n", username);
1317                 return ;
1318         }
1319         
1320         // Parse flags
1321         if( Server_int_ParseFlags(Client, flags, &mask, &value) )
1322                 return ;
1323         
1324         if( giDebugLevel )
1325                 Debug(Client, "Set %i(%s) flags to %x (masked %x)\n",
1326                         uid, username, mask, value);
1327         
1328         // Apply flags
1329         Bank_SetFlags(uid, mask, value);
1330
1331         // Log the change
1332         Log_Info("Updated '%s' with flag set '%s' - Reason: %s",
1333                 username, flags, reason);
1334         
1335         // Return OK
1336         sendf(Client->Socket, "200 User Updated\n");
1337 }
1338
1339 void Server_Cmd_UPDATEITEM(tClient *Client, char *Args)
1340 {
1341         char    *itemname, *price_str, *description;
1342          int    price;
1343         tItem   *item;
1344         
1345         if( Server_int_ParseArgs(1, Args, &itemname, &price_str, &description, NULL) ) {
1346                 sendf(Client->Socket, "407 UPDATE_ITEM takes 3 arguments\n");
1347                 return ;
1348         }
1349         
1350         if( !Client->bIsAuthed ) {
1351                 sendf(Client->Socket, "401 Not Authenticated\n");
1352                 return ;
1353         }
1354
1355         // Check user permissions
1356         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
1357                 sendf(Client->Socket, "403 Not in coke\n");
1358                 return ;
1359         }
1360         
1361         item = _GetItemFromString(itemname);
1362         if( !item ) {
1363                 // TODO: Create item?
1364                 sendf(Client->Socket, "406 Bad Item ID\n");
1365                 return ;
1366         }
1367         
1368         price = atoi(price_str);
1369         if( price <= 0 && price_str[0] != '0' ) {
1370                 sendf(Client->Socket, "407 Invalid price set\n");
1371         }
1372         
1373         switch( DispenseUpdateItem( Client->UID, item, description, price ) )
1374         {
1375         case 0:
1376                 // Return OK
1377                 sendf(Client->Socket, "200 Item updated\n");
1378                 break;
1379         default:
1380                 break;
1381         }
1382 }
1383
1384 // --- INTERNAL HELPERS ---
1385 void Debug(tClient *Client, const char *Format, ...)
1386 {
1387         va_list args;
1388         //printf("%010i [%i] ", (int)time(NULL), Client->ID);
1389         printf("[%i] ", Client->ID);
1390         va_start(args, Format);
1391         vprintf(Format, args);
1392         va_end(args);
1393         printf("\n");
1394 }
1395
1396 int sendf(int Socket, const char *Format, ...)
1397 {
1398         va_list args;
1399          int    len;
1400         
1401         va_start(args, Format);
1402         len = vsnprintf(NULL, 0, Format, args);
1403         va_end(args);
1404         
1405         {
1406                 char    buf[len+1];
1407                 va_start(args, Format);
1408                 vsnprintf(buf, len+1, Format, args);
1409                 va_end(args);
1410                 
1411                 #if DEBUG_TRACE_CLIENT
1412                 printf("sendf: %s", buf);
1413                 #endif
1414                 
1415                 return send(Socket, buf, len, 0);
1416         }
1417 }
1418
1419 // Takes a series of char *'s in
1420 /**
1421  * \brief Parse space-separated entries into 
1422  */
1423 int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...)
1424 {
1425         va_list args;
1426         char    savedChar;
1427         char    **dest;
1428         va_start(args, ArgStr);
1429
1430         // Check for null
1431         if( !ArgStr )
1432         {
1433                 while( (dest = va_arg(args, char **)) )
1434                         *dest = NULL;
1435                 va_end(args);
1436                 return 1;
1437         }
1438
1439         savedChar = *ArgStr;
1440         
1441         while( (dest = va_arg(args, char **)) )
1442         {
1443                 // Trim leading spaces
1444                 while( *ArgStr == ' ' || *ArgStr == '\t' )
1445                         ArgStr ++;
1446                 
1447                 // ... oops, not enough arguments
1448                 if( *ArgStr == '\0' )
1449                 {
1450                         // NULL unset arguments
1451                         do {
1452                                 *dest = NULL;
1453                         }       while( (dest = va_arg(args, char **)) );
1454                 va_end(args);
1455                         return -1;
1456                 }
1457                 
1458                 if( *ArgStr == '"' )
1459                 {
1460                         ArgStr ++;
1461                         *dest = ArgStr;
1462                         // Read until quote
1463                         while( *ArgStr && *ArgStr != '"' )
1464                                 ArgStr ++;
1465                 }
1466                 else
1467                 {
1468                         // Set destination
1469                         *dest = ArgStr;
1470                         // Read until a space
1471                         while( *ArgStr && *ArgStr != ' ' && *ArgStr != '\t' )
1472                                 ArgStr ++;
1473                 }
1474                 savedChar = *ArgStr;    // savedChar is used to un-mangle the last string
1475                 *ArgStr = '\0';
1476                 ArgStr ++;
1477         }
1478         va_end(args);
1479         
1480         // Oops, extra arguments, and greedy not set
1481         if( (savedChar == ' ' || savedChar == '\t') && !bUseLongLast ) {
1482                 return -1;
1483         }
1484         
1485         // Un-mangle last
1486         if(bUseLongLast) {
1487                 ArgStr --;
1488                 *ArgStr = savedChar;
1489         }
1490         
1491         return 0;       // Success!
1492 }
1493
1494 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1495 {
1496         struct {
1497                 const char      *Name;
1498                  int    Mask;
1499                  int    Value;
1500         }       cFLAGS[] = {
1501                  {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1502                 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1503                 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1504                 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1505                 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1506         };
1507         const int       ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1508         
1509         char    *space;
1510         
1511         *Mask = 0;
1512         *Value = 0;
1513         
1514         do {
1515                  int    bRemove = 0;
1516                  int    i;
1517                  int    len;
1518                 
1519                 while( *Str == ' ' )    Str ++; // Eat whitespace
1520                 space = strchr(Str, ',');       // Find the end of the flag
1521                 if(space)
1522                         len = space - Str;
1523                 else
1524                         len = strlen(Str);
1525                 
1526                 // Check for inversion/removal
1527                 if( *Str == '!' || *Str == '-' ) {
1528                         bRemove = 1;
1529                         Str ++;
1530                 }
1531                 else if( *Str == '+' ) {
1532                         Str ++;
1533                 }
1534                 
1535                 // Check flag values
1536                 for( i = 0; i < ciNumFlags; i ++ )
1537                 {
1538                         if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1539                                 *Mask |= cFLAGS[i].Mask;
1540                                 *Value &= ~cFLAGS[i].Mask;
1541                                 if( !bRemove )
1542                                         *Value |= cFLAGS[i].Value;
1543                                 break;
1544                         }
1545                 }
1546                 
1547                 // Error check
1548                 if( i == ciNumFlags ) {
1549                         char    val[len+1];
1550                         strncpy(val, Str, len+1);
1551                         sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);
1552                         return -1;
1553                 }
1554                 
1555                 Str = space + 1;
1556         } while(space);
1557         
1558         return 0;
1559 }

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