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

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