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

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