Restructured code into src/<module>
[tpg/opendispense2.git] / src / server / 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       256
21
22 #define HASH_TYPE       SHA512
23 #define HASH_LENGTH     64
24
25 #define MSG_STR_TOO_LONG        "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
26
27 // === TYPES ===
28 typedef struct sClient
29 {
30          int    ID;     // Client ID
31          
32          int    bIsTrusted;     // Is the connection from a trusted host/port
33         
34         char    *Username;
35         char    Salt[9];
36         
37          int    UID;
38          int    bIsAuthed;
39 }       tClient;
40
41 // === PROTOTYPES ===
42 void    Server_Start(void);
43 void    Server_HandleClient(int Socket, int bTrusted);
44 char    *Server_ParseClientCommand(tClient *Client, char *CommandString);
45 // --- Commands ---
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);
51 // --- Helpers ---
52 void    HexBin(uint8_t *Dest, char *Src, int BufSize);
53
54 // === GLOBALS ===
55  int    giServer_Port = 1020;
56  int    giServer_NextClientID = 1;
57 // - Commands
58 struct sClientCommand {
59         char    *Name;
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}
67 };
68 #define NUM_COMMANDS    (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
69
70 // === CODE ===
71 /**
72  * \brief Open listenting socket and serve connections
73  */
74 void Server_Start(void)
75 {
76          int    server_socket, client_socket;
77         struct sockaddr_in      server_addr, client_addr;
78
79         // Create Server
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");
83                 return ;
84         }
85         
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
91
92         // Bind
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);
95                 return ;
96         }
97         
98         // Listen
99         if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
100                 fprintf(stderr, "ERROR: Unable to listen to socket\n");
101                 return ;
102         }
103         
104         printf("Listening on 0.0.0.0:%i\n", giServer_Port);
105         
106         for(;;)
107         {
108                 uint    len = sizeof(client_addr);
109                  int    bTrusted = 0;
110                 
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");
114                         return ;
115                 }
116                 
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));
122                 }
123                 
124                 // Trusted Connections
125                 if( ntohs(client_addr.sin_port) < 1024 )
126                 {
127                         // TODO: Make this runtime configurable
128                         switch( ntohl( client_addr.sin_addr.s_addr ) )
129                         {
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
134                                 bTrusted = 1;
135                                 break;
136                         default:
137                                 break;
138                         }
139                 }
140                 
141                 // TODO: Multithread this?
142                 Server_HandleClient(client_socket, bTrusted);
143                 
144                 close(client_socket);
145         }
146 }
147
148 /**
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?
152  */
153 void Server_HandleClient(int Socket, int bTrusted)
154 {
155         char    inbuf[INPUT_BUFFER_SIZE];
156         char    *buf = inbuf;
157          int    remspace = INPUT_BUFFER_SIZE-1;
158          int    bytes = -1;
159         tClient clientInfo = {0};
160         
161         // Initialise Client info
162         clientInfo.ID = giServer_NextClientID ++;
163         clientInfo.bIsTrusted = bTrusted;
164         
165         // Read from client
166         /*
167          * Notes:
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
171          *   the end of it.
172          */
173         while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
174         {
175                 char    *eol, *start;
176                 buf[bytes] = '\0';      // Allow us to use stdlib string functions on it
177                 
178                 // Split by lines
179                 start = inbuf;
180                 while( (eol = strchr(start, '\n')) )
181                 {
182                         char    *ret;
183                         *eol = '\0';
184                         ret = Server_ParseClientCommand(&clientInfo, start);
185                         // `ret` is a string on the heap
186                         send(Socket, ret, strlen(ret), 0);
187                         free(ret);
188                         start = eol + 1;
189                 }
190                 
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;
197                         if(remspace == 0) {
198                                 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
199                                 buf = inbuf;
200                                 remspace = INPUT_BUFFER_SIZE - 1;
201                         }
202                 }
203                 else {
204                         buf = inbuf;
205                         remspace = INPUT_BUFFER_SIZE - 1;
206                 }
207         }
208         
209         // Check for errors
210         if( bytes < 0 ) {
211                 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
212                 return ;
213         }
214         
215         if(giDebugLevel >= 2) {
216                 printf("Client %i: Disconnected\n", clientInfo.ID);
217         }
218 }
219
220 /**
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
225  */
226 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
227 {
228         char    *space, *args;
229          int    i;
230         
231         // Split at first space
232         space = strchr(CommandString, ' ');
233         if(space == NULL) {
234                 args = NULL;
235         }
236         else {
237                 *space = '\0';
238                 args = space + 1;
239         }
240         
241         // Find command
242         for( i = 0; i < NUM_COMMANDS; i++ )
243         {
244                 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
245                         return gaServer_Commands[i].Function(Client, args);
246         }
247         
248         return strdup("400 Unknown Command\n");
249 }
250
251 // ---
252 // Commands
253 // ---
254 /**
255  * \brief Set client username
256  * 
257  * Usage: USER <username>
258  */
259 char *Server_Cmd_USER(tClient *Client, char *Args)
260 {
261         char    *ret;
262         
263         // Debug!
264         if( giDebugLevel )
265                 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
266         
267         // Save username
268         if(Client->Username)
269                 free(Client->Username);
270         Client->Username = strdup(Args);
271         
272         #if USE_SALT
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);
283         
284         // "100 Salt xxxxXXXX\n"
285         ret = strdup("100 SALT xxxxXXXX\n");
286         sprintf(ret, "100 SALT %s\n", Client->Salt);
287         #else
288         ret = strdup("100 User Set\n");
289         #endif
290         return ret;
291 }
292
293 /**
294  * \brief Authenticate as a user
295  * 
296  * Usage: PASS <hash>
297  */
298 char *Server_Cmd_PASS(tClient *Client, char *Args)
299 {
300         uint8_t clienthash[HASH_LENGTH] = {0};
301         
302         // Read user's hash
303         HexBin(clienthash, Args, HASH_LENGTH);
304         
305         if( giDebugLevel ) {
306                  int    i;
307                 printf("Client %i: Password hash ", Client->ID);
308                 for(i=0;i<HASH_LENGTH;i++)
309                         printf("%02x", clienthash[i]&0xFF);
310                 printf("\n");
311         }
312         
313         return strdup("401 Auth Failure\n");
314 }
315
316 /**
317  * \brief Authenticate as a user without a password
318  * 
319  * Usage: AUTOAUTH <user>
320  */
321 char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
322 {
323         char    *spos = strchr(Args, ' ');
324         if(spos)        *spos = '\0';   // Remove characters after the ' '
325         
326         // Check if trusted
327         if( !Client->bIsTrusted ) {
328                 if(giDebugLevel)
329                         printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
330                 return strdup("401 Untrusted\n");
331         }
332         
333         // Get UID
334         Client->UID = GetUserID( Args );
335         if( Client->UID < 0 ) {
336                 if(giDebugLevel)
337                         printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
338                 return strdup("401 Auth Failure\n");
339         }
340         
341         if(giDebugLevel)
342                 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
343         
344         return strdup("200 Auth OK\n");
345 }
346
347 /**
348  * \brief Enumerate the items that the server knows about
349  */
350 char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
351 {
352 //       int    nItems = giNumItems;
353          int    retLen;
354          int    i;
355         char    *ret;
356
357         retLen = snprintf(NULL, 0, "201 Items %i", giNumItems);
358
359         for( i = 0; i < giNumItems; i ++ )
360         {
361                 retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
362         }
363
364         ret = malloc(retLen+1);
365         retLen = 0;
366         retLen += sprintf(ret+retLen, "201 Items %i", giNumItems);
367
368         for( i = 0; i < giNumItems; i ++ ) {
369                 retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
370         }
371
372         strcat(ret, "\n");
373
374         return ret;
375 }
376
377 /**
378  * \brief Fetch information on a specific item
379  */
380 char *Server_Cmd_ITEMINFO(tClient *Client, char *Args)
381 {
382          int    retLen = 0;
383         char    *ret;
384         tItem   *item;
385         tHandler        *handler;
386         char    *type = Args;
387         char    *colon = strchr(Args, ':');
388          int    num, i;
389         
390         if( !colon ) {
391                 return strdup("406 Bad Item ID\n");
392         }
393
394         num = atoi(colon+1);
395         *colon = '\0';
396
397         // Find handler
398         handler = NULL;
399         for( i = 0; i < giNumHandlers; i ++ )
400         {
401                 if( strcmp(gaHandlers[i]->Name, type) == 0) {
402                         handler = gaHandlers[i];
403                         break;
404                 }
405         }
406         if( !handler ) {
407                 return strdup("406 Bad Item ID\n");
408         }
409
410         // Find item
411         for( i = 0; i < giNumItems; i ++ )
412         {
413                 if( gaItems[i].Handler != handler )     continue;
414                 if( gaItems[i].ID != num )      continue;
415                 item = &gaItems[i];
416                 break;
417         }
418         if( !item ) {
419                 return strdup("406 Bad Item ID\n");
420         }
421
422         // Create return
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);
428
429         return ret;
430 }
431
432 // --- INTERNAL HELPERS ---
433 // TODO: Move to another file
434 void HexBin(uint8_t *Dest, char *Src, int BufSize)
435 {
436          int    i;
437         for( i = 0; i < BufSize; i ++ )
438         {
439                 uint8_t val = 0;
440                 
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;
447                 else
448                         break;
449                 Src ++;
450                 
451                 if('0' <= *Src && *Src <= '9')
452                         val |= (*Src-'0');
453                 else if('A' <= *Src && *Src <= 'F')
454                         val |= (*Src-'A'+10);
455                 else if('a' <= *Src && *Src <= 'f')
456                         val |= (*Src-'a'+10);
457                 else
458                         break;
459                 Src ++;
460                 
461                 Dest[i] = val;
462         }
463         for( ; i < BufSize; i++ )
464                 Dest[i] = 0;
465 }
466
467 /**
468  * \brief Decode a Base64 value
469  */
470 int UnBase64(uint8_t *Dest, char *Src, int BufSize)
471 {
472         uint32_t        val;
473          int    i, j;
474         char    *start_src = Src;
475         
476         for( i = 0; i+2 < BufSize; i += 3 )
477         {
478                 val = 0;
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);
486                         else if(*Src == '+')
487                                 val |= 62 << ((3-j)*6);
488                         else if(*Src == '/')
489                                 val |= 63 << ((3-j)*6);
490                         else if(!*Src)
491                                 break;
492                         else if(*Src != '=')
493                                 j --;   // Ignore invalid characters
494                 }
495                 Dest[i  ] = (val >> 16) & 0xFF;
496                 Dest[i+1] = (val >> 8) & 0xFF;
497                 Dest[i+2] = val & 0xFF;
498                 if(j != 4)      break;
499         }
500         
501         // Finish things off
502         if(i   < BufSize)
503                 Dest[i] = (val >> 16) & 0xFF;
504         if(i+1 < BufSize)
505                 Dest[i+1] = (val >> 8) & 0xFF;
506         
507         return Src - start_src;
508 }

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