3 * UCC (University [of WA] Computer Club) Electronic Accounting System
5 * server.c - Client Server Code
7 * This file is licenced under the 3-clause BSD Licence. See the file
8 * COPYING for full details.
13 #include "../common/config.h"
14 #include <sys/socket.h>
15 #include <netinet/in.h>
16 #include <arpa/inet.h>
18 #include <fcntl.h> // O_*
22 #include <signal.h> // Signal handling
23 #include <ident.h> // AUTHIDENT
24 #include <time.h> // time(2)
27 #define DEBUG_TRACE_CLIENT 0
28 #define HACK_NO_REFUNDS 1
30 #define PIDFILE "/var/run/dispsrv.pid"
33 #define MAX_CONNECTION_QUEUE 5
34 #define INPUT_BUFFER_SIZE 256
35 #define CLIENT_TIMEOUT 10 // Seconds
37 #define HASH_TYPE SHA1
38 #define HASH_LENGTH 20
40 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
42 #define IDENT_TRUSTED_NETWORK 0x825F0D00
43 #define IDENT_TRUSTED_NETMASK 0xFFFFFFC0
46 typedef struct sClient
48 int Socket; // Client socket ID
52 int bCanAutoAuth; // Is the connection from a trusted host/port
63 void Server_Start(void);
64 void Server_Cleanup(void);
65 void Server_HandleClient(int Socket, int bTrustedHost, int bRootPort);
66 void Server_ParseClientCommand(tClient *Client, char *CommandString);
68 void Server_Cmd_USER(tClient *Client, char *Args);
69 void Server_Cmd_PASS(tClient *Client, char *Args);
70 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
71 void Server_Cmd_AUTHIDENT(tClient *Client, char *Args);
72 void Server_Cmd_AUTHCARD(tClient* Client, char *Args);
73 void Server_Cmd_SETEUSER(tClient *Client, char *Args);
74 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
75 void Server_Cmd_ITEMINFO(tClient *Client, char *Args);
76 void Server_Cmd_DISPENSE(tClient *Client, char *Args);
77 void Server_Cmd_REFUND(tClient *Client, char *Args);
78 void Server_Cmd_GIVE(tClient *Client, char *Args);
79 void Server_Cmd_DONATE(tClient *Client, char *Args);
80 void Server_Cmd_ADD(tClient *Client, char *Args);
81 void Server_Cmd_SET(tClient *Client, char *Args);
82 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
83 void Server_Cmd_USERINFO(tClient *Client, char *Args);
84 void _SendUserInfo(tClient *Client, int UserID);
85 void Server_Cmd_USERADD(tClient *Client, char *Args);
86 void Server_Cmd_USERFLAGS(tClient *Client, char *Args);
87 void Server_Cmd_UPDATEITEM(tClient *Client, char *Args);
88 void Server_Cmd_PINCHECK(tClient *Client, char *Args);
89 void Server_Cmd_PINSET(tClient *Client, char *Args);
90 void Server_Cmd_CARDADD(tClient *Client, char *Args);
92 void Debug(tClient *Client, const char *Format, ...);
93 int sendf(int Socket, const char *Format, ...);
94 int Server_int_ParseArgs(int bUseLongArg, char *ArgStr, ...);
95 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value);
97 #define CLIENT_DEBUG_LOW(Client, ...) do { if(giDebugLevel>1) Debug(Client, __VA_ARGS__); } while(0)
98 #define CLIENT_DEBUG(Client, ...) do { if(giDebugLevel) Debug(Client, __VA_ARGS__); } while(0)
102 const struct sClientCommand {
104 void (*Function)(tClient *Client, char *Arguments);
105 } gaServer_Commands[] = {
106 {"USER", Server_Cmd_USER},
107 {"PASS", Server_Cmd_PASS},
108 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
109 {"AUTHIDENT", Server_Cmd_AUTHIDENT},
110 {"AUTHCARD", Server_Cmd_AUTHCARD},
111 {"SETEUSER", Server_Cmd_SETEUSER},
112 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
113 {"ITEM_INFO", Server_Cmd_ITEMINFO},
114 {"DISPENSE", Server_Cmd_DISPENSE},
115 {"REFUND", Server_Cmd_REFUND},
116 {"GIVE", Server_Cmd_GIVE},
117 {"DONATE", Server_Cmd_DONATE},
118 {"ADD", Server_Cmd_ADD},
119 {"SET", Server_Cmd_SET},
120 {"ENUM_USERS", Server_Cmd_ENUMUSERS},
121 {"USER_INFO", Server_Cmd_USERINFO},
122 {"USER_ADD", Server_Cmd_USERADD},
123 {"USER_FLAGS", Server_Cmd_USERFLAGS},
124 {"UPDATE_ITEM", Server_Cmd_UPDATEITEM},
125 {"PIN_CHECK", Server_Cmd_PINCHECK},
126 {"PIN_SET", Server_Cmd_PINSET},
127 {"CARD_ADD", Server_Cmd_CARDADD},
129 #define NUM_COMMANDS ((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
133 int giServer_Port = 11020;
134 int gbServer_RunInBackground = 0;
135 char *gsServer_LogFile = "/var/log/dispsrv.log";
136 char *gsServer_ErrorLog = "/var/log/dispsrv.err";
137 int giServer_NumTrustedHosts;
138 struct in_addr *gaServer_TrustedHosts;
140 int giServer_Socket; // Server socket
141 int giServer_NextClientID = 1; // Debug client ID
146 * \brief Open listenting socket and serve connections
148 void Server_Start(void)
151 struct sockaddr_in server_addr, client_addr;
153 // Parse trusted hosts list
154 giServer_NumTrustedHosts = Config_GetValueCount("trusted_host");
155 gaServer_TrustedHosts = malloc(giServer_NumTrustedHosts * sizeof(*gaServer_TrustedHosts));
156 for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
158 const char *addr = Config_GetValue_Idx("trusted_host", i);
160 if( inet_aton(addr, &gaServer_TrustedHosts[i]) == 0 ) {
161 fprintf(stderr, "Invalid IP address '%s'\n", addr);
166 // Ignore SIGPIPE (stops crashes when the client exits early)
167 signal(SIGPIPE, SIG_IGN);
170 giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
171 if( giServer_Socket < 0 ) {
172 fprintf(stderr, "ERROR: Unable to create server socket\n");
176 // Make listen address
177 memset(&server_addr, 0, sizeof(server_addr));
178 server_addr.sin_family = AF_INET; // Internet Socket
179 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
180 server_addr.sin_port = htons(giServer_Port); // Port
183 if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
184 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
186 close(giServer_Socket);
190 // Fork into background
191 if( gbServer_RunInBackground )
195 fprintf(stderr, "ERROR: Unable to fork\n");
196 perror("fork background");
201 Debug_Notice("Forked child server as PID %i\n", pid);
205 // - Sort out stdin/stdout
207 dup2( open("/dev/null", O_RDONLY, 0644), STDIN_FILENO );
208 dup2( open(gsServer_LogFile, O_CREAT|O_APPEND, 0644), STDOUT_FILENO );
209 dup2( open(gsServer_ErrorLog, O_CREAT|O_APPEND, 0644), STDERR_FILENO );
211 freopen("/dev/null", "r", stdin);
212 freopen(gsServer_LogFile, "a", stdout);
213 freopen(gsServer_ErrorLog, "a", stderr);
214 fprintf(stdout, "OpenDispense 2 Server Started at %lld\n", (long long)time(NULL));
215 fprintf(stderr, "OpenDispense 2 Server Started at %lld\n", (long long)time(NULL));
218 atexit(Server_Cleanup);
220 // Start the helper thread
221 StartPeriodicThread();
224 if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
225 fprintf(stderr, "ERROR: Unable to listen to socket\n");
230 Debug_Notice("Listening on 0.0.0.0:%i", giServer_Port);
234 FILE *fp = fopen(PIDFILE, "w");
236 fprintf(fp, "%i", getpid());
243 uint len = sizeof(client_addr);
247 // Accept a connection
248 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
249 if(client_socket < 0) {
250 fprintf(stderr, "ERROR: Unable to accept client connection\n");
254 // Set a timeout on the user conneciton
257 tv.tv_sec = CLIENT_TIMEOUT;
259 if( setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) )
261 perror("setsockopt");
266 // Debug: Print the connection string
267 if(giDebugLevel >= 2) {
268 char ipstr[INET_ADDRSTRLEN];
269 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
270 Debug_Debug("Client connection from %s:%i",
271 ipstr, ntohs(client_addr.sin_port));
274 // Doesn't matter what, localhost is trusted
275 if( ntohl( client_addr.sin_addr.s_addr ) == 0x7F000001 )
278 // Check if the host is on the trusted list
279 for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
281 if( memcmp(&client_addr.sin_addr, &gaServer_TrustedHosts[i], sizeof(struct in_addr)) == 0 )
288 // Root port (can AUTOAUTH if also a trusted machine
289 if( ntohs(client_addr.sin_port) < 1024 )
294 // TODO: Make this runtime configurable
295 switch( ntohl( client_addr.sin_addr.s_addr ) )
297 case 0x7F000001: // 127.0.0.1 localhost
298 // case 0x825F0D00: // 130.95.13.0
299 case 0x825F0D04: // 130.95.13.4 merlo
300 // case 0x825F0D05: // 130.95.13.5 heathred (MR)
301 case 0x825F0D07: // 130.95.13.7 motsugo
302 case 0x825F0D11: // 130.95.13.17 mermaid
303 case 0x825F0D12: // 130.95.13.18 mussel
304 case 0x825F0D17: // 130.95.13.23 martello
305 case 0x825F0D2A: // 130.95.13.42 meersau
306 // case 0x825F0D42: // 130.95.13.66 heathred (Clubroom)
315 // TODO: Multithread this?
316 Server_HandleClient(client_socket, bTrusted, bRootPort);
318 close(client_socket);
322 void Server_Cleanup(void)
324 Debug_Debug("Close(%i)", giServer_Socket);
325 close(giServer_Socket);
330 * \brief Reads from a client socket and parses the command strings
331 * \param Socket Client socket number/handle
332 * \param bTrusted Is the client trusted?
334 void Server_HandleClient(int Socket, int bTrusted, int bRootPort)
336 char inbuf[INPUT_BUFFER_SIZE];
338 int remspace = INPUT_BUFFER_SIZE-1;
342 memset(&clientInfo, 0, sizeof(clientInfo));
344 // Initialise Client info
345 clientInfo.Socket = Socket;
346 clientInfo.ID = giServer_NextClientID ++;
347 clientInfo.bTrustedHost = bTrusted;
348 clientInfo.bCanAutoAuth = bTrusted && bRootPort;
349 clientInfo.EffectiveUID = -1;
354 * - The `buf` and `remspace` variables allow a line to span several
355 * calls to recv(), if a line is not completed in one recv() call
356 * it is saved to the beginning of `inbuf` and `buf` is updated to
359 // TODO: Use select() instead (to give a timeout)
360 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
363 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
367 while( (eol = strchr(start, '\n')) )
371 Server_ParseClientCommand(&clientInfo, start);
376 // Check if there was an incomplete line
377 if( *start != '\0' ) {
378 int tailBytes = bytes - (start-buf);
379 // Roll back in buffer
380 memcpy(inbuf, start, tailBytes);
381 remspace -= tailBytes;
383 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
385 remspace = INPUT_BUFFER_SIZE - 1;
390 remspace = INPUT_BUFFER_SIZE - 1;
396 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
400 if(giDebugLevel >= 2) {
401 printf("Client %i: Disconnected\n", clientInfo.ID);
406 * \brief Parses a client command and calls the required helper function
407 * \param Client Pointer to client state structure
408 * \param CommandString Command from client (single line of the command)
409 * \return Heap String to return to the client
411 void Server_ParseClientCommand(tClient *Client, char *CommandString)
413 char *command, *args;
416 if( giDebugLevel >= 2 )
417 Debug(Client, "Server_ParseClientCommand: (CommandString = '%s')", CommandString);
419 if( Server_int_ParseArgs(1, CommandString, &command, &args, NULL) )
421 if( command == NULL ) return ;
422 // Is this an error? (just ignore for now)
427 for( i = 0; i < NUM_COMMANDS; i++ )
429 if(strcmp(command, gaServer_Commands[i].Name) == 0) {
430 if( giDebugLevel >= 2 )
431 Debug(Client, "CMD %s - \"%s\"", command, args);
432 gaServer_Commands[i].Function(Client, args);
437 sendf(Client->Socket, "400 Unknown Command\n");
444 * \brief Set client username
446 * Usage: USER <username>
448 void Server_Cmd_USER(tClient *Client, char *Args)
452 if( Server_int_ParseArgs(0, Args, &username, NULL) )
454 sendf(Client->Socket, "407 USER takes 1 argument\n");
460 Debug(Client, "Authenticating as '%s'", username);
464 free(Client->Username);
465 Client->Username = strdup(username);
468 // Create a salt (that changes if the username is changed)
469 // Yes, I know, I'm a little paranoid, but who isn't?
470 Client->Salt[0] = 0x21 + (rand()&0x3F);
471 Client->Salt[1] = 0x21 + (rand()&0x3F);
472 Client->Salt[2] = 0x21 + (rand()&0x3F);
473 Client->Salt[3] = 0x21 + (rand()&0x3F);
474 Client->Salt[4] = 0x21 + (rand()&0x3F);
475 Client->Salt[5] = 0x21 + (rand()&0x3F);
476 Client->Salt[6] = 0x21 + (rand()&0x3F);
477 Client->Salt[7] = 0x21 + (rand()&0x3F);
479 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
480 sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
482 sendf(Client->Socket, "100 User Set\n");
486 /// UID: User ID (must be valid)
487 /// username: Optional username
488 bool authenticate(tClient* Client, int UID, const char* username)
492 int flags = Bank_GetFlags(Client->UID);
493 if( flags & USER_FLAG_DISABLED ) {
495 sendf(Client->Socket, "403 Authentication failure: account disabled\n");
498 // You can't be an internal account
499 if( flags & USER_FLAG_INTERNAL ) {
501 Debug(Client, "IDENT auth as '%s', not allowed", username);
503 sendf(Client->Socket, "403 Authentication failure: that account is internal\n");
508 if(Client->Username != username)
512 free(Client->Username);
515 // Fetch username (if not provided)
518 Client->Username = strdup(username);
522 Client->Username = Bank_GetAcctName(UID);
526 Client->bIsAuthed = 1;
529 Debug(Client, "Auto authenticated as '%s' (%i)", Client->Username, Client->UID);
532 bool require_auth(tClient* Client)
534 // Check authentication
535 if( !Client->bIsAuthed ) {
536 sendf(Client->Socket, "401 Not Authenticated\n");
543 * \brief Authenticate as a user
547 void Server_Cmd_PASS(tClient *Client, char *Args)
551 if( Server_int_ParseArgs(0, Args, &passhash, NULL) )
553 sendf(Client->Socket, "407 PASS takes 1 argument\n");
557 // Pass on to cokebank
558 int uid = Bank_GetUserAuth(Client->Salt, Client->Username, passhash);
561 Debug(Client, "Unknown user '%s'", Client->Username);
562 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
565 if( ! authenticate(Client, uid, Client->Username) )
569 sendf(Client->Socket, "200 Auth OK\n");
573 * \brief Authenticate as a user without a password
575 * Usage: AUTOAUTH <user>
577 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
581 if( Server_int_ParseArgs(0, Args, &username, NULL) )
583 sendf(Client->Socket, "407 AUTOAUTH takes 1 argument\n");
588 if( !Client->bCanAutoAuth ) {
590 Debug(Client, "Untrusted client attempting to AUTOAUTH");
591 sendf(Client->Socket, "401 Untrusted\n");
596 int uid = Bank_GetAcctByName( username, /*bCreate=*/0 );
599 Debug(Client, "Unknown user '%s'", username);
600 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
603 if( ! authenticate(Client, uid, username) )
608 sendf(Client->Socket, "200 Auth OK\n");
612 * \brief Authenticate as a user using the IDENT protocol
616 void Server_Cmd_AUTHIDENT(tClient *Client, char *Args)
619 const int IDENT_TIMEOUT = 5;
621 if( Args != NULL && strlen(Args) ) {
622 sendf(Client->Socket, "407 AUTHIDENT takes no arguments\n");
627 if( !Client->bTrustedHost ) {
629 Debug(Client, "Untrusted client attempting to AUTHIDENT");
630 sendf(Client->Socket, "401 Untrusted\n");
634 // Get username via IDENT
635 username = ident_id(Client->Socket, IDENT_TIMEOUT);
637 perror("AUTHIDENT - IDENT timed out");
638 sendf(Client->Socket, "403 Authentication failure: IDENT auth timed out\n");
642 int uid = Bank_GetAcctByName(username, /*bCreate=*/0);
645 Debug(Client, "Unknown user '%s'", username);
646 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
650 if( ! authenticate(Client, uid, username) )
657 sendf(Client->Socket, "200 Auth OK\n");
660 void Server_Cmd_AUTHCARD(tClient* Client, char *Args)
663 if( Server_int_ParseArgs(0, Args, &card_id, NULL) )
665 sendf(Client->Socket, "407 AUTHCARD takes 1 argument\n");
669 // Check if trusted (has to be root)
670 if( Client->UID != 1 )
673 Debug(Client, "Attempting to use AUTHCARD as non-root");
674 sendf(Client->Socket, "401 Untrusted\n");
678 CLIENT_DEBUG(Client, "MIFARE auth with '%s'", card_id);
679 int uid = Bank_GetAcctByCard(card_id);
683 Debug(Client, "Unknown MIFARE '%s'", card_id);
684 sendf(Client->Socket, "403 Authentication failure: unknown MIFARE ID\n");
687 if( ! authenticate(Client, uid, NULL) )
692 sendf(Client->Socket, "200 Auth Ok, username=%s\n", Client->Username);
696 * \brief Set effective user
698 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
701 int eUserFlags, userFlags;
703 if( Server_int_ParseArgs(0, Args, &username, NULL) )
705 sendf(Client->Socket, "407 SETEUSER takes 1 argument\n");
709 if( !strlen(Args) ) {
710 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
714 // Check authentication
715 if( !Client->bIsAuthed ) {
716 sendf(Client->Socket, "401 Not Authenticated\n");
720 // Check user permissions
721 userFlags = Bank_GetFlags(Client->UID);
722 if( !(userFlags & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
723 sendf(Client->Socket, "403 Not in coke\n");
728 Client->EffectiveUID = Bank_GetAcctByName(username, 0);
729 if( Client->EffectiveUID == -1 ) {
730 sendf(Client->Socket, "404 User not found\n");
733 // You can't be an internal account (unless you're an admin)
734 if( !(userFlags & USER_FLAG_ADMIN) )
736 eUserFlags = Bank_GetFlags(Client->EffectiveUID);
737 if( eUserFlags & USER_FLAG_INTERNAL ) {
738 Client->EffectiveUID = -1;
739 sendf(Client->Socket, "404 User not found\n");
745 // - If disabled and the actual user is not an admin (and not root)
747 if( (eUserFlags & USER_FLAG_DISABLED) && (Client->UID == 0 || !(userFlags & USER_FLAG_ADMIN)) ) {
748 Client->EffectiveUID = -1;
749 sendf(Client->Socket, "403 Account disabled\n");
753 sendf(Client->Socket, "200 User set\n");
757 * \brief Send an item status to the client
758 * \param Client Who to?
759 * \param Item Item to send
761 void Server_int_SendItem(tClient *Client, tItem *Item)
763 char *status = "avail";
765 if( Item->Handler->CanDispense )
767 switch(Item->Handler->CanDispense(Client->UID, Item->ID))
769 case 0: status = "avail"; break;
770 case 1: status = "sold"; break;
772 case -1: status = "error"; break;
776 if( !gbNoCostMode && Item->Price == 0 )
778 // KNOWN HACK: Naming a slot 'dead' disables it
779 if( strcmp(Item->Name, "dead") == 0 )
780 status = "sold"; // Another status?
782 sendf(Client->Socket,
783 "202 Item %s:%i %s %i %s\n",
784 Item->Handler->Name, Item->ID, status, Item->Price, Item->Name
789 * \brief Enumerate the items that the server knows about
791 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
795 if( Args != NULL && strlen(Args) ) {
796 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
802 for( i = 0; i < giNumItems; i ++ ) {
803 if( gaItems[i].bHidden ) continue;
807 sendf(Client->Socket, "201 Items %i\n", count);
809 for( i = 0; i < giNumItems; i ++ ) {
810 if( gaItems[i].bHidden ) continue;
811 Server_int_SendItem( Client, &gaItems[i] );
814 sendf(Client->Socket, "200 List end\n");
817 tItem *_GetItemFromString(char *String)
821 char *colon = strchr(String, ':');
833 for( i = 0; i < giNumHandlers; i ++ )
835 if( strcmp(gaHandlers[i]->Name, type) == 0) {
836 handler = gaHandlers[i];
845 for( i = 0; i < giNumItems; i ++ )
847 if( gaItems[i].Handler != handler ) continue;
848 if( gaItems[i].ID != num ) continue;
855 * \brief Fetch information on a specific item
857 * Usage: ITEMINFO <item ID>
859 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
864 if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
865 sendf(Client->Socket, "407 ITEMINFO takes 1 argument\n");
868 item = _GetItemFromString(Args);
871 sendf(Client->Socket, "406 Bad Item ID\n");
875 Server_int_SendItem( Client, item );
879 * \brief Dispense an item
881 * Usage: DISPENSE <Item ID>
883 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
890 if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
891 sendf(Client->Socket, "407 DISPENSE takes only 1 argument\n");
895 if( !Client->bIsAuthed ) {
896 sendf(Client->Socket, "401 Not Authenticated\n");
900 item = _GetItemFromString(itemname);
902 sendf(Client->Socket, "406 Bad Item ID\n");
906 if( Client->EffectiveUID != -1 ) {
907 uid = Client->EffectiveUID;
913 // if( Bank_GetFlags(Client->UID) & USER_FLAG_DISABLED ) {
916 switch( ret = DispenseItem( Client->UID, uid, item ) )
918 case 0: sendf(Client->Socket, "200 Dispense OK\n"); return ;
919 case 1: sendf(Client->Socket, "501 Unable to dispense\n"); return ;
920 case 2: sendf(Client->Socket, "402 Poor You\n"); return ;
922 sendf(Client->Socket, "500 Dispense Error (%i)\n", ret);
928 * \brief Refund an item to a user
930 * Usage: REFUND <user> <item id> [<price>]
932 void Server_Cmd_REFUND(tClient *Client, char *Args)
935 int uid, price_override = 0;
936 char *username, *itemname, *price_str;
938 if( Server_int_ParseArgs(0, Args, &username, &itemname, &price_str, NULL) ) {
939 if( !itemname || price_str ) {
940 sendf(Client->Socket, "407 REFUND takes 2 or 3 arguments\n");
945 if( !Client->bIsAuthed ) {
946 sendf(Client->Socket, "401 Not Authenticated\n");
950 // Check user permissions
951 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
952 sendf(Client->Socket, "403 Not in coke\n");
956 uid = Bank_GetAcctByName(username, 0);
958 sendf(Client->Socket, "404 Unknown user\n");
962 item = _GetItemFromString(itemname);
964 sendf(Client->Socket, "406 Bad Item ID\n");
969 price_override = atoi(price_str);
971 switch( DispenseRefund( Client->UID, uid, item, price_override ) )
973 case 0: sendf(Client->Socket, "200 Item Refunded\n"); return ;
975 sendf(Client->Socket, "500 Dispense Error\n");
981 * \brief Transfer money to another account
983 * Usage: GIVE <dest> <ammount> <reason...>
985 void Server_Cmd_GIVE(tClient *Client, char *Args)
987 char *recipient, *ammount, *reason;
992 if( Server_int_ParseArgs(1, Args, &recipient, &ammount, &reason, NULL) ) {
993 sendf(Client->Socket, "407 GIVE takes only 3 arguments\n");
998 if( !Client->bIsAuthed ) {
999 sendf(Client->Socket, "401 Not Authenticated\n");
1004 uid = Bank_GetAcctByName(recipient, 0);
1006 sendf(Client->Socket, "404 Invalid target user\n");
1010 // You can't alter an internal account
1011 // if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
1012 // sendf(Client->Socket, "404 Invalid target user\n");
1017 iAmmount = atoi(ammount);
1018 if( iAmmount <= 0 ) {
1019 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
1023 if( Client->EffectiveUID != -1 ) {
1024 thisUid = Client->EffectiveUID;
1027 thisUid = Client->UID;
1031 switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
1034 sendf(Client->Socket, "200 Give OK\n");
1037 sendf(Client->Socket, "402 Poor You\n");
1040 sendf(Client->Socket, "500 Unknown error\n");
1045 void Server_Cmd_DONATE(tClient *Client, char *Args)
1047 char *ammount, *reason;
1052 if( Server_int_ParseArgs(1, Args, &ammount, &reason, NULL) ) {
1053 sendf(Client->Socket, "407 DONATE takes 2 arguments\n");
1057 if( !Client->bIsAuthed ) {
1058 sendf(Client->Socket, "401 Not Authenticated\n");
1063 iAmmount = atoi(ammount);
1064 if( iAmmount <= 0 ) {
1065 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
1069 // Handle effective users
1070 if( Client->EffectiveUID != -1 ) {
1071 thisUid = Client->EffectiveUID;
1074 thisUid = Client->UID;
1078 switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
1081 sendf(Client->Socket, "200 Give OK\n");
1084 sendf(Client->Socket, "402 Poor You\n");
1087 sendf(Client->Socket, "500 Unknown error\n");
1092 void Server_Cmd_ADD(tClient *Client, char *Args)
1094 char *user, *ammount, *reason;
1098 if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
1099 sendf(Client->Socket, "407 ADD takes 3 arguments\n");
1103 if( !Client->bIsAuthed ) {
1104 sendf(Client->Socket, "401 Not Authenticated\n");
1108 // Check user permissions
1109 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1110 sendf(Client->Socket, "403 Not in coke\n");
1115 if( strcmp( Client->Username, "root" ) == 0 ) {
1116 // Allow adding for new users
1117 if( strcmp(reason, "treasurer: new user") != 0 ) {
1118 sendf(Client->Socket, "403 Root may not add\n");
1125 if( strstr(reason, "refund") != NULL || strstr(reason, "misdispense") != NULL )
1127 sendf(Client->Socket, "499 Don't use `dispense acct` for refunds, use `dispense refund` (and `dispense -G` to get item IDs)\n");
1133 uid = Bank_GetAcctByName(user, 0);
1135 sendf(Client->Socket, "404 Invalid user\n");
1139 // You can't alter an internal account
1140 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) )
1142 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
1143 sendf(Client->Socket, "403 Admin only\n");
1146 // TODO: Maybe disallow changes to disabled?
1150 iAmmount = atoi(ammount);
1151 if( iAmmount == 0 && ammount[0] != '0' ) {
1152 sendf(Client->Socket, "407 Invalid Argument\n");
1157 switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
1160 sendf(Client->Socket, "200 Add OK\n");
1163 sendf(Client->Socket, "402 Poor Guy\n");
1166 sendf(Client->Socket, "500 Unknown error\n");
1171 void Server_Cmd_SET(tClient *Client, char *Args)
1173 char *user, *ammount, *reason;
1177 if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
1178 sendf(Client->Socket, "407 SET takes 3 arguments\n");
1182 if( !Client->bIsAuthed ) {
1183 sendf(Client->Socket, "401 Not Authenticated\n");
1187 // Check user permissions
1188 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1189 sendf(Client->Socket, "403 Not an admin\n");
1194 uid = Bank_GetAcctByName(user, 0);
1196 sendf(Client->Socket, "404 Invalid user\n");
1201 iAmmount = atoi(ammount);
1202 if( iAmmount == 0 && ammount[0] != '0' ) {
1203 sendf(Client->Socket, "407 Invalid Argument\n");
1207 int origBalance, rv;
1209 switch( rv = DispenseSet(Client->UID, uid, iAmmount, reason, &origBalance) )
1212 sendf(Client->Socket, "200 Add OK (%i)\n", origBalance);
1215 sendf(Client->Socket, "500 Unknown error (%i)\n", rv);
1220 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
1224 int maxBal = INT_MAX, minBal = INT_MIN;
1225 int flagMask = 0, flagVal = 0;
1226 int sort = BANK_ITFLAG_SORT_NAME;
1227 time_t lastSeenAfter=0, lastSeenBefore=0;
1229 int flags; // Iterator flags
1230 int balValue; // Balance value for iterator
1231 time_t timeValue; // Time value for iterator
1234 if( Args && strlen(Args) )
1236 char *space = Args, *type, *val;
1240 while(*type == ' ') type ++;
1242 space = strchr(space, ' ');
1243 if(space) *space = '\0';
1246 val = strchr(type, ':');
1253 if( strcmp(type, "min_balance") == 0 ) {
1256 // - Maximum Balance
1257 else if( strcmp(type, "max_balance") == 0 ) {
1261 else if( strcmp(type, "flags") == 0 ) {
1262 if( Server_int_ParseFlags(Client, val, &flagMask, &flagVal) )
1265 // - Last seen before timestamp
1266 else if( strcmp(type, "last_seen_before") == 0 ) {
1267 lastSeenAfter = atoll(val);
1269 // - Last seen after timestamp
1270 else if( strcmp(type, "last_seen_after") == 0 ) {
1271 lastSeenAfter = atoll(val);
1274 else if( strcmp(type, "sort") == 0 ) {
1275 char *dash = strchr(val, '-');
1280 if( strcmp(val, "name") == 0 ) {
1281 sort = BANK_ITFLAG_SORT_NAME;
1283 else if( strcmp(val, "balance") == 0 ) {
1284 sort = BANK_ITFLAG_SORT_BAL;
1286 else if( strcmp(val, "lastseen") == 0 ) {
1287 sort = BANK_ITFLAG_SORT_LASTSEEN;
1290 sendf(Client->Socket, "407 Unknown sort field ('%s')\n", val);
1293 // Handle sort direction
1295 if( strcmp(dash, "desc") == 0 ) {
1296 sort |= BANK_ITFLAG_REVSORT;
1299 sendf(Client->Socket, "407 Unknown sort direction '%s'\n", dash);
1306 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s:%s'\n", type, val);
1313 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s'\n", type);
1319 *space = ' '; // Repair (to be nice)
1321 while(*space == ' ') space ++;
1327 if( maxBal != INT_MAX ) {
1328 flags = sort|BANK_ITFLAG_MAXBALANCE;
1331 else if( minBal != INT_MIN ) {
1332 flags = sort|BANK_ITFLAG_MINBALANCE;
1339 if( lastSeenBefore ) {
1340 timeValue = lastSeenBefore;
1341 flags |= BANK_ITFLAG_SEENBEFORE;
1343 else if( lastSeenAfter ) {
1344 timeValue = lastSeenAfter;
1345 flags |= BANK_ITFLAG_SEENAFTER;
1350 it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1352 // Get return number
1353 while( (i = Bank_IteratorNext(it)) != -1 )
1355 int bal = Bank_GetBalance(i);
1357 if( bal == INT_MIN ) continue;
1359 if( bal < minBal ) continue;
1360 if( bal > maxBal ) continue;
1365 Bank_DelIterator(it);
1368 sendf(Client->Socket, "201 Users %i\n", numRet);
1372 it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1374 while( (i = Bank_IteratorNext(it)) != -1 )
1376 int bal = Bank_GetBalance(i);
1378 if( bal == INT_MIN ) continue;
1380 if( bal < minBal ) continue;
1381 if( bal > maxBal ) continue;
1383 _SendUserInfo(Client, i);
1386 Bank_DelIterator(it);
1388 sendf(Client->Socket, "200 List End\n");
1391 void Server_Cmd_USERINFO(tClient *Client, char *Args)
1397 if( Server_int_ParseArgs(0, Args, &user, NULL) ) {
1398 sendf(Client->Socket, "407 USER_INFO takes 1 argument\n");
1402 if( giDebugLevel ) Debug(Client, "User Info '%s'", user);
1405 uid = Bank_GetAcctByName(user, 0);
1407 if( giDebugLevel >= 2 ) Debug(Client, "uid = %i", uid);
1409 sendf(Client->Socket, "404 Invalid user\n");
1413 _SendUserInfo(Client, uid);
1416 void _SendUserInfo(tClient *Client, int UserID)
1418 char *type, *disabled="", *door="";
1419 int flags = Bank_GetFlags(UserID);
1421 if( flags & USER_FLAG_INTERNAL ) {
1424 else if( flags & USER_FLAG_COKE ) {
1425 if( flags & USER_FLAG_ADMIN )
1426 type = "coke,admin";
1430 else if( flags & USER_FLAG_ADMIN ) {
1437 if( flags & USER_FLAG_DISABLED )
1438 disabled = ",disabled";
1439 if( flags & USER_FLAG_DOORGROUP )
1442 // TODO: User flags/type
1444 Client->Socket, "202 User %s %i %s%s%s\n",
1445 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
1446 type, disabled, door
1450 void Server_Cmd_USERADD(tClient *Client, char *Args)
1455 if( Server_int_ParseArgs(0, Args, &username, NULL) ) {
1456 sendf(Client->Socket, "407 USER_ADD takes 1 argument\n");
1460 // Check authentication
1461 if( !Client->bIsAuthed ) {
1462 sendf(Client->Socket, "401 Not Authenticated\n");
1466 // Check permissions
1467 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1468 sendf(Client->Socket, "403 Not a coke admin\n");
1472 // Try to create user
1473 if( Bank_CreateAcct(username) == -1 ) {
1474 sendf(Client->Socket, "404 User exists\n");
1479 char *thisName = Bank_GetAcctName(Client->UID);
1480 Log_Info("Account '%s' created by '%s'", username, thisName);
1484 sendf(Client->Socket, "200 User Added\n");
1487 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
1489 char *username, *flags, *reason=NULL;
1490 int mask=0, value=0;
1494 if( Server_int_ParseArgs(1, Args, &username, &flags, &reason, NULL) ) {
1496 sendf(Client->Socket, "407 USER_FLAGS takes at least 2 arguments\n");
1502 // Check authentication
1503 if(!require_auth(Client)) return;
1505 // Check permissions
1506 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1507 sendf(Client->Socket, "403 Not a coke admin\n");
1512 uid = Bank_GetAcctByName(username, 0);
1514 sendf(Client->Socket, "404 User '%s' not found\n", username);
1519 if( Server_int_ParseFlags(Client, flags, &mask, &value) )
1523 Debug(Client, "Set %i(%s) flags to %x (masked %x)\n",
1524 uid, username, mask, value);
1527 Bank_SetFlags(uid, mask, value);
1530 Log_Info("Updated '%s' with flag set '%s' by '%s' - Reason: %s",
1531 username, flags, Client->Username, reason);
1534 sendf(Client->Socket, "200 User Updated\n");
1537 void Server_Cmd_UPDATEITEM(tClient *Client, char *Args)
1539 char *itemname, *price_str, *description;
1543 if( Server_int_ParseArgs(1, Args, &itemname, &price_str, &description, NULL) ) {
1544 sendf(Client->Socket, "407 UPDATE_ITEM takes 3 arguments\n");
1548 if(!require_auth(Client)) return;
1550 // Check user permissions
1551 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1552 sendf(Client->Socket, "403 Not in coke\n");
1556 item = _GetItemFromString(itemname);
1558 // TODO: Create item?
1559 sendf(Client->Socket, "406 Bad Item ID\n");
1563 price = atoi(price_str);
1564 if( price <= 0 && price_str[0] != '0' ) {
1565 sendf(Client->Socket, "407 Invalid price set\n");
1568 switch( DispenseUpdateItem( Client->UID, item, description, price ) )
1572 sendf(Client->Socket, "200 Item updated\n");
1579 void Server_Cmd_PINCHECK(tClient *Client, char *Args)
1581 char *username, *pinstr;
1584 if( Server_int_ParseArgs(0, Args, &username, &pinstr, NULL) ) {
1585 sendf(Client->Socket, "407 PIN_CHECK takes 2 arguments\n");
1589 if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
1590 sendf(Client->Socket, "407 PIN should be four digits\n");
1595 if(!require_auth(Client)) return;
1598 int uid = Bank_GetAcctByName(username, 0);
1600 sendf(Client->Socket, "404 User '%s' not found\n", username);
1604 // Check user permissions
1605 if( uid != Client->UID && !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1606 sendf(Client->Socket, "403 Not in coke\n");
1611 static time_t last_wrong_pin_time;
1612 static int backoff = 1;
1613 if( time(NULL) - last_wrong_pin_time < backoff ) {
1614 sendf(Client->Socket, "407 Rate limited (%i seconds remaining)\n",
1615 backoff - (time(NULL) - last_wrong_pin_time));
1618 last_wrong_pin_time = time(NULL);
1619 if( !Bank_IsPinValid(uid, pin) )
1621 sendf(Client->Socket, "401 Pin incorrect\n");
1622 struct sockaddr_storage addr;
1623 socklen_t len = sizeof(addr);
1624 char ipstr[INET6_ADDRSTRLEN];
1625 getpeername(Client->Socket, (void*)&addr, &len);
1626 struct sockaddr_in *s = (struct sockaddr_in *)&addr;
1627 inet_ntop(addr.ss_family, &s->sin_addr, ipstr, sizeof(ipstr));
1628 Debug_Notice("Bad pin from %s for %s by %i", ipstr, username, Client->UID);
1634 last_wrong_pin_time = 0;
1636 sendf(Client->Socket, "200 Pin correct\n");
1639 void Server_Cmd_PINSET(tClient *Client, char *Args)
1645 if( Server_int_ParseArgs(0, Args, &pinstr, NULL) ) {
1646 sendf(Client->Socket, "407 PIN_SET takes 1 argument\n");
1650 if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
1651 sendf(Client->Socket, "407 PIN should be four digits\n");
1656 if(!require_auth(Client)) return;
1658 int uid = Client->EffectiveUID > 0 ? Client->EffectiveUID : Client->UID;
1659 CLIENT_DEBUG(Client, "Setting PIN for UID %i", uid);
1660 // Can only pinset yourself (well, the effective user)
1661 Bank_SetPin(uid, pin);
1662 sendf(Client->Socket, "200 Pin updated\n");
1665 void Server_Cmd_CARDADD(tClient* Client, char* Args)
1668 if( Server_int_ParseArgs(0, Args, &card_id, NULL) ) {
1669 sendf(Client->Socket, "407 CARD_ADD takes 1 argument\n");
1673 if(!require_auth(Client)) return;
1675 int uid = Client->EffectiveUID > 0 ? Client->EffectiveUID : Client->UID;
1676 CLIENT_DEBUG(Client, "Add card '%s' to UID %i", card_id, uid);
1677 if( Bank_AddAcctCard(uid, card_id) )
1679 sendf(Client->Socket, "408 Card already exists\n");
1682 sendf(Client->Socket, "200 Card added\n");
1685 // --- INTERNAL HELPERS ---
1686 void Debug(tClient *Client, const char *Format, ...)
1689 //printf("%010i [%i] ", (int)time(NULL), Client->ID);
1690 printf("[%i] ", Client->ID);
1691 va_start(args, Format);
1692 vprintf(Format, args);
1697 int sendf(int Socket, const char *Format, ...)
1702 va_start(args, Format);
1703 len = vsnprintf(NULL, 0, Format, args);
1708 va_start(args, Format);
1709 vsnprintf(buf, len+1, Format, args);
1712 #if DEBUG_TRACE_CLIENT
1713 printf("sendf: %s", buf);
1716 return send(Socket, buf, len, 0);
1720 // Takes a series of char *'s in
1722 * \brief Parse space-separated entries into
1724 int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...)
1729 va_start(args, ArgStr);
1734 while( (dest = va_arg(args, char **)) )
1740 savedChar = *ArgStr;
1742 while( (dest = va_arg(args, char **)) )
1744 // Trim leading spaces
1745 while( *ArgStr == ' ' || *ArgStr == '\t' )
1748 // ... oops, not enough arguments
1749 if( *ArgStr == '\0' )
1751 // NULL unset arguments
1754 } while( (dest = va_arg(args, char **)) );
1759 if( *ArgStr == '"' )
1764 while( *ArgStr && *ArgStr != '"' )
1771 // Read until a space
1772 while( *ArgStr && *ArgStr != ' ' && *ArgStr != '\t' )
1775 savedChar = *ArgStr; // savedChar is used to un-mangle the last string
1781 // Oops, extra arguments, and greedy not set
1782 if( (savedChar == ' ' || savedChar == '\t') && !bUseLongLast ) {
1789 *ArgStr = savedChar;
1792 return 0; // Success!
1795 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1802 {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1803 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1804 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1805 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1806 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1808 const int ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1820 while( *Str == ' ' ) Str ++; // Eat whitespace
1821 space = strchr(Str, ','); // Find the end of the flag
1827 // Check for inversion/removal
1828 if( *Str == '!' || *Str == '-' ) {
1832 else if( *Str == '+' ) {
1836 // Check flag values
1837 for( i = 0; i < ciNumFlags; i ++ )
1839 if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1840 *Mask |= cFLAGS[i].Mask;
1841 *Value &= ~cFLAGS[i].Mask;
1843 *Value |= cFLAGS[i].Value;
1849 if( i == ciNumFlags ) {
1851 strncpy(val, Str, len+1);
1852 sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);