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>
19 #define MAX_CONNECTION_QUEUE 5
20 #define INPUT_BUFFER_SIZE 256
22 #define HASH_TYPE SHA512
23 #define HASH_LENGTH 64
25 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
28 typedef struct sClient
32 int bIsTrusted; // Is the connection from a trusted host/port
42 void Server_Start(void);
43 void Server_HandleClient(int Socket, int bTrusted);
44 char *Server_ParseClientCommand(tClient *Client, char *CommandString);
46 char *Server_Cmd_USER(tClient *Client, char *Args);
47 char *Server_Cmd_PASS(tClient *Client, char *Args);
48 char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
49 char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
50 char *Server_Cmd_ITEMINFO(tClient *Client, char *Args);
52 void HexBin(uint8_t *Dest, char *Src, int BufSize);
55 int giServer_Port = 1020;
56 int giServer_NextClientID = 1;
58 struct sClientCommand {
60 char *(*Function)(tClient *Client, char *Arguments);
61 } gaServer_Commands[] = {
62 {"USER", Server_Cmd_USER},
63 {"PASS", Server_Cmd_PASS},
64 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
65 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
66 {"ITEM_INFO", Server_Cmd_ITEMINFO}
68 #define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
72 * \brief Open listenting socket and serve connections
74 void Server_Start(void)
76 int server_socket, client_socket;
77 struct sockaddr_in server_addr, client_addr;
80 server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
81 if( server_socket < 0 ) {
82 fprintf(stderr, "ERROR: Unable to create server socket\n");
86 // Make listen address
87 memset(&server_addr, 0, sizeof(server_addr));
88 server_addr.sin_family = AF_INET; // Internet Socket
89 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
90 server_addr.sin_port = htons(giServer_Port); // Port
93 if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
94 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
99 if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
100 fprintf(stderr, "ERROR: Unable to listen to socket\n");
104 printf("Listening on 0.0.0.0:%i\n", giServer_Port);
108 uint len = sizeof(client_addr);
111 client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len);
112 if(client_socket < 0) {
113 fprintf(stderr, "ERROR: Unable to accept client connection\n");
117 if(giDebugLevel >= 2) {
118 char ipstr[INET_ADDRSTRLEN];
119 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
120 printf("Client connection from %s:%i\n",
121 ipstr, ntohs(client_addr.sin_port));
124 // Trusted Connections
125 if( ntohs(client_addr.sin_port) < 1024 )
127 // TODO: Make this runtime configurable
128 switch( ntohl( client_addr.sin_addr.s_addr ) )
130 case 0x7F000001: // 127.0.0.1 localhost
131 //case 0x825E0D00: // 130.95.13.0
132 case 0x825E0D12: // 130.95.13.18 mussel
133 case 0x825E0D17: // 130.95.13.23 martello
141 // TODO: Multithread this?
142 Server_HandleClient(client_socket, bTrusted);
144 close(client_socket);
149 * \brief Reads from a client socket and parses the command strings
150 * \param Socket Client socket number/handle
151 * \param bTrusted Is the client trusted?
153 void Server_HandleClient(int Socket, int bTrusted)
155 char inbuf[INPUT_BUFFER_SIZE];
157 int remspace = INPUT_BUFFER_SIZE-1;
159 tClient clientInfo = {0};
161 // Initialise Client info
162 clientInfo.ID = giServer_NextClientID ++;
163 clientInfo.bIsTrusted = bTrusted;
168 * - The `buf` and `remspace` variables allow a line to span several
169 * calls to recv(), if a line is not completed in one recv() call
170 * it is saved to the beginning of `inbuf` and `buf` is updated to
173 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
176 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
180 while( (eol = strchr(start, '\n')) )
184 ret = Server_ParseClientCommand(&clientInfo, start);
185 // `ret` is a string on the heap
186 send(Socket, ret, strlen(ret), 0);
191 // Check if there was an incomplete line
192 if( *start != '\0' ) {
193 int tailBytes = bytes - (start-buf);
194 // Roll back in buffer
195 memcpy(inbuf, start, tailBytes);
196 remspace -= tailBytes;
198 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
200 remspace = INPUT_BUFFER_SIZE - 1;
205 remspace = INPUT_BUFFER_SIZE - 1;
211 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
215 if(giDebugLevel >= 2) {
216 printf("Client %i: Disconnected\n", clientInfo.ID);
221 * \brief Parses a client command and calls the required helper function
222 * \param Client Pointer to client state structure
223 * \param CommandString Command from client (single line of the command)
224 * \return Heap String to return to the client
226 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
231 // Split at first space
232 space = strchr(CommandString, ' ');
242 for( i = 0; i < NUM_COMMANDS; i++ )
244 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
245 return gaServer_Commands[i].Function(Client, args);
248 return strdup("400 Unknown Command\n");
255 * \brief Set client username
257 * Usage: USER <username>
259 char *Server_Cmd_USER(tClient *Client, char *Args)
265 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
269 free(Client->Username);
270 Client->Username = strdup(Args);
273 // Create a salt (that changes if the username is changed)
274 // Yes, I know, I'm a little paranoid, but who isn't?
275 Client->Salt[0] = 0x21 + (rand()&0x3F);
276 Client->Salt[1] = 0x21 + (rand()&0x3F);
277 Client->Salt[2] = 0x21 + (rand()&0x3F);
278 Client->Salt[3] = 0x21 + (rand()&0x3F);
279 Client->Salt[4] = 0x21 + (rand()&0x3F);
280 Client->Salt[5] = 0x21 + (rand()&0x3F);
281 Client->Salt[6] = 0x21 + (rand()&0x3F);
282 Client->Salt[7] = 0x21 + (rand()&0x3F);
284 // "100 Salt xxxxXXXX\n"
285 ret = strdup("100 SALT xxxxXXXX\n");
286 sprintf(ret, "100 SALT %s\n", Client->Salt);
288 ret = strdup("100 User Set\n");
294 * \brief Authenticate as a user
298 char *Server_Cmd_PASS(tClient *Client, char *Args)
300 uint8_t clienthash[HASH_LENGTH] = {0};
303 HexBin(clienthash, Args, HASH_LENGTH);
307 printf("Client %i: Password hash ", Client->ID);
308 for(i=0;i<HASH_LENGTH;i++)
309 printf("%02x", clienthash[i]&0xFF);
313 return strdup("401 Auth Failure\n");
317 * \brief Authenticate as a user without a password
319 * Usage: AUTOAUTH <user>
321 char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
323 char *spos = strchr(Args, ' ');
324 if(spos) *spos = '\0'; // Remove characters after the ' '
327 if( !Client->bIsTrusted ) {
329 printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
330 return strdup("401 Untrusted\n");
334 Client->UID = GetUserID( Args );
335 if( Client->UID < 0 ) {
337 printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
338 return strdup("401 Auth Failure\n");
342 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
344 return strdup("200 Auth OK\n");
348 * \brief Enumerate the items that the server knows about
350 char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
352 // int nItems = giNumItems;
357 retLen = snprintf(NULL, 0, "201 Items %i", giNumItems);
359 for( i = 0; i < giNumItems; i ++ )
361 retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
364 ret = malloc(retLen+1);
366 retLen += sprintf(ret+retLen, "201 Items %i", giNumItems);
368 for( i = 0; i < giNumItems; i ++ ) {
369 retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
378 * \brief Fetch information on a specific item
380 char *Server_Cmd_ITEMINFO(tClient *Client, char *Args)
387 char *colon = strchr(Args, ':');
391 return strdup("406 Bad Item ID\n");
399 for( i = 0; i < giNumHandlers; i ++ )
401 if( strcmp(gaHandlers[i]->Name, type) == 0) {
402 handler = gaHandlers[i];
407 return strdup("406 Bad Item ID\n");
411 for( i = 0; i < giNumItems; i ++ )
413 if( gaItems[i].Handler != handler ) continue;
414 if( gaItems[i].ID != num ) continue;
419 return strdup("406 Bad Item ID\n");
423 retLen = snprintf(NULL, 0, "202 Item %s:%i %i %s\n",
424 handler->Name, item->ID, item->Price, item->Name);
425 ret = malloc(retLen+1);
426 sprintf(ret, "202 Item %s:%i %i %s\n",
427 handler->Name, item->ID, item->Price, item->Name);
432 // --- INTERNAL HELPERS ---
433 // TODO: Move to another file
434 void HexBin(uint8_t *Dest, char *Src, int BufSize)
437 for( i = 0; i < BufSize; i ++ )
441 if('0' <= *Src && *Src <= '9')
442 val |= (*Src-'0') << 4;
443 else if('A' <= *Src && *Src <= 'F')
444 val |= (*Src-'A'+10) << 4;
445 else if('a' <= *Src && *Src <= 'f')
446 val |= (*Src-'a'+10) << 4;
451 if('0' <= *Src && *Src <= '9')
453 else if('A' <= *Src && *Src <= 'F')
454 val |= (*Src-'A'+10);
455 else if('a' <= *Src && *Src <= 'f')
456 val |= (*Src-'a'+10);
463 for( ; i < BufSize; i++ )
468 * \brief Decode a Base64 value
470 int UnBase64(uint8_t *Dest, char *Src, int BufSize)
474 char *start_src = Src;
476 for( i = 0; i+2 < BufSize; i += 3 )
479 for( j = 0; j < 4; j++, Src ++ ) {
480 if('A' <= *Src && *Src <= 'Z')
481 val |= (*Src - 'A') << ((3-j)*6);
482 else if('a' <= *Src && *Src <= 'z')
483 val |= (*Src - 'a' + 26) << ((3-j)*6);
484 else if('0' <= *Src && *Src <= '9')
485 val |= (*Src - '0' + 52) << ((3-j)*6);
487 val |= 62 << ((3-j)*6);
489 val |= 63 << ((3-j)*6);
493 j --; // Ignore invalid characters
495 Dest[i ] = (val >> 16) & 0xFF;
496 Dest[i+1] = (val >> 8) & 0xFF;
497 Dest[i+2] = val & 0xFF;
503 Dest[i] = (val >> 16) & 0xFF;
505 Dest[i+1] = (val >> 8) & 0xFF;
507 return Src - start_src;