Server now listens and accepts connections (and returns a value to USER)
[tpg/opendispense2.git] / server / src / server.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * server.c - Client Server Code
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file
8  * COPYING for full details.
9  */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include "common.h"
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16 #include <unistd.h>
17 #include <string.h>
18
19 #define MAX_CONNECTION_QUEUE    5
20 #define INPUT_BUFFER_SIZE       128
21
22 #define MSG_STR_TOO_LONG        "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
23
24 // === TYPES ===
25 typedef struct sClient
26 {
27          int    ID;     // Client ID
28         
29         char    *Username;
30         char    Salt[9];
31         
32          int    UID;
33          int    bIsAuthed;
34 }       tClient;
35
36 // === PROTOTYPES ===
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);
41
42 // === GLOBALS ===
43  int    giServer_Port = 1020;
44  int    giServer_NextClientID = 1;
45 // - Commands
46 struct sClientCommand {
47         char    *Name;
48         char    *(*Function)(tClient *Client, char *Arguments);
49 }       gaServer_Commands[] = {
50         {"USER", Server_Cmd_USER}
51 };
52 #define NUM_COMMANDS    (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
53
54 // === CODE ===
55 /**
56  * \brief Open listenting socket and serve connections
57  */
58 void Server_Start(void)
59 {
60          int    server_socket, client_socket;
61         struct sockaddr_in      server_addr, client_addr;
62
63         // Create Server
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");
67                 return ;
68         }
69         
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
75
76         // Bind
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);
79                 return ;
80         }
81         
82         // Listen
83         if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
84                 fprintf(stderr, "ERROR: Unable to listen to socket\n");
85                 return ;
86         }
87         
88         printf("Listening on 0.0.0.0:%i\n", giServer_Port);
89         
90         for(;;)
91         {
92                 uint    len = sizeof(client_addr);
93                 
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");
97                         return ;
98                 }
99                 
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);
104                 }
105                 
106                 // TODO: Multithread this?
107                 Server_HandleClient(client_socket);
108                 
109                 close(client_socket);
110         }
111 }
112
113 /**
114  * \brief Reads from a client socket and parses the command strings
115  * \param Socket        Client socket number/handle
116  */
117 void Server_HandleClient(int Socket)
118 {
119         char    inbuf[INPUT_BUFFER_SIZE];
120         char    *buf = inbuf;
121          int    remspace = INPUT_BUFFER_SIZE-1;
122          int    bytes = -1;
123         tClient clientInfo = {0};
124         
125         // Initialise Client info
126         clientInfo.ID = giServer_NextClientID ++;
127                 
128         // Read from client
129         /*
130          * Notes:
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
134          *   the end of it.
135          */
136         while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
137         {
138                 char    *eol, *start;
139                 buf[bytes] = '\0';      // Allow us to use stdlib string functions on it
140                 
141                 // Split by lines
142                 start = inbuf;
143                 while( (eol = strchr(start, '\n')) )
144                 {
145                         char    *ret;
146                         *eol = '\0';
147                         ret = Server_ParseClientCommand(&clientInfo, start);
148                         // `ret` is a string on the heap
149                         send(Socket, ret, strlen(ret), 0);
150                         free(ret);
151                         start = eol + 1;
152                 }
153                 
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;
160                         if(remspace == 0) {
161                                 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
162                                 buf = inbuf;
163                                 remspace = INPUT_BUFFER_SIZE - 1;
164                         }
165                 }
166                 else {
167                         buf = inbuf;
168                         remspace = INPUT_BUFFER_SIZE - 1;
169                 }
170         }
171         
172         // Check for errors
173         if( bytes < 0 ) {
174                 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
175                 return ;
176         }
177 }
178
179 /**
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
184  */
185 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
186 {
187         char    *space, *args;
188          int    i;
189         
190         // Split at first space
191         space = strchr(CommandString, ' ');
192         if(space == NULL) {
193                 args = NULL;
194         }
195         else {
196                 *space = '\0';
197                 args = space + 1;
198         }
199         
200         // Find command
201         for( i = 0; i < NUM_COMMANDS; i++ )
202         {
203                 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
204                         return gaServer_Commands[i].Function(Client, args);
205         }
206         
207         return strdup("400      Unknown Command\n");
208 }
209
210 // ---
211 // Commands
212 // ---
213 /**
214  * \brief Set client username
215  * 
216  * Usage: USER <username>
217  */
218 char *Server_Cmd_USER(tClient *Client, char *Args)
219 {
220         char    *ret;
221         
222         // Debug!
223         if( giDebugLevel )
224                 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
225         
226         // Save username
227         if(Client->Username)
228                 free(Client->Username);
229         Client->Username = strdup(Args);
230         
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);
240         
241         // "100 Salt xxxxXXXX\n"
242         ret = strdup("100 SALT xxxxXXXX\n");
243         sprintf(ret, "100 SALT %s\n", Client->Salt);
244         
245         return ret;
246 }

UCC git Repository :: git.ucc.asn.au