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>
22 #define HACK_TPG_NOAUTH 1
23 #define HACK_ROOT_NOAUTH 1
25 #define DEBUG_TRACE_CLIENT 0
28 #define MAX_CONNECTION_QUEUE 5
29 #define INPUT_BUFFER_SIZE 256
31 #define HASH_TYPE SHA1
32 #define HASH_LENGTH 20
34 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
37 typedef struct sClient
39 int Socket; // Client socket ID
42 int bIsTrusted; // Is the connection from a trusted host/port
52 void Server_Start(void);
53 void Server_Cleanup(void);
54 void Server_HandleClient(int Socket, int bTrusted);
55 void Server_ParseClientCommand(tClient *Client, char *CommandString);
57 void Server_Cmd_USER(tClient *Client, char *Args);
58 void Server_Cmd_PASS(tClient *Client, char *Args);
59 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
60 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
61 void Server_Cmd_ITEMINFO(tClient *Client, char *Args);
62 void Server_Cmd_DISPENSE(tClient *Client, char *Args);
63 void Server_Cmd_GIVE(tClient *Client, char *Args);
64 void Server_Cmd_ADD(tClient *Client, char *Args);
65 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
66 void Server_Cmd_USERINFO(tClient *Client, char *Args);
67 void _SendUserInfo(tClient *Client, int UserID);
68 void Server_Cmd_USERADD(tClient *Client, char *Args);
69 void Server_Cmd_USERFLAGS(tClient *Client, char *Args);
71 int sendf(int Socket, const char *Format, ...);
72 int GetUserAuth(const char *Salt, const char *Username, const uint8_t *Hash);
73 void HexBin(uint8_t *Dest, char *Src, int BufSize);
76 int giServer_Port = 1020;
77 int giServer_NextClientID = 1;
79 struct sClientCommand {
81 void (*Function)(tClient *Client, char *Arguments);
82 } gaServer_Commands[] = {
83 {"USER", Server_Cmd_USER},
84 {"PASS", Server_Cmd_PASS},
85 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
86 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
87 {"ITEM_INFO", Server_Cmd_ITEMINFO},
88 {"DISPENSE", Server_Cmd_DISPENSE},
89 {"GIVE", Server_Cmd_GIVE},
90 {"ADD", Server_Cmd_ADD},
91 {"ENUM_USERS", Server_Cmd_ENUMUSERS},
92 {"USER_INFO", Server_Cmd_USERINFO},
93 {"USER_ADD", Server_Cmd_USERADD},
94 {"USER_FLAGS", Server_Cmd_USERFLAGS}
96 #define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
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;
198 tClient clientInfo = {0};
200 // Initialise Client info
201 clientInfo.Socket = Socket;
202 clientInfo.ID = giServer_NextClientID ++;
203 clientInfo.bIsTrusted = bTrusted;
208 * - The `buf` and `remspace` variables allow a line to span several
209 * calls to recv(), if a line is not completed in one recv() call
210 * it is saved to the beginning of `inbuf` and `buf` is updated to
213 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
216 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
220 while( (eol = strchr(start, '\n')) )
224 Server_ParseClientCommand(&clientInfo, start);
229 // Check if there was an incomplete line
230 if( *start != '\0' ) {
231 int tailBytes = bytes - (start-buf);
232 // Roll back in buffer
233 memcpy(inbuf, start, tailBytes);
234 remspace -= tailBytes;
236 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
238 remspace = INPUT_BUFFER_SIZE - 1;
243 remspace = INPUT_BUFFER_SIZE - 1;
249 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
253 if(giDebugLevel >= 2) {
254 printf("Client %i: Disconnected\n", clientInfo.ID);
259 * \brief Parses a client command and calls the required helper function
260 * \param Client Pointer to client state structure
261 * \param CommandString Command from client (single line of the command)
262 * \return Heap String to return to the client
264 void Server_ParseClientCommand(tClient *Client, char *CommandString)
273 // Split at first space
274 space = strchr(CommandString, ' ');
281 while( *space == ' ' ) space ++;
286 for( i = 0; args[i]; )
288 while( CommandString[i] != ' ' ) {
289 if( CommandString[i] == '"' ) {
290 while( !(CommandString[i] != '\\' CommandString[i+1] == '"' ) )
297 while( CommandString[i] == ' ' ) i ++;
304 for( i = 0; i < NUM_COMMANDS; i++ )
306 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) {
307 gaServer_Commands[i].Function(Client, args);
312 sendf(Client->Socket, "400 Unknown Command\n");
319 * \brief Set client username
321 * Usage: USER <username>
323 void Server_Cmd_USER(tClient *Client, char *Args)
327 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
331 free(Client->Username);
332 Client->Username = strdup(Args);
335 // Create a salt (that changes if the username is changed)
336 // Yes, I know, I'm a little paranoid, but who isn't?
337 Client->Salt[0] = 0x21 + (rand()&0x3F);
338 Client->Salt[1] = 0x21 + (rand()&0x3F);
339 Client->Salt[2] = 0x21 + (rand()&0x3F);
340 Client->Salt[3] = 0x21 + (rand()&0x3F);
341 Client->Salt[4] = 0x21 + (rand()&0x3F);
342 Client->Salt[5] = 0x21 + (rand()&0x3F);
343 Client->Salt[6] = 0x21 + (rand()&0x3F);
344 Client->Salt[7] = 0x21 + (rand()&0x3F);
346 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
347 sendf(Client->Socket, "100 SALT %s\n", Client->Salt);
349 sendf(Client->Socket, "100 User Set\n");
354 * \brief Authenticate as a user
358 void Server_Cmd_PASS(tClient *Client, char *Args)
360 uint8_t clienthash[HASH_LENGTH] = {0};
363 HexBin(clienthash, Args, HASH_LENGTH);
365 // TODO: Decrypt password passed
367 Client->UID = GetUserAuth(Client->Salt, Client->Username, clienthash);
369 if( Client->UID != -1 ) {
370 Client->bIsAuthed = 1;
371 sendf(Client->Socket, "200 Auth OK\n");
377 printf("Client %i: Password hash ", Client->ID);
378 for(i=0;i<HASH_LENGTH;i++)
379 printf("%02x", clienthash[i]&0xFF);
383 sendf(Client->Socket, "401 Auth Failure\n");
387 * \brief Authenticate as a user without a password
389 * Usage: AUTOAUTH <user>
391 void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
393 char *spos = strchr(Args, ' ');
394 if(spos) *spos = '\0'; // Remove characters after the ' '
397 if( !Client->bIsTrusted ) {
399 printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
400 sendf(Client->Socket, "401 Untrusted\n");
405 Client->UID = GetUserID( Args );
406 if( Client->UID < 0 ) {
408 printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
409 sendf(Client->Socket, "401 Auth Failure\n");
414 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
416 sendf(Client->Socket, "200 Auth OK\n");
420 * \brief Enumerate the items that the server knows about
422 void Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
426 sendf(Client->Socket, "201 Items %i\n", giNumItems);
428 for( i = 0; i < giNumItems; i ++ ) {
429 sendf(Client->Socket,
430 "202 Item %s:%i %i %s\n",
431 gaItems[i].Handler->Name, gaItems[i].ID, gaItems[i].Price, gaItems[i].Name
435 sendf(Client->Socket, "200 List end\n");
438 tItem *_GetItemFromString(char *String)
442 char *colon = strchr(String, ':');
454 for( i = 0; i < giNumHandlers; i ++ )
456 if( strcmp(gaHandlers[i]->Name, type) == 0) {
457 handler = gaHandlers[i];
466 for( i = 0; i < giNumItems; i ++ )
468 if( gaItems[i].Handler != handler ) continue;
469 if( gaItems[i].ID != num ) continue;
476 * \brief Fetch information on a specific item
478 void Server_Cmd_ITEMINFO(tClient *Client, char *Args)
480 tItem *item = _GetItemFromString(Args);
483 sendf(Client->Socket, "406 Bad Item ID\n");
487 sendf(Client->Socket,
488 "202 Item %s:%i %i %s\n",
489 item->Handler->Name, item->ID, item->Price, item->Name
493 void Server_Cmd_DISPENSE(tClient *Client, char *Args)
497 if( !Client->bIsAuthed ) {
498 sendf(Client->Socket, "401 Not Authenticated\n");
502 item = _GetItemFromString(Args);
504 sendf(Client->Socket, "406 Bad Item ID\n");
508 switch( ret = DispenseItem( Client->UID, item ) )
510 case 0: sendf(Client->Socket, "200 Dispense OK\n"); return ;
511 case 1: sendf(Client->Socket, "501 Unable to dispense\n"); return ;
512 case 2: sendf(Client->Socket, "402 Poor You\n"); return ;
514 sendf(Client->Socket, "500 Dispense Error\n");
519 void Server_Cmd_GIVE(tClient *Client, char *Args)
521 char *recipient, *ammount, *reason;
524 if( !Client->bIsAuthed ) {
525 sendf(Client->Socket, "401 Not Authenticated\n");
531 ammount = strchr(Args, ' ');
533 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
539 reason = strchr(ammount, ' ');
541 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
548 uid = GetUserID(recipient);
550 sendf(Client->Socket, "404 Invalid target user\n");
555 iAmmount = atoi(ammount);
556 if( iAmmount <= 0 ) {
557 sendf(Client->Socket, "407 Invalid Argument, ammount must be > zero\n");
562 switch( DispenseGive(Client->UID, uid, iAmmount, reason) )
565 sendf(Client->Socket, "200 Give OK\n");
568 sendf(Client->Socket, "402 Poor You\n");
571 sendf(Client->Socket, "500 Unknown error\n");
576 void Server_Cmd_ADD(tClient *Client, char *Args)
578 char *user, *ammount, *reason;
581 if( !Client->bIsAuthed ) {
582 sendf(Client->Socket, "401 Not Authenticated\n");
588 ammount = strchr(Args, ' ');
590 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 1 encountered\n");
596 reason = strchr(ammount, ' ');
598 sendf(Client->Socket, "407 Invalid Argument, expected 3 parameters, 2 encountered\n");
604 // TODO: Check if the current user is in coke/higher
605 if( (GetFlags(Client->UID) & USER_FLAG_TYPEMASK) < USER_TYPE_COKE ) {
606 sendf(Client->Socket, "403 Not in coke\n");
611 uid = GetUserID(user);
613 sendf(Client->Socket, "404 Invalid user\n");
618 iAmmount = atoi(ammount);
619 if( iAmmount == 0 && ammount[0] != '0' ) {
620 sendf(Client->Socket, "407 Invalid Argument\n");
625 switch( DispenseAdd(uid, Client->UID, iAmmount, reason) )
628 sendf(Client->Socket, "200 Add OK\n");
631 sendf(Client->Socket, "402 Poor Guy\n");
634 sendf(Client->Socket, "500 Unknown error\n");
639 void Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
642 int maxBal = INT_MAX, minBal = INT_MIN;
643 int numUsr = GetMaxID();
646 if( Args && strlen(Args) )
648 char *min = Args, *max;
650 max = strchr(Args, ' ');
656 // If <minBal> != "-"
657 if( strcmp(min, "-") != 0 )
659 // If <maxBal> != "-"
660 if( max && strcmp(max, "-") != 0 )
665 for( i = 0; i < numUsr; i ++ )
667 int bal = GetBalance(i);
669 if( bal == INT_MIN ) continue;
671 if( bal < minBal ) continue;
672 if( bal > maxBal ) continue;
678 sendf(Client->Socket, "201 Users %i\n", numRet);
680 for( i = 0; i < numUsr; i ++ )
682 int bal = GetBalance(i);
684 if( bal == INT_MIN ) continue;
686 if( bal < minBal ) continue;
687 if( bal > maxBal ) continue;
689 _SendUserInfo(Client, i);
692 sendf(Client->Socket, "200 List End\n");
695 void Server_Cmd_USERINFO(tClient *Client, char *Args)
701 space = strchr(user, ' ');
702 if(space) *space = '\0';
705 uid = GetUserID(user);
707 sendf(Client->Socket, "404 Invalid user");
711 _SendUserInfo(Client, uid);
714 void _SendUserInfo(tClient *Client, int UserID)
716 char *type, *disabled="";
717 int flags = GetFlags(UserID);
719 switch( flags & USER_FLAG_TYPEMASK )
722 case USER_TYPE_NORMAL: type = "user"; break;
723 case USER_TYPE_COKE: type = "coke"; break;
724 case USER_TYPE_WHEEL: type = "wheel"; break;
725 case USER_TYPE_GOD: type = "meta"; break;
728 if( flags & USER_FLAG_DISABLED )
729 disabled = ",disabled";
731 // TODO: User flags/type
733 Client->Socket, "202 User %s %i %s%s\n",
734 GetUserName(UserID), GetBalance(UserID),
739 void Server_Cmd_USERADD(tClient *Client, char *Args)
741 char *username, *space;
744 if( (GetFlags(Client->UID) & USER_FLAG_TYPEMASK) < USER_TYPE_WHEEL ) {
745 sendf(Client->Socket, "403 Not Wheel\n");
751 while( *username == ' ' ) username ++;
752 space = strchr(username, ' ');
753 if(space) *space = '\0';
755 // Try to create user
756 if( CreateUser(username) == -1 ) {
757 sendf(Client->Socket, "404 User exists\n");
761 sendf(Client->Socket, "200 User Added\n");
764 void Server_Cmd_USERFLAGS(tClient *Client, char *Args)
766 char *username, *flags;
772 if( (GetFlags(Client->UID) & USER_FLAG_TYPEMASK) < USER_TYPE_WHEEL ) {
773 sendf(Client->Socket, "403 Not Wheel\n");
780 while( *username == ' ' ) username ++;
781 space = strchr(username, ' ');
783 sendf(Client->Socket, "407 USER_FLAGS requires 2 arguments, 1 given\n");
789 while( *flags == ' ' ) flags ++;
790 space = strchr(flags, ' ');
791 if(space) *space = '\0';
794 uid = GetUserID(username);
796 sendf(Client->Socket, "404 User '%s' not found\n", username);
809 {"disabled", USER_FLAG_DISABLED, USER_FLAG_DISABLED},
810 {"door", USER_FLAG_DOORGROUP, USER_FLAG_DOORGROUP},
811 {"user", USER_FLAG_TYPEMASK, USER_TYPE_NORMAL},
812 {"coke", USER_FLAG_TYPEMASK, USER_TYPE_COKE},
813 {"wheel", USER_FLAG_TYPEMASK, USER_TYPE_WHEEL},
814 {"meta", USER_FLAG_TYPEMASK, USER_TYPE_GOD}
816 const int ciNumFlags = sizeof(cFLAGS)/sizeof(cFLAGS[0]);
818 while( *flags == ' ' ) flags ++; // Eat whitespace
819 space = strchr(flags, ','); // Find the end of the flag
820 if(space) *space = '\0';
822 // Check for inversion/removal
823 if( *flags == '!' || *flags == '-' ) {
827 else if( *flags == '+' ) {
832 for( i = 0; i < ciNumFlags; i ++ )
834 if( strcmp(flags, cFLAGS[i].Name) == 0 ) {
835 mask |= cFLAGS[i].Mask;
836 value &= ~cFLAGS[i].Mask;
838 value |= cFLAGS[i].Value;
844 if( i == ciNumFlags ) {
845 sendf(Client->Socket, "407 Unknown flag value '%s'\n", flags);
853 SetFlags(uid, mask, value);
856 sendf(Client->Socket, "200 User Updated\n");
860 * \brief Authenticate a user
861 * \return User ID, or -1 if authentication failed
863 int GetUserAuth(const char *Salt, const char *Username, const uint8_t *ProvidedHash)
867 int ofs = strlen(Username) + strlen(Salt);
868 char input[ ofs + 40 + 1];
869 char tmp[4 + strlen(Username) + 1]; // uid=%s
873 if( strcmp(Username, "tpg") == 0 )
874 return GetUserID("tpg");
877 if( strcmp(Username, "root") == 0 ) {
878 int ret = GetUserID("root");
880 return CreateUser("root");
887 strcpy(input, Username);
889 // TODO: Get user's SHA-1 hash
890 sprintf(tmp, "uid=%s", Username);
891 ldap_search_s(ld, "", LDAP_SCOPE_BASE, tmp, "userPassword", 0, res);
893 sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
894 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
895 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
897 // Then create the hash from the provided salt
898 // Compare that with the provided hash
904 // --- INTERNAL HELPERS ---
905 int sendf(int Socket, const char *Format, ...)
910 va_start(args, Format);
911 len = vsnprintf(NULL, 0, Format, args);
916 va_start(args, Format);
917 vsnprintf(buf, len+1, Format, args);
920 #if DEBUG_TRACE_CLIENT
921 printf("sendf: %s", buf);
924 return send(Socket, buf, len, 0);
928 // TODO: Move to another file
929 void HexBin(uint8_t *Dest, char *Src, int BufSize)
932 for( i = 0; i < BufSize; i ++ )
936 if('0' <= *Src && *Src <= '9')
937 val |= (*Src-'0') << 4;
938 else if('A' <= *Src && *Src <= 'F')
939 val |= (*Src-'A'+10) << 4;
940 else if('a' <= *Src && *Src <= 'f')
941 val |= (*Src-'a'+10) << 4;
946 if('0' <= *Src && *Src <= '9')
948 else if('A' <= *Src && *Src <= 'F')
949 val |= (*Src-'A'+10);
950 else if('a' <= *Src && *Src <= 'f')
951 val |= (*Src-'a'+10);
958 for( ; i < BufSize; i++ )
963 * \brief Decode a Base64 value
965 int UnBase64(uint8_t *Dest, char *Src, int BufSize)
969 char *start_src = Src;
971 for( i = 0; i+2 < BufSize; i += 3 )
974 for( j = 0; j < 4; j++, Src ++ ) {
975 if('A' <= *Src && *Src <= 'Z')
976 val |= (*Src - 'A') << ((3-j)*6);
977 else if('a' <= *Src && *Src <= 'z')
978 val |= (*Src - 'a' + 26) << ((3-j)*6);
979 else if('0' <= *Src && *Src <= '9')
980 val |= (*Src - '0' + 52) << ((3-j)*6);
982 val |= 62 << ((3-j)*6);
984 val |= 63 << ((3-j)*6);
988 j --; // Ignore invalid characters
990 Dest[i ] = (val >> 16) & 0xFF;
991 Dest[i+1] = (val >> 8) & 0xFF;
992 Dest[i+2] = val & 0xFF;
998 Dest[i] = (val >> 16) & 0xFF;
1000 Dest[i+1] = (val >> 8) & 0xFF;
1002 return Src - start_src;