--- /dev/null
+/*
+ * OpenDispense 2
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * itemdb.c - Dispense Item Databse
+ *
+ * This file is licenced under the 3-clause BSD Licence. See the file COPYING
+ * for full details.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "common.h"
+#include <regex.h>
+
+// === IMPORTS ===
+extern tHandler gCoke_Handler;
+
+// === PROTOTYPES ===
+void Load_Itemlist(void);
+char *trim(char *__str);
+
+// === GLOBALS ===
+ int giNumItems = 0;
+tItem *gaItems = NULL;
+tHandler gPseudo_Handler = {Name:"pseudo"};
+tHandler *gaHandlers[] = {&gPseudo_Handler, &gCoke_Handler};
+ int giNumHandlers = sizeof(gaHandlers)/sizeof(gaHandlers[0]);
+char *gsItemListFile = DEFAULT_ITEM_FILE;
+
+// === CODE ===
+/**
+ * \brief Read the item list from disk
+ */
+void Load_Itemlist(void)
+{
+ FILE *fp = fopen(gsItemListFile, "r");
+ char buffer[BUFSIZ];
+ char *line;
+ int lineNum = 0;
+ int i;
+ regex_t regex;
+ regmatch_t matches[5];
+
+ i = regcomp(®ex, "^([a-zA-Z][a-zA-Z0-9]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED);
+ if( i )
+ {
+ size_t len = regerror(i, ®ex, NULL, 0);
+ char *errorStr = malloc(len);
+ regerror(i, ®ex, errorStr, len);
+ fprintf(stderr, "Rexex compilation failed - %s\n", errorStr);
+ free(errorStr);
+ exit(-1);
+ }
+
+ // Error check
+ if(!fp) {
+ fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile);
+ perror("Unable to open item file");
+ }
+
+ while( fgets(buffer, BUFSIZ, fp) )
+ {
+ char *tmp;
+ char *type, *desc;
+ int num, price;
+ tHandler *handler;
+
+ lineNum ++;
+
+ // Remove comments
+ tmp = strchr(buffer, '#');
+ if(tmp) *tmp = '\0';
+ tmp = strchr(buffer, ';');
+ if(tmp) *tmp = '\0';
+
+ // Trim whitespace
+ line = trim(buffer);
+
+ if(strlen(line) == 0) continue;
+
+ // Pass regex over line
+ if( (i = regexec(®ex, line, 5, matches, 0)) ) {
+ size_t len = regerror(i, ®ex, NULL, 0);
+ char *errorStr = malloc(len);
+ regerror(i, ®ex, errorStr, len);
+ fprintf(stderr, "Syntax error on line %i of item file '%s'\n%s", lineNum, gsItemListFile, errorStr);
+ free(errorStr);
+ exit(-1);
+ }
+
+ // Read line data
+ type = line + matches[1].rm_so; line[ matches[1].rm_eo ] = '\0';
+ num = atoi( line + matches[2].rm_so );
+ price = atoi( line + matches[3].rm_so );
+ desc = line + matches[4].rm_so;
+
+ printf("Item '%s' - %i cents, %s:%i\n", desc, price, type, num);
+
+ handler = NULL;
+ for( i = 0; i < giNumHandlers; i ++ )
+ {
+ if( strcmp(type, gaHandlers[i]->Name) == 0 ) {
+ handler = gaHandlers[i];
+ break;
+ }
+ }
+
+ if( !handler ) {
+ fprintf(stderr, "Unknow item type '%s' on line %i (%s)\n", type, lineNum, desc);
+ continue ;
+ }
+
+ for( i = 0; i < giNumItems; i ++ )
+ {
+ if( gaItems[i].Handler != handler ) continue;
+ if( gaItems[i].ID != num ) continue;
+
+ printf("Redefinition of %s:%i, updated\n", handler->Name, num);
+ gaItems[i].Price = price;
+ free(gaItems[i].Name);
+ gaItems[i].Name = strdup(desc);
+ break;
+ }
+ if( i < giNumItems ) continue;
+
+ gaItems = realloc( gaItems, (giNumItems + 1)*sizeof(gaItems[0]) );
+ gaItems[giNumItems].Handler = handler;
+ gaItems[giNumItems].ID = num;
+ gaItems[giNumItems].Price = price;
+ gaItems[giNumItems].Name = strdup(desc);
+ giNumItems ++;
+ }
+}
+
+char *trim(char *__str)
+{
+ char *ret;
+ int i;
+
+ while( isspace(*__str) )
+ __str++;
+ ret = __str;
+
+ i = strlen(ret);
+ while( i-- && isspace(__str[i]) ) {
+ __str[i] = '\0';
+ }
+
+ return ret;
+}
--- /dev/null
+/*
+ * OpenDispense 2
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * server.c - Client Server Code
+ *
+ * This file is licenced under the 3-clause BSD Licence. See the file
+ * COPYING for full details.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include "common.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+
+#define MAX_CONNECTION_QUEUE 5
+#define INPUT_BUFFER_SIZE 256
+
+#define HASH_TYPE SHA512
+#define HASH_LENGTH 64
+
+#define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n"
+
+// === TYPES ===
+typedef struct sClient
+{
+ int ID; // Client ID
+
+ int bIsTrusted; // Is the connection from a trusted host/port
+
+ char *Username;
+ char Salt[9];
+
+ int UID;
+ int bIsAuthed;
+} tClient;
+
+// === PROTOTYPES ===
+void Server_Start(void);
+void Server_HandleClient(int Socket, int bTrusted);
+char *Server_ParseClientCommand(tClient *Client, char *CommandString);
+// --- Commands ---
+char *Server_Cmd_USER(tClient *Client, char *Args);
+char *Server_Cmd_PASS(tClient *Client, char *Args);
+char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args);
+char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args);
+char *Server_Cmd_ITEMINFO(tClient *Client, char *Args);
+// --- Helpers ---
+void HexBin(uint8_t *Dest, char *Src, int BufSize);
+
+// === GLOBALS ===
+ int giServer_Port = 1020;
+ int giServer_NextClientID = 1;
+// - Commands
+struct sClientCommand {
+ char *Name;
+ char *(*Function)(tClient *Client, char *Arguments);
+} gaServer_Commands[] = {
+ {"USER", Server_Cmd_USER},
+ {"PASS", Server_Cmd_PASS},
+ {"AUTOAUTH", Server_Cmd_AUTOAUTH},
+ {"ENUM_ITEMS", Server_Cmd_ENUMITEMS},
+ {"ITEM_INFO", Server_Cmd_ITEMINFO}
+};
+#define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
+
+// === CODE ===
+/**
+ * \brief Open listenting socket and serve connections
+ */
+void Server_Start(void)
+{
+ int server_socket, client_socket;
+ struct sockaddr_in server_addr, client_addr;
+
+ // Create Server
+ server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if( server_socket < 0 ) {
+ fprintf(stderr, "ERROR: Unable to create server socket\n");
+ return ;
+ }
+
+ // Make listen address
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET; // Internet Socket
+ server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces
+ server_addr.sin_port = htons(giServer_Port); // Port
+
+ // Bind
+ if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) {
+ fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port);
+ return ;
+ }
+
+ // Listen
+ if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) {
+ fprintf(stderr, "ERROR: Unable to listen to socket\n");
+ return ;
+ }
+
+ printf("Listening on 0.0.0.0:%i\n", giServer_Port);
+
+ for(;;)
+ {
+ uint len = sizeof(client_addr);
+ int bTrusted = 0;
+
+ client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len);
+ if(client_socket < 0) {
+ fprintf(stderr, "ERROR: Unable to accept client connection\n");
+ return ;
+ }
+
+ if(giDebugLevel >= 2) {
+ char ipstr[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN);
+ printf("Client connection from %s:%i\n",
+ ipstr, ntohs(client_addr.sin_port));
+ }
+
+ // Trusted Connections
+ if( ntohs(client_addr.sin_port) < 1024 )
+ {
+ // TODO: Make this runtime configurable
+ switch( ntohl( client_addr.sin_addr.s_addr ) )
+ {
+ case 0x7F000001: // 127.0.0.1 localhost
+ //case 0x825E0D00: // 130.95.13.0
+ case 0x825E0D12: // 130.95.13.18 mussel
+ case 0x825E0D17: // 130.95.13.23 martello
+ bTrusted = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // TODO: Multithread this?
+ Server_HandleClient(client_socket, bTrusted);
+
+ close(client_socket);
+ }
+}
+
+/**
+ * \brief Reads from a client socket and parses the command strings
+ * \param Socket Client socket number/handle
+ * \param bTrusted Is the client trusted?
+ */
+void Server_HandleClient(int Socket, int bTrusted)
+{
+ char inbuf[INPUT_BUFFER_SIZE];
+ char *buf = inbuf;
+ int remspace = INPUT_BUFFER_SIZE-1;
+ int bytes = -1;
+ tClient clientInfo = {0};
+
+ // Initialise Client info
+ clientInfo.ID = giServer_NextClientID ++;
+ clientInfo.bIsTrusted = bTrusted;
+
+ // Read from client
+ /*
+ * Notes:
+ * - The `buf` and `remspace` variables allow a line to span several
+ * calls to recv(), if a line is not completed in one recv() call
+ * it is saved to the beginning of `inbuf` and `buf` is updated to
+ * the end of it.
+ */
+ while( (bytes = recv(Socket, buf, remspace, 0)) > 0 )
+ {
+ char *eol, *start;
+ buf[bytes] = '\0'; // Allow us to use stdlib string functions on it
+
+ // Split by lines
+ start = inbuf;
+ while( (eol = strchr(start, '\n')) )
+ {
+ char *ret;
+ *eol = '\0';
+ ret = Server_ParseClientCommand(&clientInfo, start);
+ // `ret` is a string on the heap
+ send(Socket, ret, strlen(ret), 0);
+ free(ret);
+ start = eol + 1;
+ }
+
+ // Check if there was an incomplete line
+ if( *start != '\0' ) {
+ int tailBytes = bytes - (start-buf);
+ // Roll back in buffer
+ memcpy(inbuf, start, tailBytes);
+ remspace -= tailBytes;
+ if(remspace == 0) {
+ send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0);
+ buf = inbuf;
+ remspace = INPUT_BUFFER_SIZE - 1;
+ }
+ }
+ else {
+ buf = inbuf;
+ remspace = INPUT_BUFFER_SIZE - 1;
+ }
+ }
+
+ // Check for errors
+ if( bytes < 0 ) {
+ fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket);
+ return ;
+ }
+
+ if(giDebugLevel >= 2) {
+ printf("Client %i: Disconnected\n", clientInfo.ID);
+ }
+}
+
+/**
+ * \brief Parses a client command and calls the required helper function
+ * \param Client Pointer to client state structure
+ * \param CommandString Command from client (single line of the command)
+ * \return Heap String to return to the client
+ */
+char *Server_ParseClientCommand(tClient *Client, char *CommandString)
+{
+ char *space, *args;
+ int i;
+
+ // Split at first space
+ space = strchr(CommandString, ' ');
+ if(space == NULL) {
+ args = NULL;
+ }
+ else {
+ *space = '\0';
+ args = space + 1;
+ }
+
+ // Find command
+ for( i = 0; i < NUM_COMMANDS; i++ )
+ {
+ if(strcmp(CommandString, gaServer_Commands[i].Name) == 0)
+ return gaServer_Commands[i].Function(Client, args);
+ }
+
+ return strdup("400 Unknown Command\n");
+}
+
+// ---
+// Commands
+// ---
+/**
+ * \brief Set client username
+ *
+ * Usage: USER <username>
+ */
+char *Server_Cmd_USER(tClient *Client, char *Args)
+{
+ char *ret;
+
+ // Debug!
+ if( giDebugLevel )
+ printf("Client %i authenticating as '%s'\n", Client->ID, Args);
+
+ // Save username
+ if(Client->Username)
+ free(Client->Username);
+ Client->Username = strdup(Args);
+
+ #if USE_SALT
+ // Create a salt (that changes if the username is changed)
+ // Yes, I know, I'm a little paranoid, but who isn't?
+ Client->Salt[0] = 0x21 + (rand()&0x3F);
+ Client->Salt[1] = 0x21 + (rand()&0x3F);
+ Client->Salt[2] = 0x21 + (rand()&0x3F);
+ Client->Salt[3] = 0x21 + (rand()&0x3F);
+ Client->Salt[4] = 0x21 + (rand()&0x3F);
+ Client->Salt[5] = 0x21 + (rand()&0x3F);
+ Client->Salt[6] = 0x21 + (rand()&0x3F);
+ Client->Salt[7] = 0x21 + (rand()&0x3F);
+
+ // "100 Salt xxxxXXXX\n"
+ ret = strdup("100 SALT xxxxXXXX\n");
+ sprintf(ret, "100 SALT %s\n", Client->Salt);
+ #else
+ ret = strdup("100 User Set\n");
+ #endif
+ return ret;
+}
+
+/**
+ * \brief Authenticate as a user
+ *
+ * Usage: PASS <hash>
+ */
+char *Server_Cmd_PASS(tClient *Client, char *Args)
+{
+ uint8_t clienthash[HASH_LENGTH] = {0};
+
+ // Read user's hash
+ HexBin(clienthash, Args, HASH_LENGTH);
+
+ if( giDebugLevel ) {
+ int i;
+ printf("Client %i: Password hash ", Client->ID);
+ for(i=0;i<HASH_LENGTH;i++)
+ printf("%02x", clienthash[i]&0xFF);
+ printf("\n");
+ }
+
+ return strdup("401 Auth Failure\n");
+}
+
+/**
+ * \brief Authenticate as a user without a password
+ *
+ * Usage: AUTOAUTH <user>
+ */
+char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
+{
+ char *spos = strchr(Args, ' ');
+ if(spos) *spos = '\0'; // Remove characters after the ' '
+
+ // Check if trusted
+ if( !Client->bIsTrusted ) {
+ if(giDebugLevel)
+ printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID);
+ return strdup("401 Untrusted\n");
+ }
+
+ // Get UID
+ Client->UID = GetUserID( Args );
+ if( Client->UID < 0 ) {
+ if(giDebugLevel)
+ printf("Client %i: Unknown user '%s'\n", Client->ID, Args);
+ return strdup("401 Auth Failure\n");
+ }
+
+ if(giDebugLevel)
+ printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID);
+
+ return strdup("200 Auth OK\n");
+}
+
+/**
+ * \brief Enumerate the items that the server knows about
+ */
+char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args)
+{
+// int nItems = giNumItems;
+ int retLen;
+ int i;
+ char *ret;
+
+ retLen = snprintf(NULL, 0, "201 Items %i", giNumItems);
+
+ for( i = 0; i < giNumItems; i ++ )
+ {
+ retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
+ }
+
+ ret = malloc(retLen+1);
+ retLen = 0;
+ retLen += sprintf(ret+retLen, "201 Items %i", giNumItems);
+
+ for( i = 0; i < giNumItems; i ++ ) {
+ retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID);
+ }
+
+ strcat(ret, "\n");
+
+ return ret;
+}
+
+/**
+ * \brief Fetch information on a specific item
+ */
+char *Server_Cmd_ITEMINFO(tClient *Client, char *Args)
+{
+ int retLen = 0;
+ char *ret;
+ tItem *item;
+ tHandler *handler;
+ char *type = Args;
+ char *colon = strchr(Args, ':');
+ int num, i;
+
+ if( !colon ) {
+ return strdup("406 Bad Item ID\n");
+ }
+
+ num = atoi(colon+1);
+ *colon = '\0';
+
+ // Find handler
+ handler = NULL;
+ for( i = 0; i < giNumHandlers; i ++ )
+ {
+ if( strcmp(gaHandlers[i]->Name, type) == 0) {
+ handler = gaHandlers[i];
+ break;
+ }
+ }
+ if( !handler ) {
+ return strdup("406 Bad Item ID\n");
+ }
+
+ // Find item
+ for( i = 0; i < giNumItems; i ++ )
+ {
+ if( gaItems[i].Handler != handler ) continue;
+ if( gaItems[i].ID != num ) continue;
+ item = &gaItems[i];
+ break;
+ }
+ if( !item ) {
+ return strdup("406 Bad Item ID\n");
+ }
+
+ // Create return
+ retLen = snprintf(NULL, 0, "202 Item %s:%i %i %s\n",
+ handler->Name, item->ID, item->Price, item->Name);
+ ret = malloc(retLen+1);
+ sprintf(ret, "202 Item %s:%i %i %s\n",
+ handler->Name, item->ID, item->Price, item->Name);
+
+ return ret;
+}
+
+// --- INTERNAL HELPERS ---
+// TODO: Move to another file
+void HexBin(uint8_t *Dest, char *Src, int BufSize)
+{
+ int i;
+ for( i = 0; i < BufSize; i ++ )
+ {
+ uint8_t val = 0;
+
+ if('0' <= *Src && *Src <= '9')
+ val |= (*Src-'0') << 4;
+ else if('A' <= *Src && *Src <= 'F')
+ val |= (*Src-'A'+10) << 4;
+ else if('a' <= *Src && *Src <= 'f')
+ val |= (*Src-'a'+10) << 4;
+ else
+ break;
+ Src ++;
+
+ if('0' <= *Src && *Src <= '9')
+ val |= (*Src-'0');
+ else if('A' <= *Src && *Src <= 'F')
+ val |= (*Src-'A'+10);
+ else if('a' <= *Src && *Src <= 'f')
+ val |= (*Src-'a'+10);
+ else
+ break;
+ Src ++;
+
+ Dest[i] = val;
+ }
+ for( ; i < BufSize; i++ )
+ Dest[i] = 0;
+}
+
+/**
+ * \brief Decode a Base64 value
+ */
+int UnBase64(uint8_t *Dest, char *Src, int BufSize)
+{
+ uint32_t val;
+ int i, j;
+ char *start_src = Src;
+
+ for( i = 0; i+2 < BufSize; i += 3 )
+ {
+ val = 0;
+ for( j = 0; j < 4; j++, Src ++ ) {
+ if('A' <= *Src && *Src <= 'Z')
+ val |= (*Src - 'A') << ((3-j)*6);
+ else if('a' <= *Src && *Src <= 'z')
+ val |= (*Src - 'a' + 26) << ((3-j)*6);
+ else if('0' <= *Src && *Src <= '9')
+ val |= (*Src - '0' + 52) << ((3-j)*6);
+ else if(*Src == '+')
+ val |= 62 << ((3-j)*6);
+ else if(*Src == '/')
+ val |= 63 << ((3-j)*6);
+ else if(!*Src)
+ break;
+ else if(*Src != '=')
+ j --; // Ignore invalid characters
+ }
+ Dest[i ] = (val >> 16) & 0xFF;
+ Dest[i+1] = (val >> 8) & 0xFF;
+ Dest[i+2] = val & 0xFF;
+ if(j != 4) break;
+ }
+
+ // Finish things off
+ if(i < BufSize)
+ Dest[i] = (val >> 16) & 0xFF;
+ if(i+1 < BufSize)
+ Dest[i+1] = (val >> 8) & 0xFF;
+
+ return Src - start_src;
+}