Renamed liability account
[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         // Save username
519         if(Client->Username)
520                 free(Client->Username);
521         Client->Username = strdup(username);
522
523         Client->bIsAuthed = 1;
524         
525         if(giDebugLevel)
526                 Debug(Client, "Auto authenticated as '%s' (%i)", username, Client->UID);
527         
528         sendf(Client->Socket, "200 Auth OK\n");
529 }
530
531 /**
532  * \brief Set effective user
533  */
534 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
535 {
536         char    *username;
537          int    eUserFlags, userFlags;
538         
539         if( Server_int_ParseArgs(0, Args, &username, NULL) )
540         {
541                 sendf(Client->Socket, "407 SETEUSER takes 1 argument\n");
542                 return ;
543         }
544         
545         if( !strlen(Args) ) {
546                 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
547                 return ;
548         }
549         
550         // Check authentication
551         if( !Client->bIsAuthed ) {
552                 sendf(Client->Socket, "401 Not Authenticated\n");
553                 return ;
554         }
555
556         // Check user permissions
557         userFlags = Bank_GetFlags(Client->UID);
558         if( !(userFlags & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
559                 sendf(Client->Socket, "403 Not in coke\n");
560                 return ;
561         }
562         
563         // Set id
564         Client->EffectiveUID = Bank_GetAcctByName(username, 0);
565         if( Client->EffectiveUID == -1 ) {
566                 sendf(Client->Socket, "404 User not found\n");
567                 return ;
568         }
569         
570         // You can't be an internal account
571         if( !(userFlags & USER_FLAG_ADMIN) )
572         {
573                 eUserFlags = Bank_GetFlags(Client->EffectiveUID);
574                 if( eUserFlags & USER_FLAG_INTERNAL ) {
575                         Client->EffectiveUID = -1;
576                         sendf(Client->Socket, "404 User not found\n");
577                         return ;
578                 }
579                 // Disabled only avaliable to admins
580                 if( eUserFlags & USER_FLAG_DISABLED ) {
581                         Client->EffectiveUID = -1;
582                         sendf(Client->Socket, "403 Account disabled\n");
583                         return ;
584                 }
585         }
586         
587         sendf(Client->Socket, "200 User set\n");
588 }
589
590 /**
591  * \brief Send an item status to the client
592  * \param Client        Who to?
593  * \param Item  Item to send
594  */
595 void Server_int_SendItem(tClient *Client, tItem *Item)
596 {
597         char    *status = "avail";
598         
599         if( Item->Handler->CanDispense )
600         {
601                 switch(Item->Handler->CanDispense(Client->UID, Item->ID))
602                 {
603                 case  0:        status = "avail";       break;
604                 case  1:        status = "sold";        break;
605                 default:
606                 case -1:        status = "error";       break;
607                 }
608         }
609         
610         // KNOWN HACK: Naming a slot 'dead' disables it
611         if( strcmp(Item->Name, "dead") == 0 )
612                 status = "sold";        // Another status?
613         
614         sendf(Client->Socket,
615                 "202 Item %s:%i %s %i %s\n",
616                 Item->Handler->Name, Item->ID, status, Item->Price, Item->Name
617                 );
618 }
619
620 /**
621  * \brief Enumerate the items that the server knows about
622  */
623 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
624 {
625          int    i, count;
626
627         if( Args != NULL && strlen(Args) ) {
628                 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
629                 return ;
630         }
631         
632         // Count shown items
633         count = 0;
634         for( i = 0; i < giNumItems; i ++ ) {
635                 if( gaItems[i].bHidden )        continue;
636                 count ++;
637         }
638
639         sendf(Client->Socket, "201 Items %i\n", count);
640
641         for( i = 0; i < giNumItems; i ++ ) {
642                 if( gaItems[i].bHidden )        continue;
643                 Server_int_SendItem( Client, &gaItems[i] );
644         }
645
646         sendf(Client->Socket, "200 List end\n");
647 }
648
649 tItem *_GetItemFromString(char *String)
650 {
651         tHandler        *handler;
652         char    *type = String;
653         char    *colon = strchr(String, ':');
654          int    num, i;
655         
656         if( !colon ) {
657                 return NULL;
658         }
659
660         num = atoi(colon+1);
661         *colon = '\0';
662
663         // Find handler
664         handler = NULL;
665         for( i = 0; i < giNumHandlers; i ++ )
666         {
667                 if( strcmp(gaHandlers[i]->Name, type) == 0) {
668                         handler = gaHandlers[i];
669                         break;
670                 }
671         }
672         if( !handler ) {
673                 return NULL;
674         }
675
676         // Find item
677         for( i = 0; i < giNumItems; i ++ )
678         {
679                 if( gaItems[i].Handler != handler )     continue;
680                 if( gaItems[i].ID != num )      continue;
681                 return &gaItems[i];
682         }
683         return NULL;
684 }
685
686 /**
687  * \brief Fetch information on a specific item
688  */
689 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
690 {
691         tItem   *item;
692         char    *itemname;
693         
694         if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
695                 sendf(Client->Socket, "407 ITEMINFO takes 1 argument\n");
696                 return ;
697         }
698         item = _GetItemFromString(Args);
699         
700         if( !item ) {
701                 sendf(Client->Socket, "406 Bad Item ID\n");
702                 return ;
703         }
704         
705         Server_int_SendItem( Client, item );
706 }
707
708 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
709 {
710         tItem   *item;
711          int    ret;
712          int    uid;
713         char    *itemname;
714         
715         if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
716                 sendf(Client->Socket, "407 DISPENSE takes only 1 argument\n");
717                 return ;
718         }
719          
720         if( !Client->bIsAuthed ) {
721                 sendf(Client->Socket, "401 Not Authenticated\n");
722                 return ;
723         }
724
725         item = _GetItemFromString(itemname);
726         if( !item ) {
727                 sendf(Client->Socket, "406 Bad Item ID\n");
728                 return ;
729         }
730         
731         if( Client->EffectiveUID != -1 ) {
732                 uid = Client->EffectiveUID;
733         }
734         else {
735                 uid = Client->UID;
736         }
737
738         switch( ret = DispenseItem( Client->UID, uid, item ) )
739         {
740         case 0: sendf(Client->Socket, "200 Dispense OK\n");     return ;
741         case 1: sendf(Client->Socket, "501 Unable to dispense\n");      return ;
742         case 2: sendf(Client->Socket, "402 Poor You\n");        return ;
743         default:
744                 sendf(Client->Socket, "500 Dispense Error (%i)\n", ret);
745                 return ;
746         }
747 }
748
749 void Server_Cmd_REFUND(tClient *Client, char *Args)
750 {
751         tItem   *item;
752          int    uid, price_override = 0;
753         char    *username, *itemname, *price_str;
754
755         if( Server_int_ParseArgs(0, Args, &username, &itemname, &price_str, NULL) ) {
756                 if( !itemname || price_str ) {
757                         sendf(Client->Socket, "407 REFUND takes 2 or 3 arguments\n");
758                         return ;
759                 }
760         }
761
762         if( !Client->bIsAuthed ) {
763                 sendf(Client->Socket, "401 Not Authenticated\n");
764                 return ;
765         }
766
767         // Check user permissions
768         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
769                 sendf(Client->Socket, "403 Not in coke\n");
770                 return ;
771         }
772
773         uid = Bank_GetAcctByName(username, 0);
774         if( uid == -1 ) {
775                 sendf(Client->Socket, "404 Unknown user\n");
776                 return ;
777         }
778         
779         item = _GetItemFromString(itemname);
780         if( !item ) {
781                 sendf(Client->Socket, "406 Bad Item ID\n");
782                 return ;
783         }
784
785         if( price_str )
786                 price_override = atoi(price_str);
787
788         switch( DispenseRefund( Client->UID, uid, item, price_override ) )
789         {
790         case 0: sendf(Client->Socket, "200 Item Refunded\n");   return ;
791         default:
792                 sendf(Client->Socket, "500 Dispense Error\n");
793                 return;
794         }
795 }
796
797 void Server_Cmd_GIVE(tClient *Client, char *Args)
798 {
799         char    *recipient, *ammount, *reason;
800          int    uid, iAmmount;
801          int    thisUid;
802         
803         // Parse arguments
804         if( Server_int_ParseArgs(1, Args, &recipient, &ammount, &reason, NULL) ) {
805                 sendf(Client->Socket, "407 GIVE takes only 3 arguments\n");
806                 return ;
807         }
808         
809         // Check for authed
810         if( !Client->bIsAuthed ) {
811                 sendf(Client->Socket, "401 Not Authenticated\n");
812                 return ;
813         }
814
815         // Get recipient
816         uid = Bank_GetAcctByName(recipient, 0);
817         if( uid == -1 ) {
818                 sendf(Client->Socket, "404 Invalid target user\n");
819                 return ;
820         }
821         
822         // You can't alter an internal account
823 //      if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
824 //              sendf(Client->Socket, "404 Invalid target user\n");
825 //              return ;
826 //      }
827
828         // Parse ammount
829         iAmmount = atoi(ammount);
830         if( iAmmount <= 0 ) {
831                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
832                 return ;
833         }
834         
835         if( Client->EffectiveUID != -1 ) {
836                 thisUid = Client->EffectiveUID;
837         }
838         else {
839                 thisUid = Client->UID;
840         }
841
842         // Do give
843         switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
844         {
845         case 0:
846                 sendf(Client->Socket, "200 Give OK\n");
847                 return ;
848         case 2:
849                 sendf(Client->Socket, "402 Poor You\n");
850                 return ;
851         default:
852                 sendf(Client->Socket, "500 Unknown error\n");
853                 return ;
854         }
855 }
856
857 void Server_Cmd_DONATE(tClient *Client, char *Args)
858 {
859         char    *ammount, *reason;
860          int    iAmmount;
861          int    thisUid;
862         
863         // Parse arguments
864         if( Server_int_ParseArgs(1, Args, &ammount, &reason, NULL) ) {
865                 sendf(Client->Socket, "407 DONATE takes 2 arguments\n");
866                 return ;
867         }
868         
869         if( !Client->bIsAuthed ) {
870                 sendf(Client->Socket, "401 Not Authenticated\n");
871                 return ;
872         }
873
874         // Parse ammount
875         iAmmount = atoi(ammount);
876         if( iAmmount <= 0 ) {
877                 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
878                 return ;
879         }
880         
881         // Handle effective users
882         if( Client->EffectiveUID != -1 ) {
883                 thisUid = Client->EffectiveUID;
884         }
885         else {
886                 thisUid = Client->UID;
887         }
888
889         // Do give
890         switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
891         {
892         case 0:
893                 sendf(Client->Socket, "200 Give OK\n");
894                 return ;
895         case 2:
896                 sendf(Client->Socket, "402 Poor You\n");
897                 return ;
898         default:
899                 sendf(Client->Socket, "500 Unknown error\n");
900                 return ;
901         }
902 }
903
904 void Server_Cmd_ADD(tClient *Client, char *Args)
905 {
906         char    *user, *ammount, *reason;
907          int    uid, iAmmount;
908         
909         // Parse arguments
910         if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
911                 sendf(Client->Socket, "407 ADD takes 3 arguments\n");
912                 return ;
913         }
914         
915         if( !Client->bIsAuthed ) {
916                 sendf(Client->Socket, "401 Not Authenticated\n");
917                 return ;
918         }
919
920         // Check user permissions
921         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
922                 sendf(Client->Socket, "403 Not in coke\n");
923                 return ;
924         }
925
926         #if !ROOT_CAN_ADD
927         if( strcmp( Client->Username, "root" ) == 0 ) {
928                 // Allow adding for new users
929                 if( strcmp(reason, "treasurer: new user") != 0 ) {
930                         sendf(Client->Socket, "403 Root may not add\n");
931                         return ;
932                 }
933         }
934         #endif
935
936         #if HACK_NO_REFUNDS
937         if( strstr(reason, "refund") != NULL || strstr(reason, "misdispense") != NULL )
938         {
939                 sendf(Client->Socket, "499 Don't use `dispense acct` for refunds, use `dispense refund` (and `dispense -G` to get item IDs)\n");
940                 return ;
941         }
942         #endif
943
944         // Get recipient
945         uid = Bank_GetAcctByName(user, 0);
946         if( uid == -1 ) {
947                 sendf(Client->Socket, "404 Invalid user\n");
948                 return ;
949         }
950         
951         // You can't alter an internal account
952         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) )
953         {
954                 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
955                         sendf(Client->Socket, "404 Invalid user\n");
956                         return ;
957                 }
958                 // TODO: Maybe disallow changes to disabled?
959         }
960
961         // Parse ammount
962         iAmmount = atoi(ammount);
963         if( iAmmount == 0 && ammount[0] != '0' ) {
964                 sendf(Client->Socket, "407 Invalid Argument\n");
965                 return ;
966         }
967
968         // Do give
969         switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
970         {
971         case 0:
972                 sendf(Client->Socket, "200 Add OK\n");
973                 return ;
974         case 2:
975                 sendf(Client->Socket, "402 Poor Guy\n");
976                 return ;
977         default:
978                 sendf(Client->Socket, "500 Unknown error\n");
979                 return ;
980         }
981 }
982
983 void Server_Cmd_SET(tClient *Client, char *Args)
984 {
985         char    *user, *ammount, *reason;
986          int    uid, iAmmount;
987         
988         // Parse arguments
989         if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
990                 sendf(Client->Socket, "407 SET takes 3 arguments\n");
991                 return ;
992         }
993         
994         if( !Client->bIsAuthed ) {
995                 sendf(Client->Socket, "401 Not Authenticated\n");
996                 return ;
997         }
998
999         // Check user permissions
1000         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN)  ) {
1001                 sendf(Client->Socket, "403 Not an admin\n");
1002                 return ;
1003         }
1004
1005         // Get recipient
1006         uid = Bank_GetAcctByName(user, 0);
1007         if( uid == -1 ) {
1008                 sendf(Client->Socket, "404 Invalid user\n");
1009                 return ;
1010         }
1011
1012         // Parse ammount
1013         iAmmount = atoi(ammount);
1014         if( iAmmount == 0 && ammount[0] != '0' ) {
1015                 sendf(Client->Socket, "407 Invalid Argument\n");
1016                 return ;
1017         }
1018
1019         // Do give
1020         switch( DispenseSet(Client->UID, uid, iAmmount, reason) )
1021         {
1022         case 0:
1023                 sendf(Client->Socket, "200 Add OK\n");
1024                 return ;
1025         case 2:
1026                 sendf(Client->Socket, "402 Poor Guy\n");
1027                 return ;
1028         default:
1029                 sendf(Client->Socket, "500 Unknown error\n");
1030                 return ;
1031         }
1032 }
1033
1034 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
1035 {
1036          int    i, numRet = 0;
1037         tAcctIterator   *it;
1038          int    maxBal = INT_MAX, minBal = INT_MIN;
1039          int    flagMask = 0, flagVal = 0;
1040          int    sort = BANK_ITFLAG_SORT_NAME;
1041         time_t  lastSeenAfter=0, lastSeenBefore=0;
1042         
1043          int    flags;  // Iterator flags
1044          int    balValue;       // Balance value for iterator
1045         time_t  timeValue;      // Time value for iterator
1046         
1047         // Parse arguments
1048         if( Args && strlen(Args) )
1049         {
1050                 char    *space = Args, *type, *val;
1051                 do
1052                 {
1053                         type = space;
1054                         while(*type == ' ')     type ++;
1055                         // Get next space
1056                         space = strchr(space, ' ');
1057                         if(space)       *space = '\0';
1058                         
1059                         // Get type
1060                         val = strchr(type, ':');
1061                         if( val ) {
1062                                 *val = '\0';
1063                                 val ++;
1064                                 
1065                                 // Types
1066                                 // - Minium Balance
1067                                 if( strcmp(type, "min_balance") == 0 ) {
1068                                         minBal = atoi(val);
1069                                 }
1070                                 // - Maximum Balance
1071                                 else if( strcmp(type, "max_balance") == 0 ) {
1072                                         maxBal = atoi(val);
1073                                 }
1074                                 // - Flags
1075                                 else if( strcmp(type, "flags") == 0 ) {
1076                                         if( Server_int_ParseFlags(Client, val, &flagMask, &flagVal) )
1077                                                 return ;
1078                                 }
1079                                 // - Last seen before timestamp
1080                                 else if( strcmp(type, "last_seen_before") == 0 ) {
1081                                         lastSeenAfter = atoll(val);
1082                                 }
1083                                 // - Last seen after timestamp
1084                                 else if( strcmp(type, "last_seen_after") == 0 ) {
1085                                         lastSeenAfter = atoll(val);
1086                                 }
1087                                 // - Sorting 
1088                                 else if( strcmp(type, "sort") == 0 ) {
1089                                         char    *dash = strchr(val, '-');
1090                                         if( dash ) {
1091                                                 *dash = '\0';
1092                                                 dash ++;
1093                                         }
1094                                         if( strcmp(val, "name") == 0 ) {
1095                                                 sort = BANK_ITFLAG_SORT_NAME;
1096                                         }
1097                                         else if( strcmp(val, "balance") == 0 ) {
1098                                                 sort = BANK_ITFLAG_SORT_BAL;
1099                                         }
1100                                         else if( strcmp(val, "lastseen") == 0 ) {
1101                                                 sort = BANK_ITFLAG_SORT_LASTSEEN;
1102                                         }
1103                                         else {
1104                                                 sendf(Client->Socket, "407 Unknown sort field ('%s')\n", val);
1105                                                 return ;
1106                                         }
1107                                         // Handle sort direction
1108                                         if( dash ) {
1109                                                 if( strcmp(dash, "desc") == 0 ) {
1110                                                         sort |= BANK_ITFLAG_REVSORT;
1111                                                 }
1112                                                 else {
1113                                                         sendf(Client->Socket, "407 Unknown sort direction '%s'\n", dash);
1114                                                         return ;
1115                                                 }
1116                                                 dash[-1] = '-';
1117                                         }
1118                                 }
1119                                 else {
1120                                         sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s:%s'\n", type, val);
1121                                         return ;
1122                                 }
1123                                 
1124                                 val[-1] = ':';
1125                         }
1126                         else {
1127                                 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s'\n", type);
1128                                 return ;
1129                         }
1130                         
1131                         // Eat whitespace
1132                         if( space ) {
1133                                 *space = ' ';   // Repair (to be nice)
1134                                 space ++;
1135                                 while(*space == ' ')    space ++;
1136                         }
1137                 }       while(space);
1138         }
1139         
1140         // Create iterator
1141         if( maxBal != INT_MAX ) {
1142                 flags = sort|BANK_ITFLAG_MAXBALANCE;
1143                 balValue = maxBal;
1144         }
1145         else if( minBal != INT_MIN ) {
1146                 flags = sort|BANK_ITFLAG_MINBALANCE;
1147                 balValue = minBal;
1148         }
1149         else {
1150                 flags = sort;
1151                 balValue = 0;
1152         }
1153         if( lastSeenBefore ) {
1154                 timeValue = lastSeenBefore;
1155                 flags |= BANK_ITFLAG_SEENBEFORE;
1156         }
1157         else if( lastSeenAfter ) {
1158                 timeValue = lastSeenAfter;
1159                 flags |= BANK_ITFLAG_SEENAFTER;
1160         }
1161         else {
1162                 timeValue = 0;
1163         }
1164         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1165         
1166         // Get return number
1167         while( (i = Bank_IteratorNext(it)) != -1 )
1168         {
1169                 int bal = Bank_GetBalance(i);
1170                 
1171                 if( bal == INT_MIN )    continue;
1172                 
1173                 if( bal < minBal )      continue;
1174                 if( bal > maxBal )      continue;
1175                 
1176                 numRet ++;
1177         }
1178         
1179         Bank_DelIterator(it);
1180         
1181         // Send count
1182         sendf(Client->Socket, "201 Users %i\n", numRet);
1183         
1184         
1185         // Create iterator
1186         it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1187         
1188         while( (i = Bank_IteratorNext(it)) != -1 )
1189         {
1190                 int bal = Bank_GetBalance(i);
1191                 
1192                 if( bal == INT_MIN )    continue;
1193                 
1194                 if( bal < minBal )      continue;
1195                 if( bal > maxBal )      continue;
1196                 
1197                 _SendUserInfo(Client, i);
1198         }
1199         
1200         Bank_DelIterator(it);
1201         
1202         sendf(Client->Socket, "200 List End\n");
1203 }
1204
1205 void Server_Cmd_USERINFO(tClient *Client, char *Args)
1206 {
1207          int    uid;
1208         char    *user;
1209         
1210         // Parse arguments
1211         if( Server_int_ParseArgs(0, Args, &user, NULL) ) {
1212                 sendf(Client->Socket, "407 USER_INFO takes 1 argument\n");
1213                 return ;
1214         }
1215         
1216         if( giDebugLevel )      Debug(Client, "User Info '%s'", user);
1217         
1218         // Get recipient
1219         uid = Bank_GetAcctByName(user, 0);
1220         
1221         if( giDebugLevel >= 2 ) Debug(Client, "uid = %i", uid);
1222         if( uid == -1 ) {
1223                 sendf(Client->Socket, "404 Invalid user\n");
1224                 return ;
1225         }
1226         
1227         _SendUserInfo(Client, uid);
1228 }
1229
1230 void _SendUserInfo(tClient *Client, int UserID)
1231 {
1232         char    *type, *disabled="", *door="";
1233          int    flags = Bank_GetFlags(UserID);
1234         
1235         if( flags & USER_FLAG_INTERNAL ) {
1236                 type = "internal";
1237         }
1238         else if( flags & USER_FLAG_COKE ) {
1239                 if( flags & USER_FLAG_ADMIN )
1240                         type = "coke,admin";
1241                 else
1242                         type = "coke";
1243         }
1244         else if( flags & USER_FLAG_ADMIN ) {
1245                 type = "admin";
1246         }
1247         else {
1248                 type = "user";
1249         }
1250         
1251         if( flags & USER_FLAG_DISABLED )
1252                 disabled = ",disabled";
1253         if( flags & USER_FLAG_DOORGROUP )
1254                 door = ",door";
1255         
1256         // TODO: User flags/type
1257         sendf(
1258                 Client->Socket, "202 User %s %i %s%s%s\n",
1259                 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
1260                 type, disabled, door
1261                 );
1262 }
1263
1264 void Server_Cmd_USERADD(tClient *Client, char *Args)
1265 {
1266         char    *username;
1267         
1268         // Parse arguments
1269         if( Server_int_ParseArgs(0, Args, &username, NULL) ) {
1270                 sendf(Client->Socket, "407 USER_ADD takes 1 argument\n");
1271                 return ;
1272         }
1273         
1274         // Check authentication
1275         if( !Client->bIsAuthed ) {
1276                 sendf(Client->Socket, "401 Not Authenticated\n");
1277                 return ;
1278         }
1279         
1280         // Check permissions
1281         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1282                 sendf(Client->Socket, "403 Not a coke admin\n");
1283                 return ;
1284         }
1285         
1286         // Try to create user
1287         if( Bank_CreateAcct(username) == -1 ) {
1288                 sendf(Client->Socket, "404 User exists\n");
1289                 return ;
1290         }
1291         
1292         {
1293                 char    *thisName = Bank_GetAcctName(Client->UID);
1294                 Log_Info("Account '%s' created by '%s'", username, thisName);
1295                 free(thisName);
1296         }
1297         
1298         sendf(Client->Socket, "200 User Added\n");
1299 }
1300
1301 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
1302 {
1303         char    *username, *flags, *reason=NULL;
1304          int    mask=0, value=0;
1305          int    uid;
1306         
1307         // Parse arguments
1308         if( Server_int_ParseArgs(1, Args, &username, &flags, &reason, NULL) ) {
1309                 if( !flags ) {
1310                         sendf(Client->Socket, "407 USER_FLAGS takes at least 2 arguments\n");
1311                         return ;
1312                 }
1313                 reason = "";
1314         }
1315         
1316         // Check authentication
1317         if( !Client->bIsAuthed ) {
1318                 sendf(Client->Socket, "401 Not Authenticated\n");
1319                 return ;
1320         }
1321         
1322         // Check permissions
1323         if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1324                 sendf(Client->Socket, "403 Not a coke admin\n");
1325                 return ;
1326         }
1327         
1328         // Get UID
1329         uid = Bank_GetAcctByName(username, 0);
1330         if( uid == -1 ) {
1331                 sendf(Client->Socket, "404 User '%s' not found\n", username);
1332                 return ;
1333         }
1334         
1335         // Parse flags
1336         if( Server_int_ParseFlags(Client, flags, &mask, &value) )
1337                 return ;
1338         
1339         if( giDebugLevel )
1340                 Debug(Client, "Set %i(%s) flags to %x (masked %x)\n",
1341                         uid, username, mask, value);
1342         
1343         // Apply flags
1344         Bank_SetFlags(uid, mask, value);
1345
1346         // Log the change
1347         Log_Info("Updated '%s' with flag set '%s' by '%s' - Reason: %s",
1348                 username, flags, Client->Username, reason);
1349         
1350         // Return OK
1351         sendf(Client->Socket, "200 User Updated\n");
1352 }
1353
1354 void Server_Cmd_UPDATEITEM(tClient *Client, char *Args)
1355 {
1356         char    *itemname, *price_str, *description;
1357          int    price;
1358         tItem   *item;
1359         
1360         if( Server_int_ParseArgs(1, Args, &itemname, &price_str, &description, NULL) ) {
1361                 sendf(Client->Socket, "407 UPDATE_ITEM takes 3 arguments\n");
1362                 return ;
1363         }
1364         
1365         if( !Client->bIsAuthed ) {
1366                 sendf(Client->Socket, "401 Not Authenticated\n");
1367                 return ;
1368         }
1369
1370         // Check user permissions
1371         if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
1372                 sendf(Client->Socket, "403 Not in coke\n");
1373                 return ;
1374         }
1375         
1376         item = _GetItemFromString(itemname);
1377         if( !item ) {
1378                 // TODO: Create item?
1379                 sendf(Client->Socket, "406 Bad Item ID\n");
1380                 return ;
1381         }
1382         
1383         price = atoi(price_str);
1384         if( price <= 0 && price_str[0] != '0' ) {
1385                 sendf(Client->Socket, "407 Invalid price set\n");
1386         }
1387         
1388         switch( DispenseUpdateItem( Client->UID, item, description, price ) )
1389         {
1390         case 0:
1391                 // Return OK
1392                 sendf(Client->Socket, "200 Item updated\n");
1393                 break;
1394         default:
1395                 break;
1396         }
1397 }
1398
1399 // --- INTERNAL HELPERS ---
1400 void Debug(tClient *Client, const char *Format, ...)
1401 {
1402         va_list args;
1403         //printf("%010i [%i] ", (int)time(NULL), Client->ID);
1404         printf("[%i] ", Client->ID);
1405         va_start(args, Format);
1406         vprintf(Format, args);
1407         va_end(args);
1408         printf("\n");
1409 }
1410
1411 int sendf(int Socket, const char *Format, ...)
1412 {
1413         va_list args;
1414          int    len;
1415         
1416         va_start(args, Format);
1417         len = vsnprintf(NULL, 0, Format, args);
1418         va_end(args);
1419         
1420         {
1421                 char    buf[len+1];
1422                 va_start(args, Format);
1423                 vsnprintf(buf, len+1, Format, args);
1424                 va_end(args);
1425                 
1426                 #if DEBUG_TRACE_CLIENT
1427                 printf("sendf: %s", buf);
1428                 #endif
1429                 
1430                 return send(Socket, buf, len, 0);
1431         }
1432 }
1433
1434 // Takes a series of char *'s in
1435 /**
1436  * \brief Parse space-separated entries into 
1437  */
1438 int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...)
1439 {
1440         va_list args;
1441         char    savedChar;
1442         char    **dest;
1443         va_start(args, ArgStr);
1444
1445         // Check for null
1446         if( !ArgStr )
1447         {
1448                 while( (dest = va_arg(args, char **)) )
1449                         *dest = NULL;
1450                 va_end(args);
1451                 return 1;
1452         }
1453
1454         savedChar = *ArgStr;
1455         
1456         while( (dest = va_arg(args, char **)) )
1457         {
1458                 // Trim leading spaces
1459                 while( *ArgStr == ' ' || *ArgStr == '\t' )
1460                         ArgStr ++;
1461                 
1462                 // ... oops, not enough arguments
1463                 if( *ArgStr == '\0' )
1464                 {
1465                         // NULL unset arguments
1466                         do {
1467                                 *dest = NULL;
1468                         }       while( (dest = va_arg(args, char **)) );
1469                 va_end(args);
1470                         return -1;
1471                 }
1472                 
1473                 if( *ArgStr == '"' )
1474                 {
1475                         ArgStr ++;
1476                         *dest = ArgStr;
1477                         // Read until quote
1478                         while( *ArgStr && *ArgStr != '"' )
1479                                 ArgStr ++;
1480                 }
1481                 else
1482                 {
1483                         // Set destination
1484                         *dest = ArgStr;
1485                         // Read until a space
1486                         while( *ArgStr && *ArgStr != ' ' && *ArgStr != '\t' )
1487                                 ArgStr ++;
1488                 }
1489                 savedChar = *ArgStr;    // savedChar is used to un-mangle the last string
1490                 *ArgStr = '\0';
1491                 ArgStr ++;
1492         }
1493         va_end(args);
1494         
1495         // Oops, extra arguments, and greedy not set
1496         if( (savedChar == ' ' || savedChar == '\t') && !bUseLongLast ) {
1497                 return -1;
1498         }
1499         
1500         // Un-mangle last
1501         if(bUseLongLast) {
1502                 ArgStr --;
1503                 *ArgStr = savedChar;
1504         }
1505         
1506         return 0;       // Success!
1507 }
1508
1509 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1510 {
1511         struct {
1512                 const char      *Name;
1513                  int    Mask;
1514                  int    Value;
1515         }       cFLAGS[] = {
1516                  {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1517                 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1518                 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1519                 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1520                 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1521         };
1522         const int       ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1523         
1524         char    *space;
1525         
1526         *Mask = 0;
1527         *Value = 0;
1528         
1529         do {
1530                  int    bRemove = 0;
1531                  int    i;
1532                  int    len;
1533                 
1534                 while( *Str == ' ' )    Str ++; // Eat whitespace
1535                 space = strchr(Str, ',');       // Find the end of the flag
1536                 if(space)
1537                         len = space - Str;
1538                 else
1539                         len = strlen(Str);
1540                 
1541                 // Check for inversion/removal
1542                 if( *Str == '!' || *Str == '-' ) {
1543                         bRemove = 1;
1544                         Str ++;
1545                 }
1546                 else if( *Str == '+' ) {
1547                         Str ++;
1548                 }
1549                 
1550                 // Check flag values
1551                 for( i = 0; i < ciNumFlags; i ++ )
1552                 {
1553                         if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1554                                 *Mask |= cFLAGS[i].Mask;
1555                                 *Value &= ~cFLAGS[i].Mask;
1556                                 if( !bRemove )
1557                                         *Value |= cFLAGS[i].Value;
1558                                 break;
1559                         }
1560                 }
1561                 
1562                 // Error check
1563                 if( i == ciNumFlags ) {
1564                         char    val[len+1];
1565                         strncpy(val, Str, len+1);
1566                         sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);
1567                         return -1;
1568                 }
1569                 
1570                 Str = space + 1;
1571         } while(space);
1572         
1573         return 0;
1574 }

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