diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fec2672cba52504447d14c102487f0d881eb5f80
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.[od]
+*~
+dispsrv
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..55bf2ddf25f03a8335f536465d052e37d9e64921
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,11 @@
+
+.PHONY:	all clean
+
+all:
+	@make -C cokebank all
+	@make -C server all
+
+clean:
+	@make -C cokebank clean
+	@make -C server clean
+
diff --git a/src/cokebank/Makefile b/src/cokebank/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8e6ea6041ff26c11f9f29d1fc8206d7285d7db63
--- /dev/null
+++ b/src/cokebank/Makefile
@@ -0,0 +1,20 @@
+
+BIN := ../../cokebank.so
+OBJ := main.o
+
+CPPFLAGS := 
+CFLAGS := -Wall -Werror -g -fPIC
+LDFLAGS := -shared -Wl,-soname,cokebank.so
+
+.PHONY: all clean
+
+all:	$(BIN)
+
+clean:
+	$(RM) $(BIN) $(OBJ)
+
+$(BIN):	$(OBJ)
+	$(CC) $(LDFLAGS) -o $(BIN) $(OBJ)
+
+%.o: %.c
+	$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
diff --git a/src/cokebank/main.c b/src/cokebank/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..d83958fd532b334b9fe2483e6352869374f19bb0
--- /dev/null
+++ b/src/cokebank/main.c
@@ -0,0 +1,77 @@
+/*
+ * OpenDispense 2 
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * cokebank.c - Coke-Bank management
+ *
+ * This file is licenced under the 3-clause BSD Licence. See the file COPYING
+ * for full details.
+ * 
+ * TODO: Make this a Dynamic Library and load it at runtime
+ */
+#include <stdlib.h>
+#include <stdio.h>
+
+// === PROTOTYPES ===
+void	Init_Cokebank(void);
+ int	Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
+ int	GetBalance(int User);
+char	*GetUserName(int User);
+ int	GetUserID(const char *Username); 
+ int	GetUserAuth(const char *Username, const char *Password);
+
+// === CODE ===
+/**
+ * \brief Load the cokebank database
+ */
+void Init_Cokebank(void)
+{
+	
+}
+
+/**
+ * \brief Transfers money from one user to another
+ * \param SourceUser	Source user
+ * \param DestUser	Destination user
+ * \param Ammount	Ammount of cents to move from \a SourceUser to \a DestUser
+ * \param Reason	Reason for the transfer (essentially a comment)
+ * \return Boolean failure
+ */
+int Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
+{
+	return 0;
+}
+
+/**
+ * \brief Get the balance of the passed user
+ */
+int GetBalance(int User)
+{
+	return 0;
+}
+
+/**
+ * \brief Return the name the passed user
+ */
+char *GetUserName(int User)
+{
+	return NULL;
+}
+
+/**
+ * \brief Get the User ID of the named user
+ */
+int GetUserID(const char *Username)
+{
+	return -1;
+}
+
+/**
+ * \brief Authenticate a user
+ * \return User ID, or -1 if authentication failed
+ */
+int GetUserAuth(const char *Username, const char *Password)
+{
+	return -1;
+}
+
diff --git a/src/server/Makefile b/src/server/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..34e55f6e255d0610c37160b328dbf5fe460bfc1f
--- /dev/null
+++ b/src/server/Makefile
@@ -0,0 +1,22 @@
+# OpenDispense 2
+#
+
+OBJ := main.o server.o logging.o
+OBJ += dispense.o itemdb.o
+OBJ += handler_coke.o
+BIN := ../../dispsrv
+
+LINKFLAGS := -g ../../cokebank.so
+CPPFLAGS := 
+CFLAGS := -Wall -g
+
+.PHONY: all clean
+
+all: $(BIN)
+
+clean:
+	$(RM) $(BIN) $(OBJ)
+
+$(BIN): $(OBJ)
+	$(CC) -o $(BIN) $(LINKFLAGS) $(OBJ)
+
diff --git a/src/server/common.h b/src/server/common.h
new file mode 100644
index 0000000000000000000000000000000000000000..9105db1337fcb7d09520948b5bc34c82cdae12d7
--- /dev/null
+++ b/src/server/common.h
@@ -0,0 +1,76 @@
+/*
+ * OpenDispense2
+ *
+ * This code is published under the terms of the Acess licence.
+ * See the file COPYING for details.
+ *
+ * common.h - Core Header
+ */
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+// === CONSTANTS ===
+#define	DEFAULT_CONFIG_FILE	"/etc/opendispense/main.cfg"
+#define	DEFAULT_ITEM_FILE	"/etc/opendispense/items.cfg"
+
+// === HELPER MACROS ===
+#define _EXPSTR(x)	#x
+#define EXPSTR(x)	_EXPSTR(x)
+
+// === STRUCTURES ===
+typedef struct sItem	tItem;
+typedef struct sUser	tUser;
+typedef struct sConfigItem	tConfigItem;
+typedef struct sHandler	tHandler;
+
+struct sItem
+{
+	char	*Name;	//!< Display Name
+	 int	Price;	//!< Price
+	
+	tHandler	*Handler;	//!< Handler for the item
+	short	ID;	//!< Item ID
+};
+
+struct sUser
+{
+	 int	ID;		//!< User ID (LDAP ID)
+	 int	Balance;	//!< Balance in cents
+	 int	Bytes;	//!< Traffic Usage
+	char	Name[];	//!< Username
+};
+
+struct sConfigItem
+{
+	char	*Name;
+	char	*Value;
+};
+
+struct sHandler
+{
+	char	*Name;
+	 int	(*Init)(int NConfig, tConfigItem *Config);
+	 int	(*CanDispense)(int User, int ID);
+	 int	(*DoDispense)(int User, int ID);
+};
+
+// === GLOBALS ===
+extern tItem	*gaItems;
+extern int	giNumItems;
+extern tHandler	*gaHandlers[];
+extern int	giNumHandlers;
+extern int	giDebugLevel;
+
+// === FUNCTIONS ===
+// --- Logging ---
+extern void	Log_Error(const char *Format, ...);
+extern void	Log_Info(const char *Format, ...);
+
+// --- Cokebank Functions ---
+extern int	Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason);
+extern int	GetBalance(int User);
+extern char	*GetUserName(int User);
+extern int	GetUserID(const char *Username);
+extern int	GetUserAuth(const char *Username, const char *Password);
+
+#endif
diff --git a/src/server/dispense.c b/src/server/dispense.c
new file mode 100644
index 0000000000000000000000000000000000000000..32e6647d6343f12b3054aba1409ee653cb4b0356
--- /dev/null
+++ b/src/server/dispense.c
@@ -0,0 +1,58 @@
+/**
+ */
+#include "common.h"
+#include <stdlib.h>
+
+// === CODE ===
+/**
+ * \brief Dispense an item for a user
+ * 
+ * The core of the dispense system, I kinda like it :)
+ */
+int DispenseItem(int User, int Item)
+{
+	 int	ret;
+	tItem	*item;
+	tHandler	*handler;
+	char	*username;
+	
+	// Sanity check please?
+	if(Item < 0 || Item >= giNumItems)
+		return -1;
+	
+	// Get item pointers
+	item = &gaItems[Item];
+	handler = item->Handler;
+	
+	// Check if the dispense is possible
+	ret = handler->CanDispense( User, item->ID );
+	if(!ret)	return ret;
+	
+	// Subtract the balance
+	ret = AlterBalance( User, -item->Price );
+	// What value should I use for this error?
+	// AlterBalance should return the final user balance
+	if(ret == 0)	return 1;
+	
+	// Get username for debugging
+	username = GetUserName(User);
+	
+	// Actually do the dispense
+	ret = handler->DoDispense( User, item->ID );
+	if(ret) {
+		Log_Error("Dispense failed after deducting cost (%s dispensing %s - %ic)",
+			username, item->Name, item->Price);
+		AlterBalance( User, item->Price );
+		free( username );
+		return 1;
+	}
+	
+	// And log that it happened
+	Log_Info("Dispensed %s (%i:%i) for %s [cost %i, balance %i cents]",
+		item->Name, handler->Name, item->ID,
+		username, item->Price, GetBalance(User)
+		);
+	
+	free( username );
+	return 0;
+}
diff --git a/src/server/handler_coke.c b/src/server/handler_coke.c
new file mode 100644
index 0000000000000000000000000000000000000000..f8ed1682786fd255d2d5cb6846ac10b12e42a5d4
--- /dev/null
+++ b/src/server/handler_coke.c
@@ -0,0 +1,87 @@
+/*
+ * OpenDispense 2 
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * handler_coke.c - Coke controller code
+ *
+ * This file is licenced under the 3-clause BSD Licence. See the file
+ * COPYING for full details.
+ */
+#include "common.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <regex.h>
+
+// === IMPORTS ===
+
+// === PROTOTYPES ===
+ int	Coke_InitHandler();
+ int	Coke_CanDispense(int User, int Item);
+ int	Coke_DoDispense(int User, int Item);
+
+// === GLOBALS ===
+tHandler	gCoke_Handler = {
+	"coke",
+	Coke_InitHandler,
+	Coke_CanDispense,
+	Coke_DoDispense
+};
+char	*gsCoke_SerialPort = "/dev/ttyS0";
+ int	giCoke_SerialFD;
+regex_t	gCoke_StatusRegex;
+
+// == CODE ===
+int Coke_InitHandler()
+{
+	giCoke_SerialFD = open(gsCoke_SerialPort, O_RDWR);
+	regexc(&gCoke_StatusRegex, "^$", REG_EXTENDED);
+	return 0;
+}
+
+int Coke_CanDispense(int User, int Item)
+{
+	char	tmp[32];
+	regmatch_t	matches[4];
+
+	// Sanity please
+	if( Item < 0 || Item > 6 )	return -1;
+	
+	// Ask the coke machine
+	sprintf(tmp, "s%i", Item);
+	write(giCoke_SerialFD, tmp, 2);
+
+	// Read the response
+	read(giCoke_SerialFD, tmp, sizeof(tmp)-1);
+	regexec(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches);
+
+	printf("s%i response '%s'\n", Item, tmp);
+
+	return 0;
+}
+
+/**
+ * \brief Actually do a dispense from the coke machine
+ */
+int Coke_DoDispense(int User, int Item)
+{
+	char	tmp[32];
+
+	// Sanity please
+	if( Item < 0 || Item > 6 )	return -1;
+
+	// Dispense
+	sprintf(tmp, "d%i", Item);
+	write(giCoke_SerialFD, tmp, 2);
+
+	// Get status
+	read(giCoke_SerialFD, tmp, sizeof(tmp)-1);
+	regexec(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches);
+	
+	printf("d%i response '%s'\n", Item, tmp);
+
+	return 0;
+}
+
+
diff --git a/src/server/itemdb.c b/src/server/itemdb.c
new file mode 100644
index 0000000000000000000000000000000000000000..0cb932db2bc92dc47a292d3d21df3aff704f8b59
--- /dev/null
+++ b/src/server/itemdb.c
@@ -0,0 +1,152 @@
+/*
+ * 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(&regex, "^([a-zA-Z][a-zA-Z0-9]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED);
+	if( i )
+	{
+		size_t	len = regerror(i, &regex, NULL, 0);
+		char	*errorStr = malloc(len);
+		regerror(i, &regex, 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(&regex, line, 5, matches, 0)) ) {
+			size_t  len = regerror(i, &regex, NULL, 0);
+			char    *errorStr = malloc(len);
+			regerror(i, &regex, 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;
+}
diff --git a/src/server/logging.c b/src/server/logging.c
new file mode 100644
index 0000000000000000000000000000000000000000..51cc8d22c1b2d0a6d041a4292351e65ab9c90749
--- /dev/null
+++ b/src/server/logging.c
@@ -0,0 +1,19 @@
+/*
+ * OpenDispense2
+ *
+ * logging.c - Debug/Logging Routines
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include "common.h"
+
+// === CODE ==
+void Log_Error(const char *Format, ...)
+{
+	
+}
+
+void Log_Info(const char *Format, ...)
+{
+}
+
diff --git a/src/server/main.c b/src/server/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..e789b1306115e4c52701532ace86ec43386fd541
--- /dev/null
+++ b/src/server/main.c
@@ -0,0 +1,74 @@
+/*
+ * OpenDispense 2 
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * main.c - Initialisation Code
+ *
+ * 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 "common.h"
+
+// === IMPORTS ===
+extern void	Init_Cokebank(void);	// cokebank.c
+extern void	Load_Itemlist(void);
+extern void	Server_Start(void);
+extern int	giServer_Port;
+extern char*	gsItemListFile;
+extern char*	gsCoke_SerialPort;
+
+// === GLOBALS ===
+ int	giDebugLevel = 0;
+
+// === CODE ===
+int main(int argc, char *argv[])
+{
+	 int	i;
+	
+	// Parse Arguments
+	for( i = 1; i < argc; i++ )
+	{
+		char	*arg = argv[i];
+		if( arg[0] == '-' && arg[1] != '-')
+		{
+			switch(arg[1])
+			{
+			case 'p':
+				giServer_Port = atoi(argv[++i]);
+				break;
+			case 'd':
+				giDebugLevel = atoi(argv[++i]);
+				break;
+			default:
+				// Usage Error?
+				break;
+			}
+		}
+		else if( arg[0] == '-' && arg[1] == '-' ) {
+			if( strcmp(arg, "--itemsfile") == 0 ) {
+				gsItemListFile = argv[++i];
+			}
+			if( strcmp(arg, "--cokeport") == 0 ) {
+				gsCoke_SerialPort = argv[++i];
+			}
+			else {
+				// Usage error?
+			}
+		}
+		else {
+			// Usage Error?
+		}
+	}
+	
+	Init_Cokebank();
+	
+	Load_Itemlist();
+	
+	Server_Start();
+	
+	return 0;
+}
+
diff --git a/src/server/server.c b/src/server/server.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9ab63e7e786a8d804c9054185bba36067969c79
--- /dev/null
+++ b/src/server/server.c
@@ -0,0 +1,508 @@
+/*
+ * 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;
+}