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 <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
21 #define DEBUG_TRACE_CLIENT 0
24 #define MAX_CONNECTION_QUEUE 5
25 #define INPUT_BUFFER_SIZE 256
27 #define HASH_TYPE SHA1
28 #define HASH_LENGTH 20
30 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
33 typedef struct sClient
35 int Socket; // Client socket ID
38 int bIsTrusted; // Is the connection from a trusted host/port
49 void Server_Start(void);
50 void Server_Cleanup(void);
51 void Server_HandleClient(int Socket, int bTrusted);
52 void Server_ParseClientCommand(tClient *Client, char *CommandString);
54 void Server_Cmd_USER(tClient *Client, char *Args);
55 void Server_Cmd_PASS(tClient *Client, char *Args);
56 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
57 void Server_Cmd_SETEUSER(tClient *Client, char *Args);
58 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
59 void Server_Cmd_ITEMINFO(tClient *Client, char *Args);
60 void Server_Cmd_DISPENSE(tClient *Client, char *Args);
61 void Server_Cmd_GIVE(tClient *Client, char *Args);
62 void Server_Cmd_ADD(tClient *Client, char *Args);
63 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
64 void Server_Cmd_USERINFO(tClient *Client, char *Args);
65 void _SendUserInfo(tClient *Client, int UserID);
66 void Server_Cmd_USERADD(tClient *Client, char *Args);
67 void Server_Cmd_USERFLAGS(tClient *Client, char *Args);
69 int sendf(int Socket, const char *Format, ...);
73 const struct sClientCommand {
75 void (*Function)(tClient *Client, char *Arguments);
76 } gaServer_Commands[] = {
77 {"USER", Server_Cmd_USER},
78 {"PASS", Server_Cmd_PASS},
79 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
80 {"SETEUSER", Server_Cmd_SETEUSER},
81 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
82 {"ITEM_INFO", Server_Cmd_ITEMINFO},
83 {"DISPENSE", Server_Cmd_DISPENSE},
84 {"GIVE", Server_Cmd_GIVE},
85 {"ADD", Server_Cmd_ADD},
86 {"ENUM_USERS", Server_Cmd_ENUMUSERS},
87 {"USER_INFO", Server_Cmd_USERINFO},
88 {"USER_ADD", Server_Cmd_USERADD},
89 {"USER_FLAGS", Server_Cmd_USERFLAGS}
91 #define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
94 int giServer_Port = 1020;
95 int giServer_NextClientID = 1;
100 * \brief Open listenting socket and serve connections
102 void Server_Start(void)
105 struct sockaddr_in server_addr, client_addr;
107 atexit(Server_Cleanup);
110 giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
111 if( giServer_Socket < 0 ) {
112 fprintf(stderr, "ERROR: Unable to create server socket\n");
116 // Make listen address
117 memset(&server_addr, 0, sizeof(server_addr));
118 server_addr.sin_family = AF_INET; // Internet Socket
119 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
120 server_addr.sin_port = htons(giServer_Port); // Port
123 if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
124 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
130 if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
131 fprintf(stderr, "ERROR: Unable to listen to socket\n");
136 printf("Listening on 0.0.0.0:%i\n", giServer_Port);
140 uint len = sizeof(client_addr);
143 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
144 if(client_socket < 0) {
145 fprintf(stderr, "ERROR: Unable to accept client connection\n");
149 if(giDebugLevel >= 2) {
150 char ipstr[INET_ADDRSTRLEN];
151 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
152 printf("Client connection from %s:%i\n",
153 ipstr, ntohs(client_addr.sin_port));
156 // Trusted Connections
157 if( ntohs(client_addr.sin_port) < 1024 )
159 // TODO: Make this runtime configurable
160 switch( ntohl( client_addr.sin_addr.s_addr ) )
162 case 0x7F000001: // 127.0.0.1 localhost
163 //case 0x825E0D00: // 130.95.13.0
164 case 0x825E0D12: // 130.95.13.18 mussel
165 case 0x825E0D17: // 130.95.13.23 martello
173 // TODO: Multithread this?
174 Server_HandleClient(client_socket, bTrusted);
176 close(client_socket);
180 void Server_Cleanup(void)
182 printf("Close(%i)\n", giServer_Socket);
183 close(giServer_Socket);
187 * \brief Reads from a client socket and parses the command strings
188 * \param Socket Client socket number/handle
189 * \param bTrusted Is the client trusted?
191 void Server_HandleClient(int Socket, int bTrusted)
193 char inbuf[INPUT_BUFFER_SIZE];
195 int remspace = INPUT_BUFFER_SIZE-1;
197 tClient clientInfo = {0};
199 // Initialise Client info
200 clientInfo.Socket = Socket;
201 clientInfo.ID = giServer_NextClientID ++;
202 clientInfo.bIsTrusted = bTrusted;
207 * - The `buf` and `remspace` variables allow a line to span several
208 * calls to recv(), if a line is not completed in one recv() call
209 * it is saved to the beginning of `inbuf` and `buf` is updated to
212 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
215 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
219 while( (eol = strchr(start, '\n')) )
223 Server_ParseClientCommand(&clientInfo, start);
228 // Check if there was an incomplete line
229 if( *start != '\0' ) {
230 int tailBytes = bytes - (start-buf);
231 // Roll back in buffer
232 memcpy(inbuf, start, tailBytes);
233 remspace -= tailBytes;
235 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
237 remspace = INPUT_BUFFER_SIZE - 1;
242 remspace = INPUT_BUFFER_SIZE - 1;
248 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
252 if(giDebugLevel >= 2) {
253 printf("Client %i: Disconnected\n", clientInfo.ID);
258 * \brief Parses a client command and calls the required helper function
259 * \param Client Pointer to client state structure
260 * \param CommandString Command from client (single line of the command)
261 * \return Heap String to return to the client
263 void Server_ParseClientCommand(tClient *Client, char *CommandString)
272 // Split at first space
273 space = strchr(CommandString, ' ');
280 while( *space == ' ' ) space ++;
285 for( i = 0; args[i]; )
287 while( CommandString[i] != ' ' ) {
288 if( CommandString[i] == '"' ) {
289 while( !(CommandString[i] != '\\' CommandString[i+1] == '"' ) )
296 while( CommandString[i] == ' ' ) i ++;
303 for( i = 0; i < NUM_COMMANDS; i++ )
305 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) {
306 gaServer_Commands[i].Function(Client, args);
311 sendf(Client->Socket, "400 Unknown Command\n");
318 * \brief Set client username
320 * Usage: USER <username>
322 void Server_Cmd_USER(tClient *Client, char *Args)
324 char *space = strchr(Args, ' ');
325 if(space) *space = '\0'; // Remove characters after the ' '
329 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
333 free(Client->Username);
334 Client->Username = strdup(Args);
337 // Create a salt (that changes if the username is changed)
338 // Yes, I know, I'm a little paranoid, but who isn't?
339 Client->Salt[0] = 0x21 + (rand()&0x3F);
340 Client->Salt[1] = 0x21 + (rand()&0x3F);
341 Client->Salt[2] = 0x21 + (rand()&0x3F);
342 Client->Salt[3] = 0x21 + (rand()&0x3F);
343 Client->Salt[4] = 0x21 + (rand()&0x3F);
344 Client->Salt[5] = 0x21 + (rand()&0x3F);
345 Client->Salt[6] = 0x21 + (rand()&0x3F);
346 Client->Salt[7] = 0x21 + (rand()&0x3F);
348 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
349 sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
351 sendf(Client->Socket, "100 User Set\n");
356 * \brief Authenticate as a user
360 void Server_Cmd_PASS(tClient *Client, char *Args)
362 char *space = strchr(Args, ' ');
363 if(space) *space = '\0'; // Remove characters after the ' '
365 // Pass on to cokebank
366 Client->UID = GetUserAuth(Client->Salt, Client->Username, Args);
368 if( Client->UID != -1 ) {
369 Client->bIsAuthed = 1;
370 sendf(Client->Socket, "200 Auth OK\n");
374 sendf(Client->Socket, "401 Auth Failure\n");
378 * \brief Authenticate as a user without a password
380 * Usage: AUTOAUTH <user>
382 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
384 char *space = strchr(Args, ' ');
385 if(space) *space = '\0'; // Remove characters after the ' '
388 if( !Client->bIsTrusted ) {
390 printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
391 sendf(Client->Socket, "401 Untrusted\n");
396 Client->UID = GetUserID( Args );
397 if( Client->UID < 0 ) {
399 printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
400 sendf(Client->Socket, "401 Auth Failure\n");
404 // You can't be an internal account
405 if( GetFlags(Client->UID) & USER_FLAG_INTERNAL ) {
407 sendf(Client->Socket, "401 Auth Failure\n");
412 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
414 sendf(Client->Socket, "200 Auth OK\n");
418 * \brief Set effective user
420 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
424 space = strchr(Args, ' ');
426 if(space) *space = '\0';
428 if( !strlen(Args) ) {
429 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
433 // Check user permissions
434 if( !(GetFlags(Client->UID) & USER_FLAG_COKE) ) {
435 sendf(Client->Socket, "403 Not in coke\n");
440 Client->EffectiveUID = GetUserID(Args);
441 if( Client->EffectiveUID == -1 ) {
442 sendf(Client->Socket, "404 User not found\n");
446 // You can't be an internal account
447 if( GetFlags(Client->EffectiveUID) & USER_FLAG_INTERNAL ) {
448 Client->EffectiveUID = -1;
449 sendf(Client->Socket, "404 User not found\n");
453 sendf(Client->Socket, "200 User set\n");
457 * \brief Enumerate the items that the server knows about
459 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
463 sendf(Client->Socket, "201 Items %i\n", giNumItems);
465 for( i = 0; i < giNumItems; i ++ ) {
466 sendf(Client->Socket,
467 "202 Item %s:%i %i %s\n",
468 gaItems[i].Handler->Name, gaItems[i].ID, gaItems[i].Price, gaItems[i].Name
472 sendf(Client->Socket, "200 List end\n");
475 tItem *_GetItemFromString(char *String)
479 char *colon = strchr(String, ':');
491 for( i = 0; i < giNumHandlers; i ++ )
493 if( strcmp(gaHandlers[i]->Name, type) == 0) {
494 handler = gaHandlers[i];
503 for( i = 0; i < giNumItems; i ++ )
505 if( gaItems[i].Handler != handler ) continue;
506 if( gaItems[i].ID != num ) continue;
513 * \brief Fetch information on a specific item
515 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
517 tItem *item = _GetItemFromString(Args);
520 sendf(Client->Socket, "406 Bad Item ID\n");
524 sendf(Client->Socket,
525 "202 Item %s:%i %i %s\n",
526 item->Handler->Name, item->ID, item->Price, item->Name
530 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
536 if( !Client->bIsAuthed ) {
537 sendf(Client->Socket, "401 Not Authenticated\n");
541 item = _GetItemFromString(Args);
543 sendf(Client->Socket, "406 Bad Item ID\n");
547 if( Client->EffectiveUID != -1 ) {
548 uid = Client->EffectiveUID;
554 switch( ret = DispenseItem( Client->UID, uid, item ) )
556 case 0: sendf(Client->Socket, "200 Dispense OK\n"); return ;
557 case 1: sendf(Client->Socket, "501 Unable to dispense\n"); return ;
558 case 2: sendf(Client->Socket, "402 Poor You\n"); return ;
560 sendf(Client->Socket, "500 Dispense Error\n");
565 void Server_Cmd_GIVE(tClient *Client, char *Args)
567 char *recipient, *ammount, *reason;
571 if( !Client->bIsAuthed ) {
572 sendf(Client->Socket, "401 Not Authenticated\n");
578 ammount = strchr(Args, ' ');
580 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
586 reason = strchr(ammount, ' ');
588 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
595 uid = GetUserID(recipient);
597 sendf(Client->Socket, "404 Invalid target user\n");
601 // You can't alter an internal account
602 if( GetFlags(uid) & USER_FLAG_INTERNAL ) {
603 sendf(Client->Socket, "404 Invalid target user\n");
608 iAmmount = atoi(ammount);
609 if( iAmmount <= 0 ) {
610 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
614 if( Client->EffectiveUID != -1 ) {
615 thisUid = Client->EffectiveUID;
618 thisUid = Client->UID;
622 switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
625 sendf(Client->Socket, "200 Give OK\n");
628 sendf(Client->Socket, "402 Poor You\n");
631 sendf(Client->Socket, "500 Unknown error\n");
636 void Server_Cmd_ADD(tClient *Client, char *Args)
638 char *user, *ammount, *reason;
641 if( !Client->bIsAuthed ) {
642 sendf(Client->Socket, "401 Not Authenticated\n");
648 ammount = strchr(Args, ' ');
650 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
656 reason = strchr(ammount, ' ');
658 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
664 // Check user permissions
665 if( !(GetFlags(Client->UID) & USER_FLAG_COKE) ) {
666 sendf(Client->Socket, "403 Not in coke\n");
671 uid = GetUserID(user);
673 sendf(Client->Socket, "404 Invalid user\n");
677 // You can't alter an internal account
678 if( GetFlags(uid) & USER_FLAG_INTERNAL ) {
679 sendf(Client->Socket, "404 Invalid user\n");
684 iAmmount = atoi(ammount);
685 if( iAmmount == 0 && ammount[0] != '0' ) {
686 sendf(Client->Socket, "407 Invalid Argument\n");
691 switch( DispenseAdd(uid, Client->UID, iAmmount, reason) )
694 sendf(Client->Socket, "200 Add OK\n");
697 sendf(Client->Socket, "402 Poor Guy\n");
700 sendf(Client->Socket, "500 Unknown error\n");
705 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
708 int maxBal = INT_MAX, minBal = INT_MIN;
709 int numUsr = GetMaxID();
712 if( Args && strlen(Args) )
714 char *min = Args, *max;
716 max = strchr(Args, ' ');
722 // If <minBal> != "-"
723 if( strcmp(min, "-") != 0 )
725 // If <maxBal> != "-"
726 if( max && strcmp(max, "-") != 0 )
731 for( i = 0; i < numUsr; i ++ )
733 int bal = GetBalance(i);
735 if( bal == INT_MIN ) continue;
737 if( bal < minBal ) continue;
738 if( bal > maxBal ) continue;
744 sendf(Client->Socket, "201 Users %i\n", numRet);
746 for( i = 0; i < numUsr; i ++ )
748 int bal = GetBalance(i);
750 if( bal == INT_MIN ) continue;
752 if( bal < minBal ) continue;
753 if( bal > maxBal ) continue;
755 _SendUserInfo(Client, i);
758 sendf(Client->Socket, "200 List End\n");
761 void Server_Cmd_USERINFO(tClient *Client, char *Args)
767 space = strchr(user, ' ');
768 if(space) *space = '\0';
771 uid = GetUserID(user);
773 sendf(Client->Socket, "404 Invalid user");
777 _SendUserInfo(Client, uid);
780 void _SendUserInfo(tClient *Client, int UserID)
782 char *type, *disabled="", *door="";
783 int flags = GetFlags(UserID);
785 if( flags & USER_FLAG_INTERNAL ) {
788 else if( flags & USER_FLAG_COKE ) {
789 if( flags & USER_FLAG_WHEEL )
794 else if( flags & USER_FLAG_WHEEL ) {
801 if( flags & USER_FLAG_DISABLED )
802 disabled = ",disabled";
803 if( flags & USER_FLAG_DOORGROUP )
806 // TODO: User flags/type
808 Client->Socket, "202 User %s %i %s%s\n",
809 GetUserName(UserID), GetBalance(UserID),
814 void Server_Cmd_USERADD(tClient *Client, char *Args)
816 char *username, *space;
819 if( !(GetFlags(Client->UID) & USER_FLAG_WHEEL) ) {
820 sendf(Client->Socket, "403 Not Wheel\n");
826 while( *username == ' ' ) username ++;
827 space = strchr(username, ' ');
828 if(space) *space = '\0';
830 // Try to create user
831 if( CreateUser(username) == -1 ) {
832 sendf(Client->Socket, "404 User exists\n");
836 sendf(Client->Socket, "200 User Added\n");
839 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
841 char *username, *flags;
847 if( !(GetFlags(Client->UID) & USER_FLAG_WHEEL) ) {
848 sendf(Client->Socket, "403 Not Wheel\n");
855 while( *username == ' ' ) username ++;
856 space = strchr(username, ' ');
858 sendf(Client->Socket, "407 USER_FLAGS requires 2 arguments, 1 given\n");
864 while( *flags == ' ' ) flags ++;
865 space = strchr(flags, ' ');
866 if(space) *space = '\0';
869 uid = GetUserID(username);
871 sendf(Client->Socket, "404 User '%s' not found\n", username);
884 {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
885 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
886 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
887 ,{"wheel", USER_FLAG_WHEEL, USER_FLAG_WHEEL}
888 // ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
890 const int ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
892 while( *flags == ' ' ) flags ++; // Eat whitespace
893 space = strchr(flags, ','); // Find the end of the flag
894 if(space) *space = '\0';
896 // Check for inversion/removal
897 if( *flags == '!' || *flags == '-' ) {
901 else if( *flags == '+' ) {
906 for( i = 0; i < ciNumFlags; i ++ )
908 if( strcmp(flags, cFLAGS[i].Name) == 0 ) {
909 mask |= cFLAGS[i].Mask;
910 value &= ~cFLAGS[i].Mask;
912 value |= cFLAGS[i].Value;
918 if( i == ciNumFlags ) {
919 sendf(Client->Socket, "407 Unknown flag value '%s'\n", flags);
927 SetFlags(uid, mask, value);
930 sendf(Client->Socket, "200 User Updated\n");
933 // --- INTERNAL HELPERS ---
934 int sendf(int Socket, const char *Format, ...)
939 va_start(args, Format);
940 len = vsnprintf(NULL, 0, Format, args);
945 va_start(args, Format);
946 vsnprintf(buf, len+1, Format, args);
949 #if DEBUG_TRACE_CLIENT
950 printf("sendf: %s", buf);
953 return send(Socket, buf, len, 0);