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

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