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

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