3 * UCC (University [of WA] Computer Club) Electronic Accounting System
5 * server.c - Client Server Code
7 * This file is licenced under the 3-clause BSD Licence. See the file
8 * COPYING for full details.
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
20 #define HACK_TPG_NOAUTH 1
21 #define HACK_ROOT_NOAUTH 1
24 #define MAX_CONNECTION_QUEUE 5
25 #define INPUT_BUFFER_SIZE 256
27 #define HASH_TYPE SHA1
28 #define HASH_LENGTH 20
30 #define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
33 typedef struct sClient
37 int bIsTrusted; // Is the connection from a trusted host/port
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);
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 char *Server_Cmd_USERINFO(tClient *Client, char *Args);
62 int GetUserAuth(const char *Salt, const char *Username, const uint8_t *Hash);
63 void HexBin(uint8_t *Dest, char *Src, int BufSize);
66 int giServer_Port = 1020;
67 int giServer_NextClientID = 1;
69 struct sClientCommand {
71 char *(*Function)(tClient *Client, char *Arguments);
72 } gaServer_Commands[] = {
73 {"USER", Server_Cmd_USER},
74 {"PASS", Server_Cmd_PASS},
75 {"AUTOAUTH", Server_Cmd_AUTOAUTH},
76 {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
77 {"ITEM_INFO", Server_Cmd_ITEMINFO},
78 {"DISPENSE", Server_Cmd_DISPENSE},
79 {"GIVE", Server_Cmd_GIVE},
80 {"ADD", Server_Cmd_ADD},
81 {"USER_INFO", Server_Cmd_USERINFO}
83 #define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
88 * \brief Open listenting socket and serve connections
90 void Server_Start(void)
93 struct sockaddr_in server_addr, client_addr;
95 atexit(Server_Cleanup);
98 giServer_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
99 if( giServer_Socket < 0 ) {
100 fprintf(stderr, "ERROR: Unable to create server socket\n");
104 // Make listen address
105 memset(&server_addr, 0, sizeof(server_addr));
106 server_addr.sin_family = AF_INET; // Internet Socket
107 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
108 server_addr.sin_port = htons(giServer_Port); // Port
111 if( bind(giServer_Socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
112 fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
118 if( listen(giServer_Socket, MAX_CONNECTION_QUEUE) < 0 ) {
119 fprintf(stderr, "ERROR: Unable to listen to socket\n");
124 printf("Listening on 0.0.0.0:%i\n", giServer_Port);
128 uint len = sizeof(client_addr);
131 client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
132 if(client_socket < 0) {
133 fprintf(stderr, "ERROR: Unable to accept client connection\n");
137 if(giDebugLevel >= 2) {
138 char ipstr[INET_ADDRSTRLEN];
139 inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
140 printf("Client connection from %s:%i\n",
141 ipstr, ntohs(client_addr.sin_port));
144 // Trusted Connections
145 if( ntohs(client_addr.sin_port) < 1024 )
147 // TODO: Make this runtime configurable
148 switch( ntohl( client_addr.sin_addr.s_addr ) )
150 case 0x7F000001: // 127.0.0.1 localhost
151 //case 0x825E0D00: // 130.95.13.0
152 case 0x825E0D12: // 130.95.13.18 mussel
153 case 0x825E0D17: // 130.95.13.23 martello
161 // TODO: Multithread this?
162 Server_HandleClient(client_socket, bTrusted);
164 close(client_socket);
168 void Server_Cleanup(void)
170 printf("Close(%i)\n", giServer_Socket);
171 close(giServer_Socket);
175 * \brief Reads from a client socket and parses the command strings
176 * \param Socket Client socket number/handle
177 * \param bTrusted Is the client trusted?
179 void Server_HandleClient(int Socket, int bTrusted)
181 char inbuf[INPUT_BUFFER_SIZE];
183 int remspace = INPUT_BUFFER_SIZE-1;
185 tClient clientInfo = {0};
187 // Initialise Client info
188 clientInfo.ID = giServer_NextClientID ++;
189 clientInfo.bIsTrusted = bTrusted;
194 * - The `buf` and `remspace` variables allow a line to span several
195 * calls to recv(), if a line is not completed in one recv() call
196 * it is saved to the beginning of `inbuf` and `buf` is updated to
199 while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
202 buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
206 while( (eol = strchr(start, '\n')) )
210 ret = Server_ParseClientCommand(&clientInfo, start);
212 #if DEBUG_TRACE_CLIENT
213 //printf("ret = %s", ret);
216 // `ret` is a string on the heap
217 send(Socket, ret, strlen(ret), 0);
222 // Check if there was an incomplete line
223 if( *start != '\0' ) {
224 int tailBytes = bytes - (start-buf);
225 // Roll back in buffer
226 memcpy(inbuf, start, tailBytes);
227 remspace -= tailBytes;
229 send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
231 remspace = INPUT_BUFFER_SIZE - 1;
236 remspace = INPUT_BUFFER_SIZE - 1;
242 fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
246 if(giDebugLevel >= 2) {
247 printf("Client %i: Disconnected\n", clientInfo.ID);
252 * \brief Parses a client command and calls the required helper function
253 * \param Client Pointer to client state structure
254 * \param CommandString Command from client (single line of the command)
255 * \return Heap String to return to the client
257 char *Server_ParseClientCommand(tClient *Client, char *CommandString)
262 // Split at first space
263 space = strchr(CommandString, ' ');
273 for( i = 0; i < NUM_COMMANDS; i++ )
275 if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
276 return gaServer_Commands[i].Function(Client, args);
279 return strdup("400 Unknown Command\n");
286 * \brief Set client username
288 * Usage: USER <username>
290 char *Server_Cmd_USER(tClient *Client, char *Args)
296 printf("Client %i authenticating as '%s'\n", Client->ID, Args);
300 free(Client->Username);
301 Client->Username = strdup(Args);
304 // Create a salt (that changes if the username is changed)
305 // Yes, I know, I'm a little paranoid, but who isn't?
306 Client->Salt[0] = 0x21 + (rand()&0x3F);
307 Client->Salt[1] = 0x21 + (rand()&0x3F);
308 Client->Salt[2] = 0x21 + (rand()&0x3F);
309 Client->Salt[3] = 0x21 + (rand()&0x3F);
310 Client->Salt[4] = 0x21 + (rand()&0x3F);
311 Client->Salt[5] = 0x21 + (rand()&0x3F);
312 Client->Salt[6] = 0x21 + (rand()&0x3F);
313 Client->Salt[7] = 0x21 + (rand()&0x3F);
315 // TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
316 ret = mkstr("100 SALT %s\n", Client->Salt);
318 ret = strdup("100 User Set\n");
324 * \brief Authenticate as a user
328 char *Server_Cmd_PASS(tClient *Client, char *Args)
330 uint8_t clienthash[HASH_LENGTH] = {0};
333 HexBin(clienthash, Args, HASH_LENGTH);
335 // TODO: Decrypt password passed
337 Client->UID = GetUserAuth(Client->Salt, Client->Username, clienthash);
339 if( Client->UID != -1 ) {
340 Client->bIsAuthed = 1;
341 return strdup("200 Auth OK\n");
346 printf("Client %i: Password hash ", Client->ID);
347 for(i=0;i<HASH_LENGTH;i++)
348 printf("%02x", clienthash[i]&0xFF);
352 return strdup("401 Auth Failure\n");
356 * \brief Authenticate as a user without a password
358 * Usage: AUTOAUTH <user>
360 char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
362 char *spos = strchr(Args, ' ');
363 if(spos) *spos = '\0'; // Remove characters after the ' '
366 if( !Client->bIsTrusted ) {
368 printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
369 return strdup("401 Untrusted\n");
373 Client->UID = GetUserID( Args );
374 if( Client->UID < 0 ) {
376 printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
377 return strdup("401 Auth Failure\n");
381 printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
383 return strdup("200 Auth OK\n");
387 * \brief Enumerate the items that the server knows about
389 char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
395 retLen = snprintf(NULL, 0, "201 Items %i", giNumItems);
397 for( i = 0; i < giNumItems; i ++ )
399 retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
402 ret = malloc(retLen+1);
404 retLen += sprintf(ret+retLen, "201 Items %i", giNumItems);
406 for( i = 0; i < giNumItems; i ++ ) {
407 retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
415 tItem *_GetItemFromString(char *String)
419 char *colon = strchr(String, ':');
431 for( i = 0; i < giNumHandlers; i ++ )
433 if( strcmp(gaHandlers[i]->Name, type) == 0) {
434 handler = gaHandlers[i];
443 for( i = 0; i < giNumItems; i ++ )
445 if( gaItems[i].Handler != handler ) continue;
446 if( gaItems[i].ID != num ) continue;
453 * \brief Fetch information on a specific item
455 char *Server_Cmd_ITEMINFO(tClient *Client, char *Args)
459 tItem *item = _GetItemFromString(Args);
462 return strdup("406 Bad Item ID\n");
466 retLen = snprintf(NULL, 0, "202 Item %s:%i %i %s\n",
467 item->Handler->Name, item->ID, item->Price, item->Name);
468 ret = malloc(retLen+1);
469 sprintf(ret, "202 Item %s:%i %i %s\n",
470 item->Handler->Name, item->ID, item->Price, item->Name);
475 char *Server_Cmd_DISPENSE(tClient *Client, char *Args)
479 if( !Client->bIsAuthed ) return strdup("401 Not Authenticated\n");
481 item = _GetItemFromString(Args);
483 return strdup("406 Bad Item ID\n");
486 switch( ret = DispenseItem( Client->UID, item ) )
488 case 0: return strdup("200 Dispense OK\n");
489 case 1: return strdup("501 Unable to dispense\n");
490 case 2: return strdup("402 Poor You\n");
492 return strdup("500 Dispense Error\n");
496 char *Server_Cmd_GIVE(tClient *Client, char *Args)
498 char *recipient, *ammount, *reason;
501 if( !Client->bIsAuthed ) return strdup("401 Not Authenticated\n");
505 ammount = strchr(Args, ' ');
506 if( !ammount ) return strdup("407 Invalid Argument, expected 3 parameters, 1 encountered\n");
510 reason = strchr(ammount, ' ');
511 if( !reason ) return strdup("407 Invalid Argument, expected 3 parameters, 2 encountered\n");
516 uid = GetUserID(recipient);
517 if( uid == -1 ) return strdup("404 Invalid target user");
520 iAmmount = atoi(ammount);
521 if( iAmmount <= 0 ) return strdup("407 Invalid Argument, ammount must be > zero\n");
524 switch( DispenseGive(Client->UID, uid, iAmmount, reason) )
527 return strdup("200 Give OK\n");
529 return strdup("402 Poor You\n");
531 return strdup("500 Unknown error\n");
535 char *Server_Cmd_ADD(tClient *Client, char *Args)
537 char *user, *ammount, *reason;
540 if( !Client->bIsAuthed ) return strdup("401 Not Authenticated\n");
544 ammount = strchr(Args, ' ');
545 if( !ammount ) return strdup("407 Invalid Argument, expected 3 parameters, 1 encountered\n");
549 reason = strchr(ammount, ' ');
550 if( !reason ) return strdup("407 Invalid Argument, expected 3 parameters, 2 encountered\n");
554 // TODO: Check if the current user is in coke/higher
557 uid = GetUserID(user);
558 if( uid == -1 ) return strdup("404 Invalid user");
561 iAmmount = atoi(ammount);
562 if( iAmmount == 0 && ammount[0] != '0' ) return strdup("407 Invalid Argument, ammount must be > zero\n");
565 switch( DispenseAdd(uid, Client->UID, iAmmount, reason) )
568 return strdup("200 Add OK\n");
570 return strdup("402 Poor Guy\n");
572 return strdup("500 Unknown error\n");
576 char *Server_Cmd_USERINFO(tClient *Client, char *Args)
582 space = strchr(user, ' ');
583 if(space) *space = '\0';
586 uid = GetUserID(user);
587 if( uid == -1 ) return strdup("404 Invalid user");
589 return mkstr("202 User %s %i user\n", user, GetBalance(uid));
593 * \brief Authenticate a user
594 * \return User ID, or -1 if authentication failed
596 int GetUserAuth(const char *Salt, const char *Username, const uint8_t *ProvidedHash)
600 int ofs = strlen(Username) + strlen(Salt);
601 char input[ ofs + 40 + 1];
602 char tmp[4 + strlen(Username) + 1]; // uid=%s
606 if( strcmp(Username, "tpg") == 0 )
607 return GetUserID("tpg");
610 if( strcmp(Username, "root") == 0 )
611 return GetUserID("root");
616 strcpy(input, Username);
618 // TODO: Get user's SHA-1 hash
619 sprintf(tmp, "uid=%s", Username);
620 ldap_search_s(ld, "", LDAP_SCOPE_BASE, tmp, "userPassword", 0, res);
622 sprintf(input+ofs, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
623 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
624 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
626 // Then create the hash from the provided salt
627 // Compare that with the provided hash
633 // --- INTERNAL HELPERS ---
634 // TODO: Move to another file
635 void HexBin(uint8_t *Dest, char *Src, int BufSize)
638 for( i = 0; i < BufSize; i ++ )
642 if('0' <= *Src && *Src <= '9')
643 val |= (*Src-'0') << 4;
644 else if('A' <= *Src && *Src <= 'F')
645 val |= (*Src-'A'+10) << 4;
646 else if('a' <= *Src && *Src <= 'f')
647 val |= (*Src-'a'+10) << 4;
652 if('0' <= *Src && *Src <= '9')
654 else if('A' <= *Src && *Src <= 'F')
655 val |= (*Src-'A'+10);
656 else if('a' <= *Src && *Src <= 'f')
657 val |= (*Src-'a'+10);
664 for( ; i < BufSize; i++ )
669 * \brief Decode a Base64 value
671 int UnBase64(uint8_t *Dest, char *Src, int BufSize)
675 char *start_src = Src;
677 for( i = 0; i+2 < BufSize; i += 3 )
680 for( j = 0; j < 4; j++, Src ++ ) {
681 if('A' <= *Src && *Src <= 'Z')
682 val |= (*Src - 'A') << ((3-j)*6);
683 else if('a' <= *Src && *Src <= 'z')
684 val |= (*Src - 'a' + 26) << ((3-j)*6);
685 else if('0' <= *Src && *Src <= '9')
686 val |= (*Src - '0' + 52) << ((3-j)*6);
688 val |= 62 << ((3-j)*6);
690 val |= 63 << ((3-j)*6);
694 j --; // Ignore invalid characters
696 Dest[i ] = (val >> 16) & 0xFF;
697 Dest[i+1] = (val >> 8) & 0xFF;
698 Dest[i+2] = val & 0xFF;
704 Dest[i] = (val >> 16) & 0xFF;
706 Dest[i+1] = (val >> 8) & 0xFF;
708 return Src - start_src;