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 128
22 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
25 typedef struct sClient
37 void Server_Start(void);
38 void Server_HandleClient(int Socket);
39 char *Server_ParseClientCommand(tClient *Client, char *CommandString);
40 char *Server_Cmd_USER(tClient *Client, char *Args);
43 int giServer_Port = 1020;
44 int giServer_NextClientID = 1;
46 struct sClientCommand {
48 char *(*Function)(tClient *Client, char *Arguments);
49 } gaServer_Commands[] = {
50 {"USER", Server_Cmd_USER}
52 #define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
56 * \brief Open listenting socket and serve connections
58 void Server_Start(void)
60 int server_socket, client_socket;
61 struct sockaddr_in server_addr, client_addr;
64 server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
65 if( server_socket < 0 ) {
66 fprintf(stderr, "ERROR: Unable to create server socket\n");
70 // Make listen address
71 memset(&server_addr, 0, sizeof(server_addr));
72 server_addr.sin_family = AF_INET; // Internet Socket
73 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
74 server_addr.sin_port = htons(giServer_Port); // Port
77 if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
78 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
83 if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
84 fprintf(stderr, "ERROR: Unable to listen to socket\n");
88 printf("Listening on 0.0.0.0:%i\n", giServer_Port);
92 uint len = sizeof(client_addr);
94 client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len);
95 if(client_socket < 0) {
96 fprintf(stderr, "ERROR: Unable to accept client connection\n");
100 if(giDebugLevel >= 2) {
101 char ipstr[INET_ADDRSTRLEN];
102 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
103 printf("Client connection from %s\n", ipstr);
106 // TODO: Multithread this?
107 Server_HandleClient(client_socket);
109 close(client_socket);
114 * \brief Reads from a client socket and parses the command strings
115 * \param Socket Client socket number/handle
117 void Server_HandleClient(int Socket)
119 char inbuf[INPUT_BUFFER_SIZE];
121 int remspace = INPUT_BUFFER_SIZE-1;
123 tClient clientInfo = {0};
125 // Initialise Client info
126 clientInfo.ID = giServer_NextClientID ++;
131 * - The `buf` and `remspace` variables allow a line to span several
132 * calls to recv(), if a line is not completed in one recv() call
133 * it is saved to the beginning of `inbuf` and `buf` is updated to
136 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
139 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
143 while( (eol = strchr(start, '\n')) )
147 ret = Server_ParseClientCommand(&clientInfo, start);
148 // `ret` is a string on the heap
149 send(Socket, ret, strlen(ret), 0);
154 // Check if there was an incomplete line
155 if( *start != '\0' ) {
156 int tailBytes = bytes - (start-buf);
157 // Roll back in buffer
158 memcpy(inbuf, start, tailBytes);
159 remspace -= tailBytes;
161 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
163 remspace = INPUT_BUFFER_SIZE - 1;
168 remspace = INPUT_BUFFER_SIZE - 1;
174 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
180 * \brief Parses a client command and calls the required helper function
181 * \param Client Pointer to client state structure
182 * \param CommandString Command from client (single line of the command)
183 * \return Heap String to return to the client
185 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
190 // Split at first space
191 space = strchr(CommandString, ' ');
201 for( i = 0; i < NUM_COMMANDS; i++ )
203 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
204 return gaServer_Commands[i].Function(Client, args);
207 return strdup("400 Unknown Command\n");
214 * \brief Set client username
216 * Usage: USER <username>
218 char *Server_Cmd_USER(tClient *Client, char *Args)
224 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
228 free(Client->Username);
229 Client->Username = strdup(Args);
231 // Create a salt (that changes if the username is changed)
232 Client->Salt[0] = 0x21 + (rand()&0x3F);
233 Client->Salt[1] = 0x21 + (rand()&0x3F);
234 Client->Salt[2] = 0x21 + (rand()&0x3F);
235 Client->Salt[3] = 0x21 + (rand()&0x3F);
236 Client->Salt[4] = 0x21 + (rand()&0x3F);
237 Client->Salt[5] = 0x21 + (rand()&0x3F);
238 Client->Salt[6] = 0x21 + (rand()&0x3F);
239 Client->Salt[7] = 0x21 + (rand()&0x3F);
241 // "100 Salt xxxxXXXX\n"
242 ret = strdup("100 SALT xxxxXXXX\n");
243 sprintf(ret, "100 SALT %s\n", Client->Salt);