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

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