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);
99 const struct sClientCommand {
101 void (*Function)(tClient *Client, char *Arguments);
102 } gaServer_Commands[] = {
103 {"USER", Server_Cmd_USER},
104 {"PASS", Server_Cmd_PASS},
105 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
106 {"AUTHIDENT", Server_Cmd_AUTHIDENT},
107 {"AUTHCARD", Server_Cmd_AUTHCARD},
108 {"SETEUSER", Server_Cmd_SETEUSER},
109 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
110 {"ITEM_INFO", Server_Cmd_ITEMINFO},
111 {"DISPENSE", Server_Cmd_DISPENSE},
112 {"REFUND", Server_Cmd_REFUND},
113 {"GIVE", Server_Cmd_GIVE},
114 {"DONATE", Server_Cmd_DONATE},
115 {"ADD", Server_Cmd_ADD},
116 {"SET", Server_Cmd_SET},
117 {"ENUM_USERS", Server_Cmd_ENUMUSERS},
118 {"USER_INFO", Server_Cmd_USERINFO},
119 {"USER_ADD", Server_Cmd_USERADD},
120 {"USER_FLAGS", Server_Cmd_USERFLAGS},
121 {"UPDATE_ITEM", Server_Cmd_UPDATEITEM},
122 {"PIN_CHECK", Server_Cmd_PINCHECK},
123 {"PIN_SET", Server_Cmd_PINSET},
124 {"CARD_ADD", Server_Cmd_CARDADD},
126 #define NUM_COMMANDS ((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
130 int giServer_Port = 11020;
131 int gbServer_RunInBackground = 0;
132 char *gsServer_LogFile = "/var/log/dispsrv.log";
133 char *gsServer_ErrorLog = "/var/log/dispsrv.err";
134 int giServer_NumTrustedHosts;
135 struct in_addr *gaServer_TrustedHosts;
137 int giServer_Socket; // Server socket
138 int giServer_NextClientID = 1; // Debug client ID
143 * \brief Open listenting socket and serve connections
145 void Server_Start(void)
148 struct sockaddr_in server_addr, client_addr;
150 // Parse trusted hosts list
151 giServer_NumTrustedHosts = Config_GetValueCount("trusted_host");
152 gaServer_TrustedHosts = malloc(giServer_NumTrustedHosts * sizeof(*gaServer_TrustedHosts));
153 for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
155 const char *addr = Config_GetValue_Idx("trusted_host", i);
157 if( inet_aton(addr, &gaServer_TrustedHosts[i]) == 0 ) {
158 fprintf(stderr, "Invalid IP address '%s'\n", addr);
163 // Ignore SIGPIPE (stops crashes when the client exits early)
164 signal(SIGPIPE, SIG_IGN);
167 giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
168 if( giServer_Socket < 0 ) {
169 fprintf(stderr, "ERROR: Unable to create server socket\n");
173 // Make listen address
174 memset(&server_addr, 0, sizeof(server_addr));
175 server_addr.sin_family = AF_INET; // Internet Socket
176 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
177 server_addr.sin_port = htons(giServer_Port); // Port
180 if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
181 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
183 close(giServer_Socket);
187 // Fork into background
188 if( gbServer_RunInBackground )
192 fprintf(stderr, "ERROR: Unable to fork\n");
193 perror("fork background");
198 Debug_Notice("Forked child server as PID %i\n", pid);
202 // - Sort out stdin/stdout
204 dup2( open("/dev/null", O_RDONLY, 0644), STDIN_FILENO );
205 dup2( open(gsServer_LogFile, O_CREAT|O_APPEND, 0644), STDOUT_FILENO );
206 dup2( open(gsServer_ErrorLog, O_CREAT|O_APPEND, 0644), STDERR_FILENO );
208 freopen("/dev/null", "r", stdin);
209 freopen(gsServer_LogFile, "a", stdout);
210 freopen(gsServer_ErrorLog, "a", stderr);
211 fprintf(stdout, "OpenDispense 2 Server Started at %lld\n", (long long)time(NULL));
212 fprintf(stderr, "OpenDispense 2 Server Started at %lld\n", (long long)time(NULL));
215 atexit(Server_Cleanup);
217 // Start the helper thread
218 StartPeriodicThread();
221 if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
222 fprintf(stderr, "ERROR: Unable to listen to socket\n");
227 Debug_Notice("Listening on 0.0.0.0:%i", giServer_Port);
231 FILE *fp = fopen(PIDFILE, "w");
233 fprintf(fp, "%i", getpid());
240 uint len = sizeof(client_addr);
244 // Accept a connection
245 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
246 if(client_socket < 0) {
247 fprintf(stderr, "ERROR: Unable to accept client connection\n");
251 // Set a timeout on the user conneciton
254 tv.tv_sec = CLIENT_TIMEOUT;
256 if( setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) )
258 perror("setsockopt");
263 // Debug: Print the connection string
264 if(giDebugLevel >= 2) {
265 char ipstr[INET_ADDRSTRLEN];
266 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
267 Debug_Debug("Client connection from %s:%i",
268 ipstr, ntohs(client_addr.sin_port));
271 // Doesn't matter what, localhost is trusted
272 if( ntohl( client_addr.sin_addr.s_addr ) == 0x7F000001 )
275 // Check if the host is on the trusted list
276 for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
278 if( memcmp(&client_addr.sin_addr, &gaServer_TrustedHosts[i], sizeof(struct in_addr)) == 0 )
285 // Root port (can AUTOAUTH if also a trusted machine
286 if( ntohs(client_addr.sin_port) < 1024 )
291 // TODO: Make this runtime configurable
292 switch( ntohl( client_addr.sin_addr.s_addr ) )
294 case 0x7F000001: // 127.0.0.1 localhost
295 // case 0x825F0D00: // 130.95.13.0
296 case 0x825F0D04: // 130.95.13.4 merlo
297 // case 0x825F0D05: // 130.95.13.5 heathred (MR)
298 case 0x825F0D07: // 130.95.13.7 motsugo
299 case 0x825F0D11: // 130.95.13.17 mermaid
300 case 0x825F0D12: // 130.95.13.18 mussel
301 case 0x825F0D17: // 130.95.13.23 martello
302 case 0x825F0D2A: // 130.95.13.42 meersau
303 // case 0x825F0D42: // 130.95.13.66 heathred (Clubroom)
312 // TODO: Multithread this?
313 Server_HandleClient(client_socket, bTrusted, bRootPort);
315 close(client_socket);
319 void Server_Cleanup(void)
321 Debug_Debug("Close(%i)", giServer_Socket);
322 close(giServer_Socket);
327 * \brief Reads from a client socket and parses the command strings
328 * \param Socket Client socket number/handle
329 * \param bTrusted Is the client trusted?
331 void Server_HandleClient(int Socket, int bTrusted, int bRootPort)
333 char inbuf[INPUT_BUFFER_SIZE];
335 int remspace = INPUT_BUFFER_SIZE-1;
339 memset(&clientInfo, 0, sizeof(clientInfo));
341 // Initialise Client info
342 clientInfo.Socket = Socket;
343 clientInfo.ID = giServer_NextClientID ++;
344 clientInfo.bTrustedHost = bTrusted;
345 clientInfo.bCanAutoAuth = bTrusted && bRootPort;
346 clientInfo.EffectiveUID = -1;
351 * - The `buf` and `remspace` variables allow a line to span several
352 * calls to recv(), if a line is not completed in one recv() call
353 * it is saved to the beginning of `inbuf` and `buf` is updated to
356 // TODO: Use select() instead (to give a timeout)
357 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
360 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
364 while( (eol = strchr(start, '\n')) )
368 Server_ParseClientCommand(&clientInfo, start);
373 // Check if there was an incomplete line
374 if( *start != '\0' ) {
375 int tailBytes = bytes - (start-buf);
376 // Roll back in buffer
377 memcpy(inbuf, start, tailBytes);
378 remspace -= tailBytes;
380 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
382 remspace = INPUT_BUFFER_SIZE - 1;
387 remspace = INPUT_BUFFER_SIZE - 1;
393 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
397 if(giDebugLevel >= 2) {
398 printf("Client %i: Disconnected\n", clientInfo.ID);
403 * \brief Parses a client command and calls the required helper function
404 * \param Client Pointer to client state structure
405 * \param CommandString Command from client (single line of the command)
406 * \return Heap String to return to the client
408 void Server_ParseClientCommand(tClient *Client, char *CommandString)
410 char *command, *args;
413 if( giDebugLevel >= 2 )
414 Debug(Client, "Server_ParseClientCommand: (CommandString = '%s')", CommandString);
416 if( Server_int_ParseArgs(1, CommandString, &command, &args, NULL) )
418 if( command == NULL ) return ;
419 // Is this an error? (just ignore for now)
424 for( i = 0; i < NUM_COMMANDS; i++ )
426 if(strcmp(command, gaServer_Commands[i].Name) == 0) {
427 if( giDebugLevel >= 2 )
428 Debug(Client, "CMD %s - \"%s\"", command, args);
429 gaServer_Commands[i].Function(Client, args);
434 sendf(Client->Socket, "400 Unknown Command\n");
441 * \brief Set client username
443 * Usage: USER <username>
445 void Server_Cmd_USER(tClient *Client, char *Args)
449 if( Server_int_ParseArgs(0, Args, &username, NULL) )
451 sendf(Client->Socket, "407 USER takes 1 argument\n");
457 Debug(Client, "Authenticating as '%s'", username);
461 free(Client->Username);
462 Client->Username = strdup(username);
465 // Create a salt (that changes if the username is changed)
466 // Yes, I know, I'm a little paranoid, but who isn't?
467 Client->Salt[0] = 0x21 + (rand()&0x3F);
468 Client->Salt[1] = 0x21 + (rand()&0x3F);
469 Client->Salt[2] = 0x21 + (rand()&0x3F);
470 Client->Salt[3] = 0x21 + (rand()&0x3F);
471 Client->Salt[4] = 0x21 + (rand()&0x3F);
472 Client->Salt[5] = 0x21 + (rand()&0x3F);
473 Client->Salt[6] = 0x21 + (rand()&0x3F);
474 Client->Salt[7] = 0x21 + (rand()&0x3F);
476 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
477 sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
479 sendf(Client->Socket, "100 User Set\n");
483 /// UID: User ID (must be valid)
484 /// username: Optional username
485 bool authenticate(tClient* Client, int UID, const char* username)
489 int flags = Bank_GetFlags(Client->UID);
490 if( flags & USER_FLAG_DISABLED ) {
492 sendf(Client->Socket, "403 Authentication failure: account disabled\n");
495 // You can't be an internal account
496 if( flags & USER_FLAG_INTERNAL ) {
498 Debug(Client, "IDENT auth as '%s', not allowed", username);
500 sendf(Client->Socket, "403 Authentication failure: that account is internal\n");
505 if(Client->Username != username)
509 free(Client->Username);
512 // Fetch username (if not provided)
515 Client->Username = strdup(username);
519 Client->Username = Bank_GetAcctName(UID);
523 Client->bIsAuthed = 1;
526 Debug(Client, "Auto authenticated as '%s' (%i)", Client->Username, Client->UID);
529 bool require_auth(tClient* Client)
531 // Check authentication
532 if( !Client->bIsAuthed ) {
533 sendf(Client->Socket, "401 Not Authenticated\n");
540 * \brief Authenticate as a user
544 void Server_Cmd_PASS(tClient *Client, char *Args)
548 if( Server_int_ParseArgs(0, Args, &passhash, NULL) )
550 sendf(Client->Socket, "407 PASS takes 1 argument\n");
554 // Pass on to cokebank
555 int uid = Bank_GetUserAuth(Client->Salt, Client->Username, passhash);
558 Debug(Client, "Unknown user '%s'", Client->Username);
559 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
562 if( ! authenticate(Client, uid, Client->Username) )
566 sendf(Client->Socket, "200 Auth OK\n");
570 * \brief Authenticate as a user without a password
572 * Usage: AUTOAUTH <user>
574 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
578 if( Server_int_ParseArgs(0, Args, &username, NULL) )
580 sendf(Client->Socket, "407 AUTOAUTH takes 1 argument\n");
585 if( !Client->bCanAutoAuth ) {
587 Debug(Client, "Untrusted client attempting to AUTOAUTH");
588 sendf(Client->Socket, "401 Untrusted\n");
593 int uid = Bank_GetAcctByName( username, /*bCreate=*/0 );
596 Debug(Client, "Unknown user '%s'", username);
597 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
600 if( ! authenticate(Client, uid, username) )
605 sendf(Client->Socket, "200 Auth OK\n");
609 * \brief Authenticate as a user using the IDENT protocol
613 void Server_Cmd_AUTHIDENT(tClient *Client, char *Args)
616 const int IDENT_TIMEOUT = 5;
618 if( Args != NULL && strlen(Args) ) {
619 sendf(Client->Socket, "407 AUTHIDENT takes no arguments\n");
624 if( !Client->bTrustedHost ) {
626 Debug(Client, "Untrusted client attempting to AUTHIDENT");
627 sendf(Client->Socket, "401 Untrusted\n");
631 // Get username via IDENT
632 username = ident_id(Client->Socket, IDENT_TIMEOUT);
634 perror("AUTHIDENT - IDENT timed out");
635 sendf(Client->Socket, "403 Authentication failure: IDENT auth timed out\n");
639 int uid = Bank_GetAcctByName(username, /*bCreate=*/0);
642 Debug(Client, "Unknown user '%s'", username);
643 sendf(Client->Socket, "403 Authentication failure: unknown account\n");
647 if( ! authenticate(Client, uid, username) )
654 sendf(Client->Socket, "200 Auth OK\n");
657 void Server_Cmd_AUTHCARD(tClient* Client, char *Args)
660 if( Server_int_ParseArgs(0, Args, &card_id, NULL) )
662 sendf(Client->Socket, "407 AUTHCARD takes 1 argument\n");
667 if( Client->UID != 0 )
670 Debug(Client, "Attempting to use AUTHCARD as non-root");
671 sendf(Client->Socket, "401 Untrusted\n");
675 int uid = Bank_GetAcctByCard(card_id);
679 Debug(Client, "Unknown MIFARE '%s'", card_id);
680 sendf(Client->Socket, "403 Authentication failure: unknown MIFARE ID\n");
683 if( ! authenticate(Client, uid, NULL) )
688 sendf(Client->Socket, "200 Auth Ok, username=%s\n", Client->Username);
692 * \brief Set effective user
694 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
697 int eUserFlags, userFlags;
699 if( Server_int_ParseArgs(0, Args, &username, NULL) )
701 sendf(Client->Socket, "407 SETEUSER takes 1 argument\n");
705 if( !strlen(Args) ) {
706 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
710 // Check authentication
711 if( !Client->bIsAuthed ) {
712 sendf(Client->Socket, "401 Not Authenticated\n");
716 // Check user permissions
717 userFlags = Bank_GetFlags(Client->UID);
718 if( !(userFlags & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
719 sendf(Client->Socket, "403 Not in coke\n");
724 Client->EffectiveUID = Bank_GetAcctByName(username, 0);
725 if( Client->EffectiveUID == -1 ) {
726 sendf(Client->Socket, "404 User not found\n");
729 // You can't be an internal account (unless you're an admin)
730 if( !(userFlags & USER_FLAG_ADMIN) )
732 eUserFlags = Bank_GetFlags(Client->EffectiveUID);
733 if( eUserFlags & USER_FLAG_INTERNAL ) {
734 Client->EffectiveUID = -1;
735 sendf(Client->Socket, "404 User not found\n");
741 // - If disabled and the actual user is not an admin (and not root)
743 if( (eUserFlags & USER_FLAG_DISABLED) && (Client->UID == 0 || !(userFlags & USER_FLAG_ADMIN)) ) {
744 Client->EffectiveUID = -1;
745 sendf(Client->Socket, "403 Account disabled\n");
749 sendf(Client->Socket, "200 User set\n");
753 * \brief Send an item status to the client
754 * \param Client Who to?
755 * \param Item Item to send
757 void Server_int_SendItem(tClient *Client, tItem *Item)
759 char *status = "avail";
761 if( Item->Handler->CanDispense )
763 switch(Item->Handler->CanDispense(Client->UID, Item->ID))
765 case 0: status = "avail"; break;
766 case 1: status = "sold"; break;
768 case -1: status = "error"; break;
772 if( !gbNoCostMode && Item->Price == 0 )
774 // KNOWN HACK: Naming a slot 'dead' disables it
775 if( strcmp(Item->Name, "dead") == 0 )
776 status = "sold"; // Another status?
778 sendf(Client->Socket,
779 "202 Item %s:%i %s %i %s\n",
780 Item->Handler->Name, Item->ID, status, Item->Price, Item->Name
785 * \brief Enumerate the items that the server knows about
787 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
791 if( Args != NULL && strlen(Args) ) {
792 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
798 for( i = 0; i < giNumItems; i ++ ) {
799 if( gaItems[i].bHidden ) continue;
803 sendf(Client->Socket, "201 Items %i\n", count);
805 for( i = 0; i < giNumItems; i ++ ) {
806 if( gaItems[i].bHidden ) continue;
807 Server_int_SendItem( Client, &gaItems[i] );
810 sendf(Client->Socket, "200 List end\n");
813 tItem *_GetItemFromString(char *String)
817 char *colon = strchr(String, ':');
829 for( i = 0; i < giNumHandlers; i ++ )
831 if( strcmp(gaHandlers[i]->Name, type) == 0) {
832 handler = gaHandlers[i];
841 for( i = 0; i < giNumItems; i ++ )
843 if( gaItems[i].Handler != handler ) continue;
844 if( gaItems[i].ID != num ) continue;
851 * \brief Fetch information on a specific item
853 * Usage: ITEMINFO <item ID>
855 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
860 if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
861 sendf(Client->Socket, "407 ITEMINFO takes 1 argument\n");
864 item = _GetItemFromString(Args);
867 sendf(Client->Socket, "406 Bad Item ID\n");
871 Server_int_SendItem( Client, item );
875 * \brief Dispense an item
877 * Usage: DISPENSE <Item ID>
879 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
886 if( Server_int_ParseArgs(0, Args, &itemname, NULL) ) {
887 sendf(Client->Socket, "407 DISPENSE takes only 1 argument\n");
891 if( !Client->bIsAuthed ) {
892 sendf(Client->Socket, "401 Not Authenticated\n");
896 item = _GetItemFromString(itemname);
898 sendf(Client->Socket, "406 Bad Item ID\n");
902 if( Client->EffectiveUID != -1 ) {
903 uid = Client->EffectiveUID;
909 // if( Bank_GetFlags(Client->UID) & USER_FLAG_DISABLED ) {
912 switch( ret = DispenseItem( Client->UID, uid, item ) )
914 case 0: sendf(Client->Socket, "200 Dispense OK\n"); return ;
915 case 1: sendf(Client->Socket, "501 Unable to dispense\n"); return ;
916 case 2: sendf(Client->Socket, "402 Poor You\n"); return ;
918 sendf(Client->Socket, "500 Dispense Error (%i)\n", ret);
924 * \brief Refund an item to a user
926 * Usage: REFUND <user> <item id> [<price>]
928 void Server_Cmd_REFUND(tClient *Client, char *Args)
931 int uid, price_override = 0;
932 char *username, *itemname, *price_str;
934 if( Server_int_ParseArgs(0, Args, &username, &itemname, &price_str, NULL) ) {
935 if( !itemname || price_str ) {
936 sendf(Client->Socket, "407 REFUND takes 2 or 3 arguments\n");
941 if( !Client->bIsAuthed ) {
942 sendf(Client->Socket, "401 Not Authenticated\n");
946 // Check user permissions
947 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
948 sendf(Client->Socket, "403 Not in coke\n");
952 uid = Bank_GetAcctByName(username, 0);
954 sendf(Client->Socket, "404 Unknown user\n");
958 item = _GetItemFromString(itemname);
960 sendf(Client->Socket, "406 Bad Item ID\n");
965 price_override = atoi(price_str);
967 switch( DispenseRefund( Client->UID, uid, item, price_override ) )
969 case 0: sendf(Client->Socket, "200 Item Refunded\n"); return ;
971 sendf(Client->Socket, "500 Dispense Error\n");
977 * \brief Transfer money to another account
979 * Usage: GIVE <dest> <ammount> <reason...>
981 void Server_Cmd_GIVE(tClient *Client, char *Args)
983 char *recipient, *ammount, *reason;
988 if( Server_int_ParseArgs(1, Args, &recipient, &ammount, &reason, NULL) ) {
989 sendf(Client->Socket, "407 GIVE takes only 3 arguments\n");
994 if( !Client->bIsAuthed ) {
995 sendf(Client->Socket, "401 Not Authenticated\n");
1000 uid = Bank_GetAcctByName(recipient, 0);
1002 sendf(Client->Socket, "404 Invalid target user\n");
1006 // You can't alter an internal account
1007 // if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
1008 // sendf(Client->Socket, "404 Invalid target user\n");
1013 iAmmount = atoi(ammount);
1014 if( iAmmount <= 0 ) {
1015 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
1019 if( Client->EffectiveUID != -1 ) {
1020 thisUid = Client->EffectiveUID;
1023 thisUid = Client->UID;
1027 switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
1030 sendf(Client->Socket, "200 Give OK\n");
1033 sendf(Client->Socket, "402 Poor You\n");
1036 sendf(Client->Socket, "500 Unknown error\n");
1041 void Server_Cmd_DONATE(tClient *Client, char *Args)
1043 char *ammount, *reason;
1048 if( Server_int_ParseArgs(1, Args, &ammount, &reason, NULL) ) {
1049 sendf(Client->Socket, "407 DONATE takes 2 arguments\n");
1053 if( !Client->bIsAuthed ) {
1054 sendf(Client->Socket, "401 Not Authenticated\n");
1059 iAmmount = atoi(ammount);
1060 if( iAmmount <= 0 ) {
1061 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
1065 // Handle effective users
1066 if( Client->EffectiveUID != -1 ) {
1067 thisUid = Client->EffectiveUID;
1070 thisUid = Client->UID;
1074 switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
1077 sendf(Client->Socket, "200 Give OK\n");
1080 sendf(Client->Socket, "402 Poor You\n");
1083 sendf(Client->Socket, "500 Unknown error\n");
1088 void Server_Cmd_ADD(tClient *Client, char *Args)
1090 char *user, *ammount, *reason;
1094 if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
1095 sendf(Client->Socket, "407 ADD takes 3 arguments\n");
1099 if( !Client->bIsAuthed ) {
1100 sendf(Client->Socket, "401 Not Authenticated\n");
1104 // Check user permissions
1105 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1106 sendf(Client->Socket, "403 Not in coke\n");
1111 if( strcmp( Client->Username, "root" ) == 0 ) {
1112 // Allow adding for new users
1113 if( strcmp(reason, "treasurer: new user") != 0 ) {
1114 sendf(Client->Socket, "403 Root may not add\n");
1121 if( strstr(reason, "refund") != NULL || strstr(reason, "misdispense") != NULL )
1123 sendf(Client->Socket, "499 Don't use `dispense acct` for refunds, use `dispense refund` (and `dispense -G` to get item IDs)\n");
1129 uid = Bank_GetAcctByName(user, 0);
1131 sendf(Client->Socket, "404 Invalid user\n");
1135 // You can't alter an internal account
1136 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) )
1138 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
1139 sendf(Client->Socket, "403 Admin only\n");
1142 // TODO: Maybe disallow changes to disabled?
1146 iAmmount = atoi(ammount);
1147 if( iAmmount == 0 && ammount[0] != '0' ) {
1148 sendf(Client->Socket, "407 Invalid Argument\n");
1153 switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
1156 sendf(Client->Socket, "200 Add OK\n");
1159 sendf(Client->Socket, "402 Poor Guy\n");
1162 sendf(Client->Socket, "500 Unknown error\n");
1167 void Server_Cmd_SET(tClient *Client, char *Args)
1169 char *user, *ammount, *reason;
1173 if( Server_int_ParseArgs(1, Args, &user, &ammount, &reason, NULL) ) {
1174 sendf(Client->Socket, "407 SET takes 3 arguments\n");
1178 if( !Client->bIsAuthed ) {
1179 sendf(Client->Socket, "401 Not Authenticated\n");
1183 // Check user permissions
1184 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1185 sendf(Client->Socket, "403 Not an admin\n");
1190 uid = Bank_GetAcctByName(user, 0);
1192 sendf(Client->Socket, "404 Invalid user\n");
1197 iAmmount = atoi(ammount);
1198 if( iAmmount == 0 && ammount[0] != '0' ) {
1199 sendf(Client->Socket, "407 Invalid Argument\n");
1203 int origBalance, rv;
1205 switch( rv = DispenseSet(Client->UID, uid, iAmmount, reason, &origBalance) )
1208 sendf(Client->Socket, "200 Add OK (%i)\n", origBalance);
1211 sendf(Client->Socket, "500 Unknown error (%i)\n", rv);
1216 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
1220 int maxBal = INT_MAX, minBal = INT_MIN;
1221 int flagMask = 0, flagVal = 0;
1222 int sort = BANK_ITFLAG_SORT_NAME;
1223 time_t lastSeenAfter=0, lastSeenBefore=0;
1225 int flags; // Iterator flags
1226 int balValue; // Balance value for iterator
1227 time_t timeValue; // Time value for iterator
1230 if( Args && strlen(Args) )
1232 char *space = Args, *type, *val;
1236 while(*type == ' ') type ++;
1238 space = strchr(space, ' ');
1239 if(space) *space = '\0';
1242 val = strchr(type, ':');
1249 if( strcmp(type, "min_balance") == 0 ) {
1252 // - Maximum Balance
1253 else if( strcmp(type, "max_balance") == 0 ) {
1257 else if( strcmp(type, "flags") == 0 ) {
1258 if( Server_int_ParseFlags(Client, val, &flagMask, &flagVal) )
1261 // - Last seen before timestamp
1262 else if( strcmp(type, "last_seen_before") == 0 ) {
1263 lastSeenAfter = atoll(val);
1265 // - Last seen after timestamp
1266 else if( strcmp(type, "last_seen_after") == 0 ) {
1267 lastSeenAfter = atoll(val);
1270 else if( strcmp(type, "sort") == 0 ) {
1271 char *dash = strchr(val, '-');
1276 if( strcmp(val, "name") == 0 ) {
1277 sort = BANK_ITFLAG_SORT_NAME;
1279 else if( strcmp(val, "balance") == 0 ) {
1280 sort = BANK_ITFLAG_SORT_BAL;
1282 else if( strcmp(val, "lastseen") == 0 ) {
1283 sort = BANK_ITFLAG_SORT_LASTSEEN;
1286 sendf(Client->Socket, "407 Unknown sort field ('%s')\n", val);
1289 // Handle sort direction
1291 if( strcmp(dash, "desc") == 0 ) {
1292 sort |= BANK_ITFLAG_REVSORT;
1295 sendf(Client->Socket, "407 Unknown sort direction '%s'\n", dash);
1302 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s:%s'\n", type, val);
1309 sendf(Client->Socket, "407 Unknown argument to ENUM_USERS '%s'\n", type);
1315 *space = ' '; // Repair (to be nice)
1317 while(*space == ' ') space ++;
1323 if( maxBal != INT_MAX ) {
1324 flags = sort|BANK_ITFLAG_MAXBALANCE;
1327 else if( minBal != INT_MIN ) {
1328 flags = sort|BANK_ITFLAG_MINBALANCE;
1335 if( lastSeenBefore ) {
1336 timeValue = lastSeenBefore;
1337 flags |= BANK_ITFLAG_SEENBEFORE;
1339 else if( lastSeenAfter ) {
1340 timeValue = lastSeenAfter;
1341 flags |= BANK_ITFLAG_SEENAFTER;
1346 it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1348 // Get return number
1349 while( (i = Bank_IteratorNext(it)) != -1 )
1351 int bal = Bank_GetBalance(i);
1353 if( bal == INT_MIN ) continue;
1355 if( bal < minBal ) continue;
1356 if( bal > maxBal ) continue;
1361 Bank_DelIterator(it);
1364 sendf(Client->Socket, "201 Users %i\n", numRet);
1368 it = Bank_Iterator(flagMask, flagVal, flags, balValue, timeValue);
1370 while( (i = Bank_IteratorNext(it)) != -1 )
1372 int bal = Bank_GetBalance(i);
1374 if( bal == INT_MIN ) continue;
1376 if( bal < minBal ) continue;
1377 if( bal > maxBal ) continue;
1379 _SendUserInfo(Client, i);
1382 Bank_DelIterator(it);
1384 sendf(Client->Socket, "200 List End\n");
1387 void Server_Cmd_USERINFO(tClient *Client, char *Args)
1393 if( Server_int_ParseArgs(0, Args, &user, NULL) ) {
1394 sendf(Client->Socket, "407 USER_INFO takes 1 argument\n");
1398 if( giDebugLevel ) Debug(Client, "User Info '%s'", user);
1401 uid = Bank_GetAcctByName(user, 0);
1403 if( giDebugLevel >= 2 ) Debug(Client, "uid = %i", uid);
1405 sendf(Client->Socket, "404 Invalid user\n");
1409 _SendUserInfo(Client, uid);
1412 void _SendUserInfo(tClient *Client, int UserID)
1414 char *type, *disabled="", *door="";
1415 int flags = Bank_GetFlags(UserID);
1417 if( flags & USER_FLAG_INTERNAL ) {
1420 else if( flags & USER_FLAG_COKE ) {
1421 if( flags & USER_FLAG_ADMIN )
1422 type = "coke,admin";
1426 else if( flags & USER_FLAG_ADMIN ) {
1433 if( flags & USER_FLAG_DISABLED )
1434 disabled = ",disabled";
1435 if( flags & USER_FLAG_DOORGROUP )
1438 // TODO: User flags/type
1440 Client->Socket, "202 User %s %i %s%s%s\n",
1441 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
1442 type, disabled, door
1446 void Server_Cmd_USERADD(tClient *Client, char *Args)
1451 if( Server_int_ParseArgs(0, Args, &username, NULL) ) {
1452 sendf(Client->Socket, "407 USER_ADD takes 1 argument\n");
1456 // Check authentication
1457 if( !Client->bIsAuthed ) {
1458 sendf(Client->Socket, "401 Not Authenticated\n");
1462 // Check permissions
1463 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1464 sendf(Client->Socket, "403 Not a coke admin\n");
1468 // Try to create user
1469 if( Bank_CreateAcct(username) == -1 ) {
1470 sendf(Client->Socket, "404 User exists\n");
1475 char *thisName = Bank_GetAcctName(Client->UID);
1476 Log_Info("Account '%s' created by '%s'", username, thisName);
1480 sendf(Client->Socket, "200 User Added\n");
1483 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
1485 char *username, *flags, *reason=NULL;
1486 int mask=0, value=0;
1490 if( Server_int_ParseArgs(1, Args, &username, &flags, &reason, NULL) ) {
1492 sendf(Client->Socket, "407 USER_FLAGS takes at least 2 arguments\n");
1498 // Check authentication
1499 if(!require_auth(Client)) return;
1501 // Check permissions
1502 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
1503 sendf(Client->Socket, "403 Not a coke admin\n");
1508 uid = Bank_GetAcctByName(username, 0);
1510 sendf(Client->Socket, "404 User '%s' not found\n", username);
1515 if( Server_int_ParseFlags(Client, flags, &mask, &value) )
1519 Debug(Client, "Set %i(%s) flags to %x (masked %x)\n",
1520 uid, username, mask, value);
1523 Bank_SetFlags(uid, mask, value);
1526 Log_Info("Updated '%s' with flag set '%s' by '%s' - Reason: %s",
1527 username, flags, Client->Username, reason);
1530 sendf(Client->Socket, "200 User Updated\n");
1533 void Server_Cmd_UPDATEITEM(tClient *Client, char *Args)
1535 char *itemname, *price_str, *description;
1539 if( Server_int_ParseArgs(1, Args, &itemname, &price_str, &description, NULL) ) {
1540 sendf(Client->Socket, "407 UPDATE_ITEM takes 3 arguments\n");
1544 if(!require_auth(Client)) return;
1546 // Check user permissions
1547 if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1548 sendf(Client->Socket, "403 Not in coke\n");
1552 item = _GetItemFromString(itemname);
1554 // TODO: Create item?
1555 sendf(Client->Socket, "406 Bad Item ID\n");
1559 price = atoi(price_str);
1560 if( price <= 0 && price_str[0] != '0' ) {
1561 sendf(Client->Socket, "407 Invalid price set\n");
1564 switch( DispenseUpdateItem( Client->UID, item, description, price ) )
1568 sendf(Client->Socket, "200 Item updated\n");
1575 void Server_Cmd_PINCHECK(tClient *Client, char *Args)
1577 char *username, *pinstr;
1580 if( Server_int_ParseArgs(0, Args, &username, &pinstr, NULL) ) {
1581 sendf(Client->Socket, "407 PIN_CHECK takes 2 arguments\n");
1585 if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
1586 sendf(Client->Socket, "407 PIN should be four digits\n");
1591 if(!require_auth(Client)) return;
1594 int uid = Bank_GetAcctByName(username, 0);
1596 sendf(Client->Socket, "404 User '%s' not found\n", username);
1600 // Check user permissions
1601 if( uid != Client->UID && !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN)) ) {
1602 sendf(Client->Socket, "403 Not in coke\n");
1607 static time_t last_wrong_pin_time;
1608 static int backoff = 1;
1609 if( time(NULL) - last_wrong_pin_time < backoff ) {
1610 sendf(Client->Socket, "407 Rate limited (%i seconds remaining)\n",
1611 backoff - (time(NULL) - last_wrong_pin_time));
1614 last_wrong_pin_time = time(NULL);
1615 if( !Bank_IsPinValid(uid, pin) )
1617 sendf(Client->Socket, "401 Pin incorrect\n");
1618 struct sockaddr_storage addr;
1619 socklen_t len = sizeof(addr);
1620 char ipstr[INET6_ADDRSTRLEN];
1621 getpeername(Client->Socket, (void*)&addr, &len);
1622 struct sockaddr_in *s = (struct sockaddr_in *)&addr;
1623 inet_ntop(addr.ss_family, &s->sin_addr, ipstr, sizeof(ipstr));
1624 Debug_Notice("Bad pin from %s for %s by %i", ipstr, username, Client->UID);
1630 last_wrong_pin_time = 0;
1632 sendf(Client->Socket, "200 Pin correct\n");
1635 void Server_Cmd_PINSET(tClient *Client, char *Args)
1641 if( Server_int_ParseArgs(0, Args, &pinstr, NULL) ) {
1642 sendf(Client->Socket, "407 PIN_SET takes 1 argument\n");
1646 if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
1647 sendf(Client->Socket, "407 PIN should be four digits\n");
1652 if(!require_auth(Client)) return;
1654 int uid = Client->EffectiveUID;
1657 // Can only pinset yourself (well, the effective user)
1658 Bank_SetPin(uid, pin);
1659 sendf(Client->Socket, "200 Pin updated\n");
1662 void Server_Cmd_CARDADD(tClient* Client, char* Args)
1665 if( Server_int_ParseArgs(0, Args, &card_id, NULL) ) {
1666 sendf(Client->Socket, "407 CARD_ADD takes 1 argument\n");
1670 if(!require_auth(Client)) return;
1672 if( Bank_AddAcctCard(Client->UID, card_id) )
1674 sendf(Client->Socket, "408 Card already exists\n");
1677 sendf(Client->Socket, "200 Card added\n");
1680 // --- INTERNAL HELPERS ---
1681 void Debug(tClient *Client, const char *Format, ...)
1684 //printf("%010i [%i] ", (int)time(NULL), Client->ID);
1685 printf("[%i] ", Client->ID);
1686 va_start(args, Format);
1687 vprintf(Format, args);
1692 int sendf(int Socket, const char *Format, ...)
1697 va_start(args, Format);
1698 len = vsnprintf(NULL, 0, Format, args);
1703 va_start(args, Format);
1704 vsnprintf(buf, len+1, Format, args);
1707 #if DEBUG_TRACE_CLIENT
1708 printf("sendf: %s", buf);
1711 return send(Socket, buf, len, 0);
1715 // Takes a series of char *'s in
1717 * \brief Parse space-separated entries into
1719 int Server_int_ParseArgs(int bUseLongLast, char *ArgStr, ...)
1724 va_start(args, ArgStr);
1729 while( (dest = va_arg(args, char **)) )
1735 savedChar = *ArgStr;
1737 while( (dest = va_arg(args, char **)) )
1739 // Trim leading spaces
1740 while( *ArgStr == ' ' || *ArgStr == '\t' )
1743 // ... oops, not enough arguments
1744 if( *ArgStr == '\0' )
1746 // NULL unset arguments
1749 } while( (dest = va_arg(args, char **)) );
1754 if( *ArgStr == '"' )
1759 while( *ArgStr && *ArgStr != '"' )
1766 // Read until a space
1767 while( *ArgStr && *ArgStr != ' ' && *ArgStr != '\t' )
1770 savedChar = *ArgStr; // savedChar is used to un-mangle the last string
1776 // Oops, extra arguments, and greedy not set
1777 if( (savedChar == ' ' || savedChar == '\t') && !bUseLongLast ) {
1784 *ArgStr = savedChar;
1787 return 0; // Success!
1790 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1797 {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1798 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1799 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1800 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1801 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1803 const int ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1815 while( *Str == ' ' ) Str ++; // Eat whitespace
1816 space = strchr(Str, ','); // Find the end of the flag
1822 // Check for inversion/removal
1823 if( *Str == '!' || *Str == '-' ) {
1827 else if( *Str == '+' ) {
1831 // Check flag values
1832 for( i = 0; i < ciNumFlags; i ++ )
1834 if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1835 *Mask |= cFLAGS[i].Mask;
1836 *Value &= ~cFLAGS[i].Mask;
1838 *Value |= cFLAGS[i].Value;
1844 if( i == ciNumFlags ) {
1846 strncpy(val, Str, len+1);
1847 sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);