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 Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value);
70 int sendf(int Socket, const char *Format, ...);
74 const struct sClientCommand {
76 void (*Function)(tClient *Client, char *Arguments);
77 } gaServer_Commands[] = {
78 {"USER", Server_Cmd_USER},
79 {"PASS", Server_Cmd_PASS},
80 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
81 {"SETEUSER", Server_Cmd_SETEUSER},
82 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
83 {"ITEM_INFO", Server_Cmd_ITEMINFO},
84 {"DISPENSE", Server_Cmd_DISPENSE},
85 {"GIVE", Server_Cmd_GIVE},
86 {"ADD", Server_Cmd_ADD},
87 {"ENUM_USERS", Server_Cmd_ENUMUSERS},
88 {"USER_INFO", Server_Cmd_USERINFO},
89 {"USER_ADD", Server_Cmd_USERADD},
90 {"USER_FLAGS", Server_Cmd_USERFLAGS}
92 #define NUM_COMMANDS ((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
95 int giServer_Port = 1020;
96 int giServer_NextClientID = 1;
101 * \brief Open listenting socket and serve connections
103 void Server_Start(void)
106 struct sockaddr_in server_addr, client_addr;
108 atexit(Server_Cleanup);
111 giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
112 if( giServer_Socket < 0 ) {
113 fprintf(stderr, "ERROR: Unable to create server socket\n");
117 // Make listen address
118 memset(&server_addr, 0, sizeof(server_addr));
119 server_addr.sin_family = AF_INET; // Internet Socket
120 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
121 server_addr.sin_port = htons(giServer_Port); // Port
124 if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
125 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
131 if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
132 fprintf(stderr, "ERROR: Unable to listen to socket\n");
137 printf("Listening on 0.0.0.0:%i\n", giServer_Port);
141 uint len = sizeof(client_addr);
144 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
145 if(client_socket < 0) {
146 fprintf(stderr, "ERROR: Unable to accept client connection\n");
150 if(giDebugLevel >= 2) {
151 char ipstr[INET_ADDRSTRLEN];
152 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
153 printf("Client connection from %s:%i\n",
154 ipstr, ntohs(client_addr.sin_port));
157 // Trusted Connections
158 if( ntohs(client_addr.sin_port) < 1024 )
160 // TODO: Make this runtime configurable
161 switch( ntohl( client_addr.sin_addr.s_addr ) )
163 case 0x7F000001: // 127.0.0.1 localhost
164 //case 0x825E0D00: // 130.95.13.0
165 case 0x825E0D12: // 130.95.13.18 mussel
166 case 0x825E0D17: // 130.95.13.23 martello
174 // TODO: Multithread this?
175 Server_HandleClient(client_socket, bTrusted);
177 close(client_socket);
181 void Server_Cleanup(void)
183 printf("Close(%i)\n", giServer_Socket);
184 close(giServer_Socket);
188 * \brief Reads from a client socket and parses the command strings
189 * \param Socket Client socket number/handle
190 * \param bTrusted Is the client trusted?
192 void Server_HandleClient(int Socket, int bTrusted)
194 char inbuf[INPUT_BUFFER_SIZE];
196 int remspace = INPUT_BUFFER_SIZE-1;
200 memset(&clientInfo, 0, sizeof(clientInfo));
202 // Initialise Client info
203 clientInfo.Socket = Socket;
204 clientInfo.ID = giServer_NextClientID ++;
205 clientInfo.bIsTrusted = bTrusted;
210 * - The `buf` and `remspace` variables allow a line to span several
211 * calls to recv(), if a line is not completed in one recv() call
212 * it is saved to the beginning of `inbuf` and `buf` is updated to
215 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
218 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
222 while( (eol = strchr(start, '\n')) )
226 Server_ParseClientCommand(&clientInfo, start);
231 // Check if there was an incomplete line
232 if( *start != '\0' ) {
233 int tailBytes = bytes - (start-buf);
234 // Roll back in buffer
235 memcpy(inbuf, start, tailBytes);
236 remspace -= tailBytes;
238 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
240 remspace = INPUT_BUFFER_SIZE - 1;
245 remspace = INPUT_BUFFER_SIZE - 1;
251 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
255 if(giDebugLevel >= 2) {
256 printf("Client %i: Disconnected\n", clientInfo.ID);
261 * \brief Parses a client command and calls the required helper function
262 * \param Client Pointer to client state structure
263 * \param CommandString Command from client (single line of the command)
264 * \return Heap String to return to the client
266 void Server_ParseClientCommand(tClient *Client, char *CommandString)
275 // Split at first space
276 space = strchr(CommandString, ' ');
283 while( *space == ' ' ) space ++;
288 for( i = 0; args[i]; )
290 while( CommandString[i] != ' ' ) {
291 if( CommandString[i] == '"' ) {
292 while( !(CommandString[i] != '\\' CommandString[i+1] == '"' ) )
299 while( CommandString[i] == ' ' ) i ++;
306 for( i = 0; i < NUM_COMMANDS; i++ )
308 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) {
309 gaServer_Commands[i].Function(Client, args);
314 sendf(Client->Socket, "400 Unknown Command\n");
321 * \brief Set client username
323 * Usage: USER <username>
325 void Server_Cmd_USER(tClient *Client, char *Args)
327 char *space = strchr(Args, ' ');
328 if(space) *space = '\0'; // Remove characters after the ' '
332 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
336 free(Client->Username);
337 Client->Username = strdup(Args);
340 // Create a salt (that changes if the username is changed)
341 // Yes, I know, I'm a little paranoid, but who isn't?
342 Client->Salt[0] = 0x21 + (rand()&0x3F);
343 Client->Salt[1] = 0x21 + (rand()&0x3F);
344 Client->Salt[2] = 0x21 + (rand()&0x3F);
345 Client->Salt[3] = 0x21 + (rand()&0x3F);
346 Client->Salt[4] = 0x21 + (rand()&0x3F);
347 Client->Salt[5] = 0x21 + (rand()&0x3F);
348 Client->Salt[6] = 0x21 + (rand()&0x3F);
349 Client->Salt[7] = 0x21 + (rand()&0x3F);
351 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
352 sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
354 sendf(Client->Socket, "100 User Set\n");
359 * \brief Authenticate as a user
363 void Server_Cmd_PASS(tClient *Client, char *Args)
365 char *space = strchr(Args, ' ');
366 if(space) *space = '\0'; // Remove characters after the ' '
368 // Pass on to cokebank
369 Client->UID = Bank_GetUserAuth(Client->Salt, Client->Username, Args);
371 if( Client->UID != -1 ) {
372 Client->bIsAuthed = 1;
373 sendf(Client->Socket, "200 Auth OK\n");
377 sendf(Client->Socket, "401 Auth Failure\n");
381 * \brief Authenticate as a user without a password
383 * Usage: AUTOAUTH <user>
385 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
387 char *space = strchr(Args, ' ');
388 if(space) *space = '\0'; // Remove characters after the ' '
391 if( !Client->bIsTrusted ) {
393 printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
394 sendf(Client->Socket, "401 Untrusted\n");
399 Client->UID = Bank_GetAcctByName( Args );
400 if( Client->UID < 0 ) {
402 printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
403 sendf(Client->Socket, "401 Auth Failure\n");
407 // You can't be an internal account
408 if( Bank_GetFlags(Client->UID) & USER_FLAG_INTERNAL ) {
410 sendf(Client->Socket, "401 Auth Failure\n");
415 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
417 sendf(Client->Socket, "200 Auth OK\n");
421 * \brief Set effective user
423 void Server_Cmd_SETEUSER(tClient *Client, char *Args)
427 space = strchr(Args, ' ');
429 if(space) *space = '\0';
431 if( !strlen(Args) ) {
432 sendf(Client->Socket, "407 SETEUSER expects an argument\n");
436 // Check user permissions
437 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_COKE) ) {
438 sendf(Client->Socket, "403 Not in coke\n");
443 Client->EffectiveUID = Bank_GetAcctByName(Args);
444 if( Client->EffectiveUID == -1 ) {
445 sendf(Client->Socket, "404 User not found\n");
449 // You can't be an internal account
450 if( Bank_GetFlags(Client->EffectiveUID) & USER_FLAG_INTERNAL ) {
451 Client->EffectiveUID = -1;
452 sendf(Client->Socket, "404 User not found\n");
456 sendf(Client->Socket, "200 User set\n");
460 * \brief Enumerate the items that the server knows about
462 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
466 if( Args != NULL && strlen(Args) ) {
467 sendf(Client->Socket, "407 ENUM_ITEMS takes no arguments\n");
471 sendf(Client->Socket, "201 Items %i\n", giNumItems);
473 for( i = 0; i < giNumItems; i ++ ) {
474 sendf(Client->Socket,
475 "202 Item %s:%i %i %s\n",
476 gaItems[i].Handler->Name, gaItems[i].ID, gaItems[i].Price, gaItems[i].Name
480 sendf(Client->Socket, "200 List end\n");
483 tItem *_GetItemFromString(char *String)
487 char *colon = strchr(String, ':');
499 for( i = 0; i < giNumHandlers; i ++ )
501 if( strcmp(gaHandlers[i]->Name, type) == 0) {
502 handler = gaHandlers[i];
511 for( i = 0; i < giNumItems; i ++ )
513 if( gaItems[i].Handler != handler ) continue;
514 if( gaItems[i].ID != num ) continue;
521 * \brief Fetch information on a specific item
523 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
525 tItem *item = _GetItemFromString(Args);
528 sendf(Client->Socket, "406 Bad Item ID\n");
532 sendf(Client->Socket,
533 "202 Item %s:%i %i %s\n",
534 item->Handler->Name, item->ID, item->Price, item->Name
538 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
544 if( !Client->bIsAuthed ) {
545 sendf(Client->Socket, "401 Not Authenticated\n");
549 item = _GetItemFromString(Args);
551 sendf(Client->Socket, "406 Bad Item ID\n");
555 if( Client->EffectiveUID != -1 ) {
556 uid = Client->EffectiveUID;
562 switch( ret = DispenseItem( Client->UID, uid, item ) )
564 case 0: sendf(Client->Socket, "200 Dispense OK\n"); return ;
565 case 1: sendf(Client->Socket, "501 Unable to dispense\n"); return ;
566 case 2: sendf(Client->Socket, "402 Poor You\n"); return ;
568 sendf(Client->Socket, "500 Dispense Error\n");
573 void Server_Cmd_GIVE(tClient *Client, char *Args)
575 char *recipient, *ammount, *reason;
579 if( !Client->bIsAuthed ) {
580 sendf(Client->Socket, "401 Not Authenticated\n");
586 ammount = strchr(Args, ' ');
588 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
594 reason = strchr(ammount, ' ');
596 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
603 uid = Bank_GetAcctByName(recipient);
605 sendf(Client->Socket, "404 Invalid target user\n");
609 // You can't alter an internal account
610 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
611 sendf(Client->Socket, "404 Invalid target user\n");
616 iAmmount = atoi(ammount);
617 if( iAmmount <= 0 ) {
618 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
622 if( Client->EffectiveUID != -1 ) {
623 thisUid = Client->EffectiveUID;
626 thisUid = Client->UID;
630 switch( DispenseGive(Client->UID, thisUid, uid, iAmmount, reason) )
633 sendf(Client->Socket, "200 Give OK\n");
636 sendf(Client->Socket, "402 Poor You\n");
639 sendf(Client->Socket, "500 Unknown error\n");
644 void Server_Cmd_DONATE(tClient *Client, char *Args)
646 char *ammount, *reason;
650 if( !Client->bIsAuthed ) {
651 sendf(Client->Socket, "401 Not Authenticated\n");
657 // Get the start of the reason
658 reason = strchr(Args, ' ');
660 sendf(Client->Socket, "407 Invalid Argument, expected 2 parameters, 1 encountered\n");
666 // Check the end of the reason
667 if( strchr(reason, ' ') ) {
668 sendf(Client->Socket, "407 Invalid Argument, expected 2 parameters, more encountered\n");
673 iAmmount = atoi(ammount);
674 if( iAmmount <= 0 ) {
675 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
679 // Handle effective users
680 if( Client->EffectiveUID != -1 ) {
681 thisUid = Client->EffectiveUID;
684 thisUid = Client->UID;
688 switch( DispenseDonate(Client->UID, thisUid, iAmmount, reason) )
691 sendf(Client->Socket, "200 Give OK\n");
694 sendf(Client->Socket, "402 Poor You\n");
697 sendf(Client->Socket, "500 Unknown error\n");
702 void Server_Cmd_ADD(tClient *Client, char *Args)
704 char *user, *ammount, *reason;
707 if( !Client->bIsAuthed ) {
708 sendf(Client->Socket, "401 Not Authenticated\n");
714 ammount = strchr(Args, ' ');
716 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
722 reason = strchr(ammount, ' ');
724 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
730 // Check user permissions
731 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_COKE) ) {
732 sendf(Client->Socket, "403 Not in coke\n");
737 uid = Bank_GetAcctByName(user);
739 sendf(Client->Socket, "404 Invalid user\n");
743 // You can't alter an internal account
744 if( Bank_GetFlags(uid) & USER_FLAG_INTERNAL ) {
745 sendf(Client->Socket, "404 Invalid user\n");
750 iAmmount = atoi(ammount);
751 if( iAmmount == 0 && ammount[0] != '0' ) {
752 sendf(Client->Socket, "407 Invalid Argument\n");
757 switch( DispenseAdd(Client->UID, uid, iAmmount, reason) )
760 sendf(Client->Socket, "200 Add OK\n");
763 sendf(Client->Socket, "402 Poor Guy\n");
766 sendf(Client->Socket, "500 Unknown error\n");
771 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
774 int maxBal = INT_MAX, minBal = INT_MIN;
776 int sort = BANK_ITFLAG_SORT_NAME;
779 if( Args && strlen(Args) )
781 char *min = Args, *max;
783 max = strchr(Args, ' ');
789 // If <minBal> != "-"
790 if( strcmp(min, "-") != 0 )
792 // If <maxBal> != "-"
793 if( max && strcmp(max, "-") != 0 )
798 if( maxBal != INT_MAX )
799 it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MAXBALANCE, maxBal, 0);
800 else if( minBal != INT_MIN )
801 it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MINBALANCE, minBal, 0);
803 it = Bank_Iterator(0, 0, sort, 0, 0);
806 while( (i = Bank_IteratorNext(it)) != -1 )
808 int bal = Bank_GetBalance(i);
810 if( bal == INT_MIN ) continue;
812 if( bal < minBal ) continue;
813 if( bal > maxBal ) continue;
818 Bank_DelIterator(it);
821 sendf(Client->Socket, "201 Users %i\n", numRet);
825 if( maxBal != INT_MAX )
826 it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MAXBALANCE, maxBal, 0);
827 else if( minBal != INT_MIN )
828 it = Bank_Iterator(0, 0, sort|BANK_ITFLAG_MINBALANCE, minBal, 0);
830 it = Bank_Iterator(0, 0, sort, 0, 0);
832 while( (i = Bank_IteratorNext(it)) != -1 )
834 int bal = Bank_GetBalance(i);
836 if( bal == INT_MIN ) continue;
838 if( bal < minBal ) continue;
839 if( bal > maxBal ) continue;
841 _SendUserInfo(Client, i);
844 Bank_DelIterator(it);
846 sendf(Client->Socket, "200 List End\n");
849 void Server_Cmd_USERINFO(tClient *Client, char *Args)
855 space = strchr(user, ' ');
856 if(space) *space = '\0';
859 uid = Bank_GetAcctByName(user);
861 sendf(Client->Socket, "404 Invalid user");
865 _SendUserInfo(Client, uid);
868 void _SendUserInfo(tClient *Client, int UserID)
870 char *type, *disabled="", *door="";
871 int flags = Bank_GetFlags(UserID);
873 if( flags & USER_FLAG_INTERNAL ) {
876 else if( flags & USER_FLAG_COKE ) {
877 if( flags & USER_FLAG_ADMIN )
882 else if( flags & USER_FLAG_ADMIN ) {
889 if( flags & USER_FLAG_DISABLED )
890 disabled = ",disabled";
891 if( flags & USER_FLAG_DOORGROUP )
894 // TODO: User flags/type
896 Client->Socket, "202 User %s %i %s%s\n",
897 Bank_GetAcctName(UserID), Bank_GetBalance(UserID),
902 void Server_Cmd_USERADD(tClient *Client, char *Args)
904 char *username, *space;
907 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
908 sendf(Client->Socket, "403 Not a coke admin\n");
914 while( *username == ' ' ) username ++;
915 space = strchr(username, ' ');
916 if(space) *space = '\0';
918 // Try to create user
919 if( Bank_CreateAcct(username) == -1 ) {
920 sendf(Client->Socket, "404 User exists\n");
924 sendf(Client->Socket, "200 User Added\n");
927 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
929 char *username, *flags;
935 if( !(Bank_GetFlags(Client->UID) & USER_FLAG_ADMIN) ) {
936 sendf(Client->Socket, "403 Not a coke admin\n");
943 while( *username == ' ' ) username ++;
944 space = strchr(username, ' ');
946 sendf(Client->Socket, "407 USER_FLAGS requires 2 arguments, 1 given\n");
952 while( *flags == ' ' ) flags ++;
953 space = strchr(flags, ' ');
954 if(space) *space = '\0';
957 uid = Bank_GetAcctByName(username);
959 sendf(Client->Socket, "404 User '%s' not found\n", username);
964 if( Server_int_ParseFlags(Client, flags, &mask, &value) )
968 Bank_SetFlags(uid, mask, value);
971 sendf(Client->Socket, "200 User Updated\n");
974 // --- INTERNAL HELPERS ---
975 int sendf(int Socket, const char *Format, ...)
980 va_start(args, Format);
981 len = vsnprintf(NULL, 0, Format, args);
986 va_start(args, Format);
987 vsnprintf(buf, len+1, Format, args);
990 #if DEBUG_TRACE_CLIENT
991 printf("sendf: %s", buf);
994 return send(Socket, buf, len, 0);
998 int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Value)
1005 {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED}
1006 ,{"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP}
1007 ,{"coke", USER_FLAG_COKE, USER_FLAG_COKE}
1008 ,{"admin", USER_FLAG_ADMIN, USER_FLAG_ADMIN}
1009 ,{"internal", USER_FLAG_INTERNAL, USER_FLAG_INTERNAL}
1011 const int ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
1023 while( *Str == ' ' ) Str ++; // Eat whitespace
1024 space = strchr(Str, ','); // Find the end of the flag
1030 // Check for inversion/removal
1031 if( *Str == '!' || *Str == '-' ) {
1035 else if( *Str == '+' ) {
1039 // Check flag values
1040 for( i = 0; i < ciNumFlags; i ++ )
1042 if( strncmp(Str, cFLAGS[i].Name, len) == 0 ) {
1043 *Mask |= cFLAGS[i].Mask;
1044 *Value &= ~cFLAGS[i].Mask;
1046 *Value |= cFLAGS[i].Value;
1052 if( i == ciNumFlags ) {
1054 strncpy(val, Str, len+1);
1055 sendf(Client->Socket, "407 Unknown flag value '%s'\n", val);