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

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