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

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