Client work
[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 char    *Server_Cmd_DISPENSE(tClient *Client, char *Args);
52 // --- Helpers ---
53 void    HexBin(uint8_t *Dest, char *Src, int BufSize);
54
55 // === GLOBALS ===
56  int    giServer_Port = 1020;
57  int    giServer_NextClientID = 1;
58 // - Commands
59 struct sClientCommand {
60         char    *Name;
61         char    *(*Function)(tClient *Client, char *Arguments);
62 }       gaServer_Commands[] = {
63         {"USER", Server_Cmd_USER},
64         {"PASS", Server_Cmd_PASS},
65         {"AUTOAUTH", Server_Cmd_AUTOAUTH},
66         {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
67         {"ITEM_INFO", Server_Cmd_ITEMINFO},
68         {"DISPENSE", Server_Cmd_DISPENSE}
69 };
70 #define NUM_COMMANDS    (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
71
72 // === CODE ===
73 /**
74  * \brief Open listenting socket and serve connections
75  */
76 void Server_Start(void)
77 {
78          int    server_socket, client_socket;
79         struct sockaddr_in      server_addr, client_addr;
80
81         // Create Server
82         server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
83         if( server_socket < 0 ) {
84                 fprintf(stderr, "ERROR: Unable to create server socket\n");
85                 return ;
86         }
87         
88         // Make listen address
89         memset(&server_addr, 0, sizeof(server_addr));
90         server_addr.sin_family = AF_INET;       // Internet Socket
91         server_addr.sin_addr.s_addr = htonl(INADDR_ANY);        // Listen on all interfaces
92         server_addr.sin_port = htons(giServer_Port);    // Port
93
94         // Bind
95         if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
96                 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
97                 return ;
98         }
99         
100         // Listen
101         if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
102                 fprintf(stderr, "ERROR: Unable to listen to socket\n");
103                 return ;
104         }
105         
106         printf("Listening on 0.0.0.0:%i\n", giServer_Port);
107         
108         for(;;)
109         {
110                 uint    len = sizeof(client_addr);
111                  int    bTrusted = 0;
112                 
113                 client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len);
114                 if(client_socket < 0) {
115                         fprintf(stderr, "ERROR: Unable to accept client connection\n");
116                         return ;
117                 }
118                 
119                 if(giDebugLevel >= 2) {
120                         char    ipstr[INET_ADDRSTRLEN];
121                         inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
122                         printf("Client connection from %s:%i\n",
123                                 ipstr, ntohs(client_addr.sin_port));
124                 }
125                 
126                 // Trusted Connections
127                 if( ntohs(client_addr.sin_port) < 1024 )
128                 {
129                         // TODO: Make this runtime configurable
130                         switch( ntohl( client_addr.sin_addr.s_addr ) )
131                         {
132                         case 0x7F000001:        // 127.0.0.1    localhost
133                         //case 0x825E0D00:      // 130.95.13.0
134                         case 0x825E0D12:        // 130.95.13.18 mussel
135                         case 0x825E0D17:        // 130.95.13.23 martello
136                                 bTrusted = 1;
137                                 break;
138                         default:
139                                 break;
140                         }
141                 }
142                 
143                 // TODO: Multithread this?
144                 Server_HandleClient(client_socket, bTrusted);
145                 
146                 close(client_socket);
147         }
148 }
149
150 /**
151  * \brief Reads from a client socket and parses the command strings
152  * \param Socket        Client socket number/handle
153  * \param bTrusted      Is the client trusted?
154  */
155 void Server_HandleClient(int Socket, int bTrusted)
156 {
157         char    inbuf[INPUT_BUFFER_SIZE];
158         char    *buf = inbuf;
159          int    remspace = INPUT_BUFFER_SIZE-1;
160          int    bytes = -1;
161         tClient clientInfo = {0};
162         
163         // Initialise Client info
164         clientInfo.ID = giServer_NextClientID ++;
165         clientInfo.bIsTrusted = bTrusted;
166         
167         // Read from client
168         /*
169          * Notes:
170          * - The `buf` and `remspace` variables allow a line to span several
171          *   calls to recv(), if a line is not completed in one recv() call
172          *   it is saved to the beginning of `inbuf` and `buf` is updated to
173          *   the end of it.
174          */
175         while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
176         {
177                 char    *eol, *start;
178                 buf[bytes] = '\0';      // Allow us to use stdlib string functions on it
179                 
180                 // Split by lines
181                 start = inbuf;
182                 while( (eol = strchr(start, '\n')) )
183                 {
184                         char    *ret;
185                         *eol = '\0';
186                         ret = Server_ParseClientCommand(&clientInfo, start);
187                         // `ret` is a string on the heap
188                         send(Socket, ret, strlen(ret), 0);
189                         free(ret);
190                         start = eol + 1;
191                 }
192                 
193                 // Check if there was an incomplete line
194                 if( *start != '\0' ) {
195                          int    tailBytes = bytes - (start-buf);
196                         // Roll back in buffer
197                         memcpy(inbuf, start, tailBytes);
198                         remspace -= tailBytes;
199                         if(remspace == 0) {
200                                 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
201                                 buf = inbuf;
202                                 remspace = INPUT_BUFFER_SIZE - 1;
203                         }
204                 }
205                 else {
206                         buf = inbuf;
207                         remspace = INPUT_BUFFER_SIZE - 1;
208                 }
209         }
210         
211         // Check for errors
212         if( bytes < 0 ) {
213                 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
214                 return ;
215         }
216         
217         if(giDebugLevel >= 2) {
218                 printf("Client %i: Disconnected\n", clientInfo.ID);
219         }
220 }
221
222 /**
223  * \brief Parses a client command and calls the required helper function
224  * \param Client        Pointer to client state structure
225  * \param CommandString Command from client (single line of the command)
226  * \return Heap String to return to the client
227  */
228 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
229 {
230         char    *space, *args;
231          int    i;
232         
233         // Split at first space
234         space = strchr(CommandString, ' ');
235         if(space == NULL) {
236                 args = NULL;
237         }
238         else {
239                 *space = '\0';
240                 args = space + 1;
241         }
242         
243         // Find command
244         for( i = 0; i < NUM_COMMANDS; i++ )
245         {
246                 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
247                         return gaServer_Commands[i].Function(Client, args);
248         }
249         
250         return strdup("400 Unknown Command\n");
251 }
252
253 // ---
254 // Commands
255 // ---
256 /**
257  * \brief Set client username
258  * 
259  * Usage: USER <username>
260  */
261 char *Server_Cmd_USER(tClient *Client, char *Args)
262 {
263         char    *ret;
264         
265         // Debug!
266         if( giDebugLevel )
267                 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
268         
269         // Save username
270         if(Client->Username)
271                 free(Client->Username);
272         Client->Username = strdup(Args);
273         
274         #if USE_SALT
275         // Create a salt (that changes if the username is changed)
276         // Yes, I know, I'm a little paranoid, but who isn't?
277         Client->Salt[0] = 0x21 + (rand()&0x3F);
278         Client->Salt[1] = 0x21 + (rand()&0x3F);
279         Client->Salt[2] = 0x21 + (rand()&0x3F);
280         Client->Salt[3] = 0x21 + (rand()&0x3F);
281         Client->Salt[4] = 0x21 + (rand()&0x3F);
282         Client->Salt[5] = 0x21 + (rand()&0x3F);
283         Client->Salt[6] = 0x21 + (rand()&0x3F);
284         Client->Salt[7] = 0x21 + (rand()&0x3F);
285         
286         // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
287         // "100 Salt xxxxXXXX\n"
288         ret = strdup("100 SALT xxxxXXXX\n");
289         sprintf(ret, "100 SALT %s\n", Client->Salt);
290         #else
291         ret = strdup("100 User Set\n");
292         #endif
293         return ret;
294 }
295
296 /**
297  * \brief Authenticate as a user
298  * 
299  * Usage: PASS <hash>
300  */
301 char *Server_Cmd_PASS(tClient *Client, char *Args)
302 {
303         uint8_t clienthash[HASH_LENGTH] = {0};
304         
305         // Read user's hash
306         HexBin(clienthash, Args, HASH_LENGTH);
307         
308         // TODO: Decrypt password passed
309         
310         Client->UID = GetUserAuth(Client->Username, "");
311
312         if( Client->UID != -1 ) {
313                 Client->bIsAuthed = 1;
314                 return strdup("200 Auth OK\n");
315         }
316
317         if( giDebugLevel ) {
318                  int    i;
319                 printf("Client %i: Password hash ", Client->ID);
320                 for(i=0;i<HASH_LENGTH;i++)
321                         printf("%02x", clienthash[i]&0xFF);
322                 printf("\n");
323         }
324         
325         return strdup("401 Auth Failure\n");
326 }
327
328 /**
329  * \brief Authenticate as a user without a password
330  * 
331  * Usage: AUTOAUTH <user>
332  */
333 char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
334 {
335         char    *spos = strchr(Args, ' ');
336         if(spos)        *spos = '\0';   // Remove characters after the ' '
337         
338         // Check if trusted
339         if( !Client->bIsTrusted ) {
340                 if(giDebugLevel)
341                         printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
342                 return strdup("401 Untrusted\n");
343         }
344         
345         // Get UID
346         Client->UID = GetUserID( Args );
347         if( Client->UID < 0 ) {
348                 if(giDebugLevel)
349                         printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
350                 return strdup("401 Auth Failure\n");
351         }
352         
353         if(giDebugLevel)
354                 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
355         
356         return strdup("200 Auth OK\n");
357 }
358
359 /**
360  * \brief Enumerate the items that the server knows about
361  */
362 char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
363 {
364 //       int    nItems = giNumItems;
365          int    retLen;
366          int    i;
367         char    *ret;
368
369         retLen = snprintf(NULL, 0, "201 Items %i", giNumItems);
370
371         for( i = 0; i < giNumItems; i ++ )
372         {
373                 retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
374         }
375
376         ret = malloc(retLen+1);
377         retLen = 0;
378         retLen += sprintf(ret+retLen, "201 Items %i", giNumItems);
379
380         for( i = 0; i < giNumItems; i ++ ) {
381                 retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
382         }
383
384         strcat(ret, "\n");
385
386         return ret;
387 }
388
389 tItem *_GetItemFromString(char *String)
390 {
391         tHandler        *handler;
392         char    *type = String;
393         char    *colon = strchr(String, ':');
394          int    num, i;
395         
396         if( !colon ) {
397                 return NULL;
398         }
399
400         num = atoi(colon+1);
401         *colon = '\0';
402
403         // Find handler
404         handler = NULL;
405         for( i = 0; i < giNumHandlers; i ++ )
406         {
407                 if( strcmp(gaHandlers[i]->Name, type) == 0) {
408                         handler = gaHandlers[i];
409                         break;
410                 }
411         }
412         if( !handler ) {
413                 return NULL;
414         }
415
416         // Find item
417         for( i = 0; i < giNumItems; i ++ )
418         {
419                 if( gaItems[i].Handler != handler )     continue;
420                 if( gaItems[i].ID != num )      continue;
421                 return &gaItems[i];
422         }
423         return NULL;
424 }
425
426 /**
427  * \brief Fetch information on a specific item
428  */
429 char *Server_Cmd_ITEMINFO(tClient *Client, char *Args)
430 {
431          int    retLen = 0;
432         char    *ret;
433         tItem   *item = _GetItemFromString(Args);
434         
435         if( !item ) {
436                 return strdup("406 Bad Item ID\n");
437         }
438
439         // Create return
440         retLen = snprintf(NULL, 0, "202 Item %s:%i %i %s\n",
441                 item->Handler->Name, item->ID, item->Price, item->Name);
442         ret = malloc(retLen+1);
443         sprintf(ret, "202 Item %s:%i %i %s\n",
444                 item->Handler->Name, item->ID, item->Price, item->Name);
445
446         return ret;
447 }
448
449 char *Server_Cmd_DISPENSE(tClient *Client, char *Args)
450 {
451         tItem   *item;
452         if( !Client->bIsAuthed )        return strdup("401 Not Authenticated\n");
453
454         item = _GetItemFromString(Args);
455         if( !item ) {
456                 return strdup("406 Bad Item ID\n");
457         }
458
459         switch( DispenseItem( Client->UID, item ) )
460         {
461         case 0: return strdup("200 Dispense OK\n");
462         case 1: return strdup("501 Unable to dispense\n");
463         case 2: return strdup("402 Poor You\n");
464         default:
465                 return strdup("500 Dispense Error\n");
466         }
467 }
468
469 char *Server_Cmd_GIVE(tClient *Client, char *Args)
470 {
471         char    *recipient, *ammount, *reason;
472          int    uid, iAmmount;
473         
474         if( !Client->bIsAuthed )        return strdup("401 Not Authenticated\n");
475
476         recipient = Args;
477
478         ammount = strchr(Args, ' ');
479         if( !ammount )  return strdup("407 Invalid Argument, expected 3 parameters, 1 encountered\n");
480         *ammount = '\0';
481         ammount ++;
482
483         reason = strchr(ammount, ' ');
484         if( !reason )   return strdup("407 Invalid Argument, expected 3 parameters, 2 encountered\n");
485         *reason = '\0';
486         reason ++;
487
488         // Get recipient
489         uid = GetUserID(recipient);
490         if( uid == -1 ) return strdup("404 Invalid target user");
491
492         // Parse ammount
493         iAmmount = atoi(ammount);
494         if( iAmmount <= 0 )     return strdup("407 Invalid Argument, ammount must be > zero\n");
495
496         // Do give
497         switch( Transfer(Client->UID, uid, iAmmount, reason) )
498         {
499         case 0:
500                 return strdup("200 Give OK\n");
501         default:
502                 return strdup("402 Poor You\n");
503         }
504 }
505
506 // --- INTERNAL HELPERS ---
507 // TODO: Move to another file
508 void HexBin(uint8_t *Dest, char *Src, int BufSize)
509 {
510          int    i;
511         for( i = 0; i < BufSize; i ++ )
512         {
513                 uint8_t val = 0;
514                 
515                 if('0' <= *Src && *Src <= '9')
516                         val |= (*Src-'0') << 4;
517                 else if('A' <= *Src && *Src <= 'F')
518                         val |= (*Src-'A'+10) << 4;
519                 else if('a' <= *Src && *Src <= 'f')
520                         val |= (*Src-'a'+10) << 4;
521                 else
522                         break;
523                 Src ++;
524                 
525                 if('0' <= *Src && *Src <= '9')
526                         val |= (*Src-'0');
527                 else if('A' <= *Src && *Src <= 'F')
528                         val |= (*Src-'A'+10);
529                 else if('a' <= *Src && *Src <= 'f')
530                         val |= (*Src-'a'+10);
531                 else
532                         break;
533                 Src ++;
534                 
535                 Dest[i] = val;
536         }
537         for( ; i < BufSize; i++ )
538                 Dest[i] = 0;
539 }
540
541 /**
542  * \brief Decode a Base64 value
543  */
544 int UnBase64(uint8_t *Dest, char *Src, int BufSize)
545 {
546         uint32_t        val;
547          int    i, j;
548         char    *start_src = Src;
549         
550         for( i = 0; i+2 < BufSize; i += 3 )
551         {
552                 val = 0;
553                 for( j = 0; j < 4; j++, Src ++ ) {
554                         if('A' <= *Src && *Src <= 'Z')
555                                 val |= (*Src - 'A') << ((3-j)*6);
556                         else if('a' <= *Src && *Src <= 'z')
557                                 val |= (*Src - 'a' + 26) << ((3-j)*6);
558                         else if('0' <= *Src && *Src <= '9')
559                                 val |= (*Src - '0' + 52) << ((3-j)*6);
560                         else if(*Src == '+')
561                                 val |= 62 << ((3-j)*6);
562                         else if(*Src == '/')
563                                 val |= 63 << ((3-j)*6);
564                         else if(!*Src)
565                                 break;
566                         else if(*Src != '=')
567                                 j --;   // Ignore invalid characters
568                 }
569                 Dest[i  ] = (val >> 16) & 0xFF;
570                 Dest[i+1] = (val >> 8) & 0xFF;
571                 Dest[i+2] = val & 0xFF;
572                 if(j != 4)      break;
573         }
574         
575         // Finish things off
576         if(i   < BufSize)
577                 Dest[i] = (val >> 16) & 0xFF;
578         if(i+1 < BufSize)
579                 Dest[i+1] = (val >> 8) & 0xFF;
580         
581         return Src - start_src;
582 }

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