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

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