From da639e3f13ee62627f4e7b29eccfcac70aad681d Mon Sep 17 00:00:00 2001
From: John Hodge <tpg@mutabah.net>
Date: Tue, 27 Dec 2011 17:34:32 +0800
Subject: [PATCH] Adding new config system, template modbus coke, AUTHIDENT in
 client

---
 .gitignore                |   2 +
 src/client/main.c         | 246 ++++++++++++++++----------
 src/server/Makefile       |  10 +-
 src/server/common.h       |  10 ++
 src/server/config.c       | 242 +++++++++++++++++++++++++
 src/server/handler_coke.c | 363 +++++---------------------------------
 src/server/main.c         |  84 ++++-----
 src/server/server.c       |  73 +++++---
 8 files changed, 538 insertions(+), 492 deletions(-)
 create mode 100644 src/server/config.c

diff --git a/.gitignore b/.gitignore
index 69b5969..6e59002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
 *.[od]
 *.so
 *~
+*.swp
 dispsrv
 dispense
+cokebank.log
diff --git a/src/client/main.c b/src/client/main.c
index ce9a9af..595d3e6 100644
--- a/src/client/main.c
+++ b/src/client/main.c
@@ -1314,28 +1314,53 @@ int OpenConnection(const char *Host, int Port)
 	return sock;
 }
 
-/**
- * \brief Authenticate with the server
- * \return Boolean Failure
- */
-int Authenticate(int Socket)
+int Authenticate_AutoAuth(int Socket, const char *Username)
 {
-	struct passwd	*pwd;
 	char	*buf;
 	 int	responseCode;
-	#if ATTEMPT_PASSWORD_AUTH
-	char	salt[32];
-	 int	i;
-	regmatch_t	matches[4];
-	#endif
+	 int	ret = -1;
 	
-	if( gbIsAuthenticated )	return 0;
+	// Attempt automatic authentication
+	sendf(Socket, "AUTOAUTH %s\n", Username);
 	
-	// Get user name
-	pwd = getpwuid( getuid() );
+	// Check if it worked
+	buf = ReadLine(Socket);
+	
+	responseCode = atoi(buf);
+	switch( responseCode )
+	{
+	case 200:	// Autoauth succeeded, return
+		ret = 0;
+		break;
+	
+	case 401:	// Untrusted
+		fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
+		ret = RV_PERMISSIONS;
+		break;
+	case 404:	// Bad Username
+		fprintf(stderr, "Bad Username '%s'\n", Username);
+		ret = RV_INVALID_USER;
+		break;
+	
+	default:
+		fprintf(stderr, "Unkown response code %i from server\n", responseCode);
+		printf("%s\n", buf);
+		ret = RV_UNKNOWN_ERROR;
+		break;;
+	}
+	
+	free(buf);
+	return ret;
+}
+
+int Authenticate_AuthIdent(int Socket)
+{
+	char	*buf;
+	 int	responseCode;
+	 int	ret = -1;
 	
 	// Attempt automatic authentication
-	sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
+	sendf(Socket, "AUTHIDENT\n");
 	
 	// Check if it worked
 	buf = ReadLine(Socket);
@@ -1344,98 +1369,131 @@ int Authenticate(int Socket)
 	switch( responseCode )
 	{
 	case 200:	// Autoauth succeeded, return
-		free(buf);
+		ret = 0;
 		break;
 	
-	case 401:	// Untrusted, attempt password authentication
-		free(buf);
-
-		#if ATTEMPT_PASSWORD_AUTH	
-		sendf(Socket, "USER %s\n", pwd->pw_name);
-		printf("Using username %s\n", pwd->pw_name);
-		
-		buf = ReadLine(Socket);
-		
-		// TODO: Get Salt
-		// Expected format: 100 SALT <something> ...
-		// OR             : 100 User Set
-		RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
-		responseCode = atoi(buf);
-		if( responseCode != 100 ) {
-			fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
-			free(buf);
-			return RV_UNKNOWN_ERROR;	// ERROR
-		}
-		
-		// Check for salt
-		if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
-			// Store it for later
-			memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
-			salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
-		}
-		free(buf);
-		
-		// Give three attempts
-		for( i = 0; i < 3; i ++ )
-		{
-			 int	ofs = strlen(pwd->pw_name)+strlen(salt);
-			char	tmpBuf[42];
-			char	tmp[ofs+20];
-			char	*pass = getpass("Password: ");
-			uint8_t	h[20];
-			
-			// Create hash string
-			// <username><salt><hash>
-			strcpy(tmp, pwd->pw_name);
-			strcat(tmp, salt);
-			SHA1( (unsigned char*)pass, strlen(pass), h );
-			memcpy(tmp+ofs, h, 20);
-			
-			// Hash all that
-			SHA1( (unsigned char*)tmp, ofs+20, h );
-			sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
-				h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
-				h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
-				);
-		
-			// Send password
-			sendf(Socket, "PASS %s\n", tmpBuf);
-			buf = ReadLine(Socket);
-		
-			responseCode = atoi(buf);
-			// Auth OK?
-			if( responseCode == 200 )	break;
-			// Bad username/password
-			if( responseCode == 401 )	continue;
-			
-			fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
-			free(buf);
-			return RV_UNKNOWN_ERROR;
-		}
-		free(buf);
-		if( i == 3 )
-			return RV_INVALID_USER;	// 2 = Bad Password
-		
-		#else
+	case 401:	// Untrusted
 		fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
-		return RV_INVALID_USER;
-		#endif
+		ret = RV_PERMISSIONS;
 		break;
 	
-	case 404:	// Bad Username
-		fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
-		free(buf);
-		return RV_INVALID_USER;
-	
 	default:
 		fprintf(stderr, "Unkown response code %i from server\n", responseCode);
 		printf("%s\n", buf);
+		ret = RV_UNKNOWN_RESPONSE;
+		break;
+	}
+	
+	free(buf);
+
+	return ret;
+}
+
+int Authenticate_Password(int Socket, const char *Username)
+{
+	#if USE_PASSWORD_AUTH
+	char	*buf;
+	 int	responseCode;	
+	char	salt[32];
+	 int	i;
+	regmatch_t	matches[4];
+
+	sendf(Socket, "USER %s\n", Username);
+	printf("Using username %s\n", Username);
+	
+	buf = ReadLine(Socket);
+	
+	// TODO: Get Salt
+	// Expected format: 100 SALT <something> ...
+	// OR             : 100 User Set
+	RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
+	responseCode = atoi(buf);
+	if( responseCode != 100 ) {
+		fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
 		free(buf);
-		return RV_UNKNOWN_ERROR;
+		return RV_UNKNOWN_ERROR;	// ERROR
+	}
+	
+	// Check for salt
+	if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
+		// Store it for later
+		memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
+		salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
+	}
+	free(buf);
+	
+	// Give three attempts
+	for( i = 0; i < 3; i ++ )
+	{
+		 int	ofs = strlen(Username)+strlen(salt);
+		char	tmpBuf[42];
+		char	tmp[ofs+20];
+		char	*pass = getpass("Password: ");
+		uint8_t	h[20];
+		
+		// Create hash string
+		// <username><salt><hash>
+		strcpy(tmp, Username);
+		strcat(tmp, salt);
+		SHA1( (unsigned char*)pass, strlen(pass), h );
+		memcpy(tmp+ofs, h, 20);
+		
+		// Hash all that
+		SHA1( (unsigned char*)tmp, ofs+20, h );
+		sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+			h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
+			h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
+			);
+	
+		// Send password
+		sendf(Socket, "PASS %s\n", tmpBuf);
+		buf = ReadLine(Socket);
+	
+		responseCode = atoi(buf);
+		// Auth OK?
+		if( responseCode == 200 )	break;
+		// Bad username/password
+		if( responseCode == 401 )	continue;
+		
+		fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
+		free(buf);
+		return -1;
 	}
+	free(buf);
+	if( i == 3 )
+		return RV_INVALID_USER;	// 2 = Bad Password
+
+	return 0;
+	#else
+	return RV_INVALID_USER;
+	#endif
+}
+
+/**
+ * \brief Authenticate with the server
+ * \return Boolean Failure
+ */
+int Authenticate(int Socket)
+{
+	struct passwd	*pwd;
+	
+	if( gbIsAuthenticated )	return 0;
 	
+	// Get user name
+	pwd = getpwuid( getuid() );
+
+	// Attempt AUTOAUTH
+	if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
+		;
+	else if( Authenticate_AuthIdent(Socket) == 0 )
+		;
+	else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
+		return RV_INVALID_USER;
+
 	// Set effective user
 	if( gsEffectiveUser ) {
+		char	*buf;
+		 int	responseCode;
 		sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
 		
 		buf = ReadLine(Socket);
diff --git a/src/server/Makefile b/src/server/Makefile
index 6acb88c..300145d 100644
--- a/src/server/Makefile
+++ b/src/server/Makefile
@@ -1,16 +1,18 @@
 # OpenDispense 2
 #
 
-OBJ := main.o server.o logging.o
+INSTALLDIR := /usr/local/opendispense2
+
+OBJ := main.o server.o logging.o config.o
 OBJ += dispense.o itemdb.o
 OBJ += handler_coke.o handler_snack.o handler_door.o
 BIN := ../../dispsrv
 
 DEPFILES := $(OBJ:%.o=%.d)
 
-LINKFLAGS := -g ../../cokebank.so -lutil -lident -Wl,-rpath,. -Wl,-rpath,/usr/local/sbin
+LINKFLAGS := -g ../../cokebank.so -lutil -lident -lmodbus -Wl,-rpath,. -Wl,-rpath,$(INSTALLDIR)
 CPPFLAGS := 
-CFLAGS := -Wall -Wextra -Werror -g
+CFLAGS := -Wall -Wextra -Werror -g -std=gnu99
 
 .PHONY: all clean
 
@@ -20,7 +22,7 @@ clean:
 	$(RM) $(BIN) $(OBJ)
 
 $(BIN): $(OBJ)
-	$(CC) -o $(BIN) $(LINKFLAGS) $(OBJ)
+	$(CC) -o $(BIN) $(OBJ) $(LINKFLAGS)
 
 %.o: %.c
 	$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
diff --git a/src/server/common.h b/src/server/common.h
index 19337e5..004fbf8 100644
--- a/src/server/common.h
+++ b/src/server/common.h
@@ -22,6 +22,8 @@
 
 #define UNUSED(var)	unused__##var __attribute__((__unused__))
 
+#define ASSERT(cnd) do{if(!(cnd)){fprintf(stderr, "ASSERT failed at "__FILE__":"EXPSTR(__LINE__)" - "EXPSTR(cnd)"\n");exit(-1);}}while(0)
+
 // === STRUCTURES ===
 typedef struct sItem	tItem;
 typedef struct sUser	tUser;
@@ -96,4 +98,12 @@ extern int	DispenseUpdateItem(int User, tItem *Item, const char *NewName, int Ne
 extern void	Log_Error(const char *Format, ...);
 extern void	Log_Info(const char *Format, ...);
 
+// --- Config Database ---
+extern void	Config_ParseFile(const char *Filename);
+extern void	Config_AddValue(const char *Key, const char *Value);
+extern int	Config_GetValueCount(const char *KeyName);
+extern const char	*Config_GetValue(const char *KeyName, int Index);
+extern int	Config_GetValue_Bool(const char *KeyName, int Index);
+extern int	Config_GetValue_Int(const char *KeyName, int Index);
+
 #endif
diff --git a/src/server/config.c b/src/server/config.c
new file mode 100644
index 0000000..4630b58
--- /dev/null
+++ b/src/server/config.c
@@ -0,0 +1,242 @@
+/*
+ * OpenDispense 2 
+ * UCC (University [of WA] Computer Club) Electronic Accounting System
+ *
+ * config.c - Configuration file parser
+ *
+ * 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 <regex.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX_LINE_LEN	128
+
+// === TYPES ===
+typedef struct sConfigValue	tConfigValue;
+typedef struct sConfigKey	tConfigKey;
+
+// === STRUCTURES ===
+struct sConfigValue
+{
+	tConfigValue	*Next;
+	char	Data[];
+};
+
+struct sConfigKey
+{
+	tConfigKey	*NextKey;
+	tConfigValue	*FirstValue;
+	tConfigValue	*LastValue;
+	 int	ValueCount;
+	char	KeyName[];
+};
+
+// === PROTOTYPES ===
+void	Config_ParseFile(const char *Filename);
+void	Config_AddValue(const char *Key, const char *Value);
+void	Config_int_AddValueToKey(tConfigKey *Key, const char *Value);
+tConfigKey	*Config_int_GetKey(const char *KeyName, int bCreate);
+ int	Config_GetValueCount(const char *KeyName);
+const char	*Config_GetValue(const char *KeyName, int Index);
+
+// === GLOBALS ===
+tConfigKey	*gConfig;
+
+// === CODE ===
+void Config_ParseFile(const char *Filename)
+{
+	FILE	*fp;
+	char	line[MAX_LINE_LEN];
+	regex_t	regexp_option;
+	regex_t	regexp_empty;
+
+	CompileRegex(&regexp_option, "^\\s*([^ \t]+)\\s+(.+)$", REG_EXTENDED);	//
+	CompileRegex(&regexp_empty, "^\\s*$", REG_EXTENDED);	//
+	
+	fp = fopen(Filename, "r");
+	if(!fp) {
+		fprintf(stderr, "Unable to open config file '%s'\n", Filename);
+		perror("Config_ParseFile");
+		exit(-1);
+	}
+	
+	while( fgets(line, sizeof(line), fp) )
+	{
+		regmatch_t	matches[3];
+
+		// Trim and clean up
+		{
+			 int	i;
+			for( i = 0; line[i]; i ++ )
+			{
+				if( line[i] == '#' || line[i] == ';' ) {
+					line[i] = '\0';
+					break;
+				}
+			}
+			
+			while( i --, isspace(line[i]) )
+				line[i] = 0;
+		}
+		
+				
+		if( regexec(&regexp_empty, line, 1, matches, 0) == 0 )
+			continue ;
+
+		if( RunRegex(&regexp_option, line, 3, matches, "Parsing configuration file") )
+		{
+			fprintf(stderr, "Syntax error\n- %s", line);
+			continue ;
+		}
+		
+		line[matches[1].rm_eo] = 0;
+		line[matches[2].rm_eo] = 0;
+	
+		Config_AddValue(line + matches[1].rm_so, line + matches[2].rm_so);
+	}
+	
+	fclose(fp);
+	regfree(&regexp_option);
+	regfree(&regexp_empty);
+}
+
+void Config_AddValue(const char *Key, const char *Value)
+{
+	tConfigKey	*key;
+	
+	// Find key (creating if needed)
+	key = Config_int_GetKey(Key, 1);
+
+	Config_int_AddValueToKey(key, Value);	
+}
+
+void Config_int_AddValueToKey(tConfigKey *Key, const char *Value)
+{
+	tConfigValue	*newVal;
+	// Create value
+	newVal = malloc(sizeof(tConfigValue) + strlen(Value) + 1);
+	newVal->Next = NULL;
+	strcpy(newVal->Data, Value);
+	
+	#if 1
+	// Add to the end of the key's list
+	if(Key->LastValue)
+		Key->LastValue->Next = newVal;
+	else
+		Key->FirstValue = newVal;
+	Key->LastValue = newVal;
+	#else
+	// Add to the start of the key's list
+	if(Key->LastValue == NULL)
+		Key->LastValue = newVal;
+	newVal->Next = Key->FirstValue;
+	Key->FirstValue = newVal;
+	#endif
+	Key->ValueCount ++;
+}
+
+/**
+ * \brief 
+ */
+tConfigKey *Config_int_GetKey(const char *KeyName, int bCreate)
+{
+	tConfigKey	*key, *prev = NULL;
+	
+	// Search the sorted list of keys
+	for( key = gConfig; key; prev = key, key = key->NextKey )
+	{
+		int cmp = strcmp(key->KeyName, KeyName);
+		if(cmp == 0)	return key;	// Equal, return
+		if(cmp > 0)	break;	// Greater, fast exit
+	}
+	
+	if( bCreate )
+	{
+		// Create new key
+		key = malloc(sizeof(tConfigKey) + strlen(KeyName) + 1);
+		key->FirstValue = NULL;
+		key->LastValue = NULL;
+		key->ValueCount = 0;
+		strcpy(key->KeyName, KeyName);
+		
+		// Append
+		if(prev) {
+			key->NextKey = prev->NextKey;
+			prev->NextKey = key;
+		}
+		else {
+			key->NextKey = gConfig;
+			gConfig = key;
+		}
+	}
+	else
+	{
+		key = NULL;
+	}
+	
+	return key;
+}
+
+int Config_GetValueCount(const char *KeyName)
+{
+	tConfigKey	*key = Config_int_GetKey(KeyName, 0);
+	if(!key)	return 0;
+	
+	return key->ValueCount;
+}
+
+const char *Config_GetValue(const char *KeyName, int Index)
+{
+	tConfigKey	*key;
+	tConfigValue	*val;	
+
+	key = Config_int_GetKey(KeyName, 0);
+	if(!key) {
+		fprintf(stderr, "Unknown key '%s'\n", KeyName);
+		exit(1);
+		return NULL;
+	}
+	
+	if(Index < 0 || Index >= key->ValueCount)	return NULL;
+	
+	for( val = key->FirstValue; Index && val; val = val->Next, Index -- );
+
+	ASSERT(val != NULL);
+	
+	return val->Data;
+}
+
+int Config_GetValue_Bool(const char *KeyName, int Index)
+{
+	const char *val = Config_GetValue(KeyName, Index);
+	if(!val)	return -1;
+	
+	if( atoi(val) == 1 )	return 1;
+	if( val[0] == '0' && val[1] == '\0' )	return 0;
+	
+	if( strcasecmp(val, "true") == 0 )	return 1;
+	if( strcasecmp(val, "false") == 0 )	return 0;
+	
+	if( strcasecmp(val, "yes") == 0 )	return 1;
+	if( strcasecmp(val, "no") == 0 )	return 0;
+	
+	return -1;
+}
+
+int Config_GetValue_Int(const char *KeyName, int Index)
+{
+	 int	tmp;
+	const char *val = Config_GetValue(KeyName, Index);
+	if(!val)	return -1;
+	
+	if( (tmp = atoi(val)) )	return tmp;
+	if( val[0] == '0' && val[1] == '\0' )	return 0;
+	
+	return -1;
+}
+
diff --git a/src/server/handler_coke.c b/src/server/handler_coke.c
index fffa7b6..21d61f6 100644
--- a/src/server/handler_coke.c
+++ b/src/server/handler_coke.c
@@ -13,33 +13,24 @@
 #include "common.h"
 #include <stdio.h>
 #include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <regex.h>
 #include <stdarg.h>
 #include <pthread.h>
+#include <unistd.h>
+#include <modbus/modbus.h>
 
-#define READ_TIMEOUT	2	// 2 seconds for ReadChar
-#define TRACE_COKE	0
+#define MIN_DISPENSE_PERIOD	5
 
-#if TRACE_COKE
-# define TRACE(v...) do{printf("%s: ",__func__);printf(v);}while(0)
-#else
-# define TRACE(...)
-#endif
+// === CONSTANTS ===
+const int	ciCoke_MinPeriod = 5;
+const int	ciCoke_DropBitBase = 0;
+const int	ciCoke_StatusBitBase = 0;
 
 // === IMPORTS ===
 
 // === PROTOTYPES ===
  int	Coke_InitHandler();
- int	Coke_int_GetSlotStatus(char *Buffer, int Slot);
-void	Coke_int_UpdateSlotStatuses(void);
  int	Coke_CanDispense(int User, int Item);
  int	Coke_DoDispense(int User, int Item);
- int	Writef(const char *Format, ...);
- int	WaitForColon();
- int	ReadLine(int len, char *output);
 
 // === GLOBALS ===
 tHandler	gCoke_Handler = {
@@ -48,152 +39,58 @@ tHandler	gCoke_Handler = {
 	Coke_CanDispense,
 	Coke_DoDispense
 };
-char	*gsCoke_SerialPort = "/dev/ttyS0";
- int	giCoke_SerialFD;
-regex_t	gCoke_StatusRegex;
- int	gaCoke_CachedStatus[7];
-pthread_mutex_t	gCoke_Mutex = PTHREAD_MUTEX_INITIALIZER;
+const char	*gsCoke_ModbusAddress = "130.95.13.73";
+modbus_t	*gCoke_Modbus;
 time_t	gtCoke_LastDispenseTime;
+ int	gbCoke_DummyMode = 1;
 
 // == CODE ===
 int Coke_InitHandler()
 {
-	CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED);
-	
-	printf("Connecting to coke machine on '%s'\n", gsCoke_SerialPort);
-	
-	giCoke_SerialFD = InitSerial(gsCoke_SerialPort, 9600);
-	if( giCoke_SerialFD == -1 ) {
-		fprintf(stderr, "ERROR: Unable to open coke serial port ('%s')\n", gsCoke_SerialPort);
-	}
-	else {
-		int i;
-		for( i = 0; i < 7; i ++ )
-			gaCoke_CachedStatus[i] = -1;
-		// Reset the slot names.
-		// - Dunno why this is needed, but the machine plays silly
-		//   sometimes.
-		Writef("\r\n\r\n");
-		WaitForColon();
-		Writef("n0 Slot0\r\n");
-		if( !WaitForColon() )
-		{
-			Writef("n1 Slot1\r\n");
-			WaitForColon();
-			Writef("n2 Slot2\r\n");
-			WaitForColon();
-			Writef("n3 Slot3\r\n");
-			WaitForColon();
-			Writef("n4 Slot4\r\n");
-			WaitForColon();
-			Writef("n5 Slot5\r\n");
-			WaitForColon();
-			Writef("n6 Coke\r\n");
-			WaitForColon();
-			
-			Coke_int_UpdateSlotStatuses();
-		}
-		else
-			fprintf(stderr, "Coke machine timed out.\n");
-	}
-	
-	AddPeriodicFunction(Coke_int_UpdateSlotStatuses);
-	
-	return 0;
-}
+	printf("Connecting to coke machine on '%s'\n", gsCoke_ModbusAddress);
 
-int Coke_int_GetSlotStatus(char *Buffer, int Slot)
-{
-	regmatch_t	matches[4];
-	 int	ret;
-	char	*status;	
-	
-	// Parse status response
-	ret = RunRegex(&gCoke_StatusRegex, Buffer, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
-	if( ret ) {
-		return -1;
+	// Configuable dummy/blank mode (all dispenses succeed)
+	// TODO: Find a better way of handling missing/invalid options
+	if( Config_GetValueCount("coke_dummy_mode") > 0 )
+	{
+		gbCoke_DummyMode = Config_GetValue_Bool("coke_dummy_mode", 0);
+		if(gbCoke_DummyMode == -1)	gbCoke_DummyMode = 0;
 	}
 
-	// Get slot status
-	Buffer[ matches[3].rm_eo ] = '\0';
-	status = &Buffer[ matches[3].rm_so ];
-	
-	TRACE("Machine responded slot %i status '%s'\n", Slot, status);
-
-	if( strcmp(status, "full") == 0 ) {
-		gaCoke_CachedStatus[Slot] = 0;	// 0: Avaliiable
-		return 0;
-	}
-	else {
-		gaCoke_CachedStatus[Slot] = 1;	// 1: Empty
-		return 1;
+	// Open modbus
+	modbus_new_tcp(gsCoke_ModbusAddress, 502);
+	if( !gCoke_Modbus )
+	{
+		perror("coke - modbus_new_tcp");
 	}
-}
-
-/**
- * \brief Update the status of all coke slots
- * \note Uses goto to reduce the chance of the lock being kept
- */
-void Coke_int_UpdateSlotStatuses(void)
-{
-	 int	i, len;
-	char	tmp[40];
-	
-	if( giCoke_SerialFD == -1 )	return ;
-	
-	pthread_mutex_lock(&gCoke_Mutex);
-	
-	while( ReadLine(sizeof tmp, tmp) >= 0 )	;
-	TRACE("send d7\n");
-	Writef("d7\r\n");	// Update slot statuses
-	if( WaitForColon() )	goto ret;
-	TRACE("send s\n");
-	Writef("s\r\n");
-	do {
-		i = ReadLine(sizeof tmp, tmp);	// Read back what we just said
-		if( i == -1 ) {
-			TRACE("Eat read failed");
-			goto ret;
-		}
-	} while(tmp[0] != ':' && tmp[1] != 's');
-	
-	for( i = 0; i <= 6; i ++ )
+	else
 	{
-		// Read until non-blank line
-		while( (len = ReadLine(sizeof tmp, tmp)) == 0 )	;
-		if( len == -1 ) {
-			TRACE("Read failed on slot %i\n", i);
-			goto ret;	// I give up :(
+		if( modbus_connect(gCoke_Modbus) )
+		{
+			perror("coke - modbus_connect");
 		}
-		TRACE("tmp = '%s'\n", tmp);
-		Coke_int_GetSlotStatus(tmp, i);
-	}
-	// Eat blank line
-	len = ReadLine(sizeof tmp, tmp);
-	if( len == -1 ) {
-		TRACE("Read failed on blank line\n");
 	}
 
-	TRACE("Updated\n");
-
-ret:
-	pthread_mutex_unlock(&gCoke_Mutex);
+	return 0;
 }
 
 int Coke_CanDispense(int UNUSED(User), int Item)
 {
+	uint8_t	status;
 	// Sanity please
-	if( Item < 0 || Item > 6 )	return -1;	// -EYOURBAD
+	if( Item < 0 || Item > 6 )	return -1;
+
+	// Check for 'dummy' mode
+	if( gbCoke_DummyMode )
+		return 0;
 	
 	// Can't dispense if the machine is not connected
-	if( giCoke_SerialFD == -1 )
+	if( !gCoke_Modbus )
 		return -2;
 
-	// HACK!
-	// 2011-10-21: The sensors in slot 1 and 3 a bad, just ignore the sensor result
-	if( Item == 1 || Item == 3 )	return 0;
+	modbus_read_bits(gCoke_Modbus, ciCoke_StatusBitBase + Item, 1, &status);
 
-	return gaCoke_CachedStatus[Item];
+	return status == 0;
 }
 
 /**
@@ -201,195 +98,29 @@ int Coke_CanDispense(int UNUSED(User), int Item)
  */
 int Coke_DoDispense(int UNUSED(User), int Item)
 {
-	char	tmp[32];
-	 int	ret, len;
-
 	// Sanity please
 	if( Item < 0 || Item > 6 )	return -1;
 
+	// Check for 'dummy' mode
+	if( gbCoke_DummyMode )
+		return 0;
+	
 	// Can't dispense if the machine is not connected
-	if( giCoke_SerialFD == -1 )
+	if( !gCoke_Modbus )
 		return -2;
 
 	// Make sure there are not two dispenses within n seconds
-	if( time(NULL) - gtCoke_LastDispenseTime < 10 )
+	if( time(NULL) - gtCoke_LastDispenseTime < ciCoke_MinPeriod )
 	{
-		printf("Wait %li seconds?\n", 10 - (time(NULL) - gtCoke_LastDispenseTime));
-		sleep( 10 - (time(NULL) - gtCoke_LastDispenseTime) );
+		 int	delay = ciCoke_MinPeriod - (time(NULL) - gtCoke_LastDispenseTime);
+		printf("Wait %i seconds?\n", delay);
+		sleep( delay );
 		printf("wait done\n");
 	}
 	
-	// LOCK
-	pthread_mutex_lock(&gCoke_Mutex);
-	
-	TRACE("flushing input\n");
-	
-
-	// Wait for prompt
-	ret = 0;
-	while( WaitForColon() && ret < 3 )
-	{
-		// Flush the input buffer
-		char	tmpbuf[512];
-		read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
-		TRACE("sending 'd7'\n");
-		Writef("d7\r\n");
-		ret ++;
-	}
-	if( ret == 3 )
-	{
-		TRACE("timed out\n");
-		pthread_mutex_unlock(&gCoke_Mutex);
-		return -1;
-	}
-
-	TRACE("sending 'd%i'\n", Item);
-	// Dispense
-	Writef("d%i\r\n", Item);
-	
-	// Read empty lines and echo-backs
-	do {
-		ret = ReadLine(sizeof(tmp)-1, tmp);
-		if( ret == -1 ) {
-			pthread_mutex_unlock(&gCoke_Mutex);
-			return -1;
-		}
-		TRACE("read %i '%s'\n", ret, tmp);
-	} while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' );
-
-	WaitForColon();	// Eat up rest of response
-	
-	TRACE("done\n");
-
-	// TODO: Regex instead?
-	if( strcmp(tmp, "ok") == 0 ) {
-		// We think dispense worked
-		// - The machine returns 'ok' if an empty slot is dispensed, even if
-		//   it doesn't actually try to dispense (no sound)
-		ret = 0;
-	}
-	else {
-		printf("Coke_DoDispense: Machine returned unknown value '%s'\n", tmp);
-		ret = -1;
-	}
-	
-	TRACE("Updating slot status\n");
-	
-	// Update status
-	WaitForColon();
-	Writef("s%i\r\n", Item);
-	len = ReadLine(sizeof tmp, tmp);
-	if(len == -1)	gaCoke_CachedStatus[Item] = -1;
-	Coke_int_GetSlotStatus(tmp, Item);
-	{
-		char buf[512];
-		read(giCoke_SerialFD, buf, 512);	// Flush
-	}
-
-	gtCoke_LastDispenseTime = time(NULL);
-	
-	// Release and return
-	pthread_mutex_unlock(&gCoke_Mutex);
-	
-	//return ret;
-	// HACK!!!
-	return 0;
-}
-
-char ReadChar()
-{
-	fd_set	readfs;
-	char	ch = 0;
-	 int	ret;
-	struct timeval	timeout;
-	
-	timeout.tv_sec = READ_TIMEOUT;
-	timeout.tv_usec = 0;
-	
-	FD_ZERO(&readfs);
-	FD_SET(giCoke_SerialFD, &readfs);
-	
-	ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout);
-	if( ret == 0 ) {
-		fprintf(stderr, "ReadChar: Timeout of %is expired\n", READ_TIMEOUT);
-		return 0;	// Timeout
-	}
-	if( ret != 1 ) {
-		printf("ReadChar: select return %i\n", ret);
-		return 0;
-	}
-	
-	ret = read(giCoke_SerialFD, &ch, 1);
-	if( ret != 1 ) {
-		printf("ReadChar: ret != 1 (%i)\n", ret);
-		return 0;
-	}
-	
-	return ch;
-}
-
-int Writef(const char *Format, ...)
-{
-	va_list	args;
-	 int	len;
-	
-	va_start(args, Format);
-	len = vsnprintf(NULL, 0, Format, args);
-	va_end(args);
-	
-	{
-		char	buf[len+1];
-		va_start(args, Format);
-		vsnprintf(buf, len+1, Format, args);
-		va_end(args);
-		
-		#if DEBUG
-		printf("Writef: %s", buf);
-		#endif
-		
-		return write(giCoke_SerialFD, buf, len);
-	}
-	
-}
-
-int WaitForColon()
-{
-	fd_set	readfs;
-	char	ch = 0;
-	
-	FD_SET(giCoke_SerialFD, &readfs);
-	
-	while( (ch = ReadChar()) != ':' && ch != 0);
-	
-	if( ch == 0 )	return -1;	// Timeout
+	// Dispense (with locking)
+	modbus_write_bit(gCoke_Modbus, ciCoke_DropBitBase + Item, 1);
 	
 	return 0;
 }
 
-int ReadLine(int len, char *output)
-{
-	char	ch;
-	 int	i = 0;
-	
-	for(;;)
-	{
-		ch = ReadChar();
-			
-		if( i < len )
-			output[i++] = ch;
-		
-		if( ch == '\0' ) {
-			break;
-		}
-		if( ch == '\n' || ch == '\r' ) {
-			if( i < len )
-				output[--i] = '\0';
-			break;
-		}
-	}
-
-	if( !ch ) 	return -1;
-	return i;
-}
-
-
diff --git a/src/server/main.c b/src/server/main.c
index 590e197..c2ef55c 100644
--- a/src/server/main.c
+++ b/src/server/main.c
@@ -27,17 +27,16 @@ extern void	Load_Itemlist(void);
 extern void	Server_Start(void);
 extern int	gbServer_RunInBackground;
 extern int	giServer_Port;
-extern char	*gsItemListFile;
-extern char	*gsCoke_SerialPort;
-extern char	*gsSnack_SerialPort;
-extern char	*gsDoor_SerialPort;
+extern const char	*gsItemListFile;
+extern const char	*gsCoke_ModbusAddress;
+extern const char	*gsDoor_SerialPort;
 
 // === PROTOTYPES ===
 void	*Periodic_Thread(void *Unused);
 
 // === GLOBALS ===
  int	giDebugLevel = 0;
-char	*gsCokebankPath = "cokebank.db";
+const char	*gsCokebankPath = "cokebank.db";
 // - Functions called every 20s (or so)
 #define ciMaxPeriodics	10
 struct sPeriodicCall {
@@ -54,24 +53,16 @@ void sigint_handler()
 void PrintUsage(const char *progname)
 {
 	fprintf(stderr, "Usage: %s\n", progname);
-	fprintf(stderr, "  -p    Set server port (default 11020)\n");
 	fprintf(stderr, "  -d    Set debug level (0 - 2, default 0)\n");
-	fprintf(stderr, "  --itemsfile\n");
-	fprintf(stderr, "        Set debug level (0 - 2, default 0)\n");
-	fprintf(stderr, "  --cokeport\n");
-	fprintf(stderr, "        Coke machine serial port (Default \"/dev/ttyS0\")\n");
-	fprintf(stderr, "  --doorport\n");
-	fprintf(stderr, "        Door modem/relay serial port (Default \"/dev/ttyS3\")\n");
-	fprintf(stderr, "  --cokebank\n");
-	fprintf(stderr, "        Coke bank database file (Default \"cokebank.db\")\n");
 	fprintf(stderr, "  --[dont-]daemonise\n");
-	fprintf(stderr, "        Run (or explicitly don't) the server disconnected from the terminal\n");
+	fprintf(stderr, "        Run (or explicitly don't run) the server disconnected from the terminal\n");
 }
 
 int main(int argc, char *argv[])
 {
 	 int	i;
-	
+	const char	*config_file = "dispsrv.conf";
+
 	// Parse Arguments
 	for( i = 1; i < argc; i++ )
 	{
@@ -80,62 +71,56 @@ int main(int argc, char *argv[])
 		{
 			switch(arg[1])
 			{
-			case 'p':
+			case 'f':
 				if( i + 1 >= argc )	return -1;
-				giServer_Port = atoi(argv[++i]);
+				config_file = argv[++i];
 				break;
 			case 'd':
 				if( i + 1 >= argc )	return -1;
-				giDebugLevel = atoi(argv[++i]);
+				Config_AddValue("debug_level", argv[++i]);
+				giDebugLevel = atoi(argv[i]);
 				break;
-			case 'D':
-				gbServer_RunInBackground = 1;
-				return -1;
 			default:
-				// Usage Error?
+				// Usage Error
 				PrintUsage(argv[0]);
 				return -1;
 			}
 		}
-		else if( arg[0] == '-' && arg[1] == '-' ) {
-			if( strcmp(arg, "--itemsfile") == 0 ) {
-				if( i + 1 >= argc )	return -1;
-				gsItemListFile = argv[++i];
-			}
-			else if( strcmp(arg, "--cokeport") == 0 ) {
-				if( i + 1 >= argc )	return -1;
-				gsCoke_SerialPort = argv[++i];
-			}
-			else if( strcmp(arg, "--snackport") == 0 ) {
-				if( i + 1 >= argc )	return -1;
-				gsSnack_SerialPort = argv[++i];
-			}
-			else if( strcmp(arg, "--doorport") == 0 ) {
-				if( i + 1 >= argc )	return -1;
-				gsDoor_SerialPort = argv[++i];
-			}
-			else if( strcmp(arg, "--cokebank") == 0 ) {
+		else if( arg[0] == '-' && arg[1] == '-' )
+		{
+			if( strcmp(arg, "--configfile") == 0 ) {
 				if( i + 1 >= argc )	return -1;
-				gsCokebankPath = argv[++i];
+				config_file = argv[++i];
 			}
 			else if( strcmp(arg, "--daemonise") == 0 ) {
-				gbServer_RunInBackground = 1;
+				Config_AddValue("daemonise", "true");
 			}
 			else if( strcmp(arg, "--dont-daemonise") == 0 ) {
-				gbServer_RunInBackground = 0;
+				Config_AddValue("daemonise", "false");
 			}
 			else {
-				// Usage error?
+				// Usage error
 				PrintUsage(argv[0]);
 				return -1;
 			}
 		}
-		else {
-			// Usage Error?
+		else
+		{
+			// Usage Error
 			PrintUsage(argv[0]);
 			return -1;
 		}
 	}
+
+	Config_ParseFile( config_file );
+
+	// Parse config values
+	gbServer_RunInBackground = Config_GetValue_Bool("daemonise", 0);
+	gsCokebankPath       = Config_GetValue("cokebank_database", 0);
+	gsDoor_SerialPort    = Config_GetValue("door_serial_port", 0);
+	gsCoke_ModbusAddress = Config_GetValue("coke_modbus_address", 0);
+	giServer_Port        = Config_GetValue_Int("server_port", 0);
+	gsItemListFile       = Config_GetValue("items_file", 0);
 	
 	signal(SIGINT, sigint_handler);
 	signal(SIGTERM, sigint_handler);
@@ -157,10 +142,9 @@ int main(int argc, char *argv[])
 	return 0;
 }
 
-void *Periodic_Thread(void *Unused)
+void *Periodic_Thread(void *Unused __attribute__((unused)))
 {
 	 int	i;
-	Unused = NULL;	// quiet, gcc
 	
 	for( ;; )
 	{
@@ -220,7 +204,7 @@ void CompileRegex(regex_t *regex, const char *pattern, int flags)
 		size_t	len = regerror(ret, regex, NULL, 0);
 		char    errorStr[len];
 		regerror(ret, regex, errorStr, len);
-		fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
+		fprintf(stderr, "Regex compilation failed - %s\n%s\n", errorStr, pattern);
 		exit(-1);
 	}
 }
diff --git a/src/server/server.c b/src/server/server.c
index b7ca068..05ee547 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -44,7 +44,8 @@ typedef struct sClient
 	 int	Socket;	// Client socket ID
 	 int	ID;	// Client ID
 	 
-	 int	bIsTrusted;	// Is the connection from a trusted host/port
+	 int	bTrustedHost;
+	 int	bCanAutoAuth;	// Is the connection from a trusted host/port
 	
 	char	*Username;
 	char	Salt[9];
@@ -57,7 +58,7 @@ typedef struct sClient
 // === PROTOTYPES ===
 void	Server_Start(void);
 void	Server_Cleanup(void);
-void	Server_HandleClient(int Socket, int bTrusted);
+void	Server_HandleClient(int Socket, int bTrustedHost, int bRootPort);
 void	Server_ParseClientCommand(tClient *Client, char *CommandString);
 // --- Commands ---
 void	Server_Cmd_USER(tClient *Client, char *Args);
@@ -118,6 +119,8 @@ const struct sClientCommand {
  int	gbServer_RunInBackground = 0;
 char	*gsServer_LogFile = "/var/log/dispsrv.log";
 char	*gsServer_ErrorLog = "/var/log/dispsrv.err";
+ int	giServer_NumTrustedHosts;
+struct in_addr	*gaServer_TrustedHosts;
 // - State variables
  int	giServer_Socket;	// Server socket
  int	giServer_NextClientID = 1;	// Debug client ID
@@ -132,6 +135,19 @@ void Server_Start(void)
 	 int	client_socket;
 	struct sockaddr_in	server_addr, client_addr;
 
+	// Parse trusted hosts list
+	giServer_NumTrustedHosts = Config_GetValueCount("trusted_host");
+	gaServer_TrustedHosts = malloc(giServer_NumTrustedHosts * sizeof(*gaServer_TrustedHosts));
+	for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
+	{
+		const char	*addr = Config_GetValue("trusted_host", i);
+		
+		if( inet_aton(addr, &gaServer_TrustedHosts[i]) == 0 ) {
+			fprintf(stderr, "Invalid IP address '%s'\n", addr);
+			continue ;
+		}
+	}
+
 	atexit(Server_Cleanup);
 	// Ignore SIGPIPE (stops crashes when the client exits early)
 	signal(SIGPIPE, SIG_IGN);
@@ -210,6 +226,7 @@ void Server_Start(void)
 	{
 		uint	len = sizeof(client_addr);
 		 int	bTrusted = 0;
+		 int	bRootPort = 0;
 		
 		// Accept a connection
 		client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
@@ -241,9 +258,22 @@ void Server_Start(void)
 		// Doesn't matter what, localhost is trusted
 		if( ntohl( client_addr.sin_addr.s_addr ) == 0x7F000001 )
 			bTrusted = 1;
-		
-		// Trusted Connections
+	
+		// Check if the host is on the trusted list	
+		for( int i = 0; i < giServer_NumTrustedHosts; i ++ )
+		{
+			if( memcmp(&client_addr.sin_addr, &gaServer_TrustedHosts[i], sizeof(struct in_addr)) == 0 )
+			{
+				bTrusted = 1;
+				break;
+			}
+		}
+
+		// Root port (can AUTOAUTH if also a trusted machine
 		if( ntohs(client_addr.sin_port) < 1024 )
+			bRootPort = 1;
+		
+		#if 0
 		{
 			// TODO: Make this runtime configurable
 			switch( ntohl( client_addr.sin_addr.s_addr ) )
@@ -264,9 +294,10 @@ void Server_Start(void)
 				break;
 			}
 		}
+		#endif
 		
 		// TODO: Multithread this?
-		Server_HandleClient(client_socket, bTrusted);
+		Server_HandleClient(client_socket, bTrusted, bRootPort);
 		
 		close(client_socket);
 	}
@@ -284,7 +315,7 @@ void Server_Cleanup(void)
  * \param Socket	Client socket number/handle
  * \param bTrusted	Is the client trusted?
  */
-void Server_HandleClient(int Socket, int bTrusted)
+void Server_HandleClient(int Socket, int bTrusted, int bRootPort)
 {
 	char	inbuf[INPUT_BUFFER_SIZE];
 	char	*buf = inbuf;
@@ -297,7 +328,8 @@ void Server_HandleClient(int Socket, int bTrusted)
 	// Initialise Client info
 	clientInfo.Socket = Socket;
 	clientInfo.ID = giServer_NextClientID ++;
-	clientInfo.bIsTrusted = bTrusted;
+	clientInfo.bTrustedHost = bTrusted;
+	clientInfo.bCanAutoAuth = bTrusted && bRootPort;
 	clientInfo.EffectiveUID = -1;
 	
 	// Read from client
@@ -492,7 +524,7 @@ void Server_Cmd_AUTOAUTH(tClient *Client, char *Args)
 	}
 	
 	// Check if trusted
-	if( !Client->bIsTrusted ) {
+	if( !Client->bCanAutoAuth ) {
 		if(giDebugLevel)
 			Debug(Client, "Untrusted client attempting to AUTOAUTH");
 		sendf(Client->Socket, "401 Untrusted\n");
@@ -548,35 +580,20 @@ void Server_Cmd_AUTHIDENT(tClient *Client, char *Args)
 	char	*username;
 	 int	userflags;
 	const int ident_timeout = 5;
-	socklen_t len;
-	struct sockaddr_in client_addr;
-	uint32_t  client_ip;
 
 	if( Args != NULL && strlen(Args) ) {
 		sendf(Client->Socket, "407 AUTHIDENT takes no arguments\n");
 		return ;
 	}
 
-	// Check if trusted (only works with INET sockets at present)
-	len = sizeof(client_addr);
-	if( getpeername(Client->Socket, (struct sockaddr*)&client_addr, &len) == -1 ) {
-		Debug(Client, "500 getpeername() failed\n");
-		perror("Getting AUTHIDENT peer name");
-		sendf(Client->Socket, "500 getpeername() failed\n");
+	// Check if trusted
+	if( !Client->bTrustedHost ) {
+		if(giDebugLevel)
+			Debug(Client, "Untrusted client attempting to AUTHIDENT");
+		sendf(Client->Socket, "401 Untrusted\n");
 		return ;
 	}
 
-	client_ip = client_addr.sin_addr.s_addr;
-	if(giDebugLevel >= 2) {
-		Debug(Client, "client_ip = %x, ntohl(client_ip) = %x", client_ip, ntohl(client_ip));
-	}
-	if( ntohl(client_ip) != 0x7F000001 && (ntohl(client_ip) & IDENT_TRUSTED_NETMASK) != IDENT_TRUSTED_NETWORK ) {
-			if(giDebugLevel)
-				Debug(Client, "Untrusted client attempting to AUTHIDENT");
-			sendf(Client->Socket, "401 Untrusted\n");
-			return ;
-	}
-
 	// Get username via IDENT
 	username = ident_id(Client->Socket, ident_timeout);
 	if( !username ) {
-- 
GitLab