diff --git a/src/client/Makefile b/src/client/Makefile
index 490e4dc2c5e6790a7aadfae59625179bc46416c9..01a8c92944a4157fbf0ac10b8be8c42b135c8d37 100644
--- a/src/client/Makefile
+++ b/src/client/Makefile
@@ -14,6 +14,9 @@ all: $(BIN)
 clean:
 	$(RM) $(BIN) $(OBJ)
 
+$(BIN): $(OBJ)
+	$(CC) -o $(BIN) $(OBJ) $(LDFLAGS)
+
 %.o: %.c
 	$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
 	$(CC) -M -MT $@ -o $*.d $< $(CPPFLAGS)
diff --git a/src/client/main.c b/src/client/main.c
index 841118160ff48696d57a0e6859196e1d47d12fe9..2bb4f63f6bc600a9a65f304a472914083e3ccd37 100644
--- a/src/client/main.c
+++ b/src/client/main.c
@@ -8,31 +8,308 @@
  * 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>	// isspace
+#include <stdarg.h>
+#include <regex.h>
+
+#include <unistd.h>	// close
+#include <netdb.h>	// gethostbyname
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+// === TYPES ===
+typedef struct sItem {
+	char	*Ident;
+	char	*Desc;
+	 int	Price;
+}	tItem;
+
+// === PROTOTYPES ===
+ int	sendf(int Socket, const char *Format, ...);
+ int	OpenConnection(const char *Host, int Port);
+char	*trim(char *string);
+ int	RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
+void	CompileRegex(regex_t *regex, const char *pattern, int flags);
 
 // === GLOBALS ===
-char	*gsDispenseServer = "martello";
+char	*gsDispenseServer = "localhost";
  int	giDispensePort = 11020;
+tItem	*gaItems;
+ int	giNumItems;
+regex_t	gArrayRegex;
+regex_t	gItemRegex;
 
 // === CODE ===
 int main(int argc, char *argv[])
 {
-	// Connect to server
+	 int	sock;
+	 int	i, responseCode, len;
+	char	buffer[BUFSIZ];
+	
+	// -- Create regular expressions
+	// > Code Type Count ...
+	CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);	//
+	// > Code Type Ident Price Desc
+	CompileRegex(&gItemRegex, "^([0-9]{3})\\s+(.+?)\\s+(.+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
 	
+	// Connect to server
+	sock = OpenConnection(gsDispenseServer, giDispensePort);
+	if( sock < 0 )	return -1;
 
 	// Determine what to do
 	if( argc > 1 )
 	{
 		if( strcmp(argv[1], "acct") == 0 )
 		{
+			// Alter account
+			// List accounts
 			return 0;
 		}
 	}
 
 	// Ask server for stock list
+	send(sock, "ENUM_ITEMS\n", 11, 0);
+	len = recv(sock, buffer, BUFSIZ-1, 0);
+	buffer[len] = '\0';
+	
+	trim(buffer);
+	
+	printf("Output: %s\n", buffer);
+	
+	responseCode = atoi(buffer);
+	if( responseCode != 201 )
+	{
+		fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
+		return -1;
+	}
+	
+	// Get item list
+	{
+		char	*itemType, *itemStart;
+		 int	count;
+		regmatch_t	matches[4];
+		
+		// Expected format: 201 Items <count> <item1> <item2> ...
+		RunRegex(&gArrayRegex, buffer, 4, matches, "Malformed server response");
+		
+		itemType = &buffer[ matches[2].rm_so ];	buffer[ matches[2].rm_eo ] = '\0';
+		count = atoi( &buffer[ matches[3].rm_so ] );
+		
+		// Check array type
+		if( strcmp(itemType, "Items") != 0 ) {
+			// What the?!
+			fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
+				itemType);
+			return -1;
+		}
+		
+		itemStart = &buffer[ matches[3].rm_eo ];
+		
+		gaItems = malloc( count * sizeof(tItem) );
+		
+		for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
+		{
+			char	*next = strchr( ++itemStart, ' ' );
+			if( next )	*next = '\0';
+			gaItems[giNumItems].Ident = strdup(itemStart);
+			itemStart = next;
+		}
+	}
+	
 	
 	// Display the list for the user
+	for( i = 0; i < giNumItems; i ++ )
+	{
+		regmatch_t	matches[6];
+		
+		// Print item Ident
+		printf("%2i %s\t", i, gaItems[i].Ident);
+		
+		// Get item info
+		sendf(sock, "ITEM_INFO %s\n", gaItems[i].Ident);
+		len = recv(sock, buffer, BUFSIZ-1, 0);
+		buffer[len] = '\0';
+		trim(buffer);
+		
+		responseCode = atoi(buffer);
+		if( responseCode != 202 ) {
+			fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
+			return -1;
+		}
+		
+		RunRegex(&gItemRegex, buffer, 6, matches, "Malformed server response");
+		
+		buffer[ matches[3].rm_eo ] = '\0';
+		
+		gaItems[i].Price = atoi( buffer + matches[4].rm_so );
+		gaItems[i].Desc = strdup( buffer + matches[5].rm_so );
+		
+		printf("%3i %s\n", gaItems[i].Price, gaItems[i].Desc);
+	}
+	
+	
 	// and choose what to dispense
+	for(;;)
+	{
+		char	*buf;
+		
+		fgets(buffer, BUFSIZ, stdin);
+		
+		buf = trim(buffer);
+		
+		if( buf[0] == 'q' )	break;
+		
+		i = atoi(buf);
+		
+		printf("buf = '%s', atoi(buf) = %i\n", buf, i);
+		
+		if( i != 0 || buf[0] == '0' )
+		{
+			printf("i = %i\n", i);
+			
+			if( i < 0 || i >= giNumItems ) {
+				printf("Bad item (should be between 0 and %i)\n", giNumItems);
+				continue;
+			}
+			
+			sendf(sock, "DISPENSE %s\n", gaItems[i].Ident);
+			
+			len = recv(sock, buffer, BUFSIZ-1, 0);
+			buffer[len] = '\0';
+			trim(buffer);
+			
+			responseCode = atoi(buffer);
+			switch( responseCode )
+			{
+			case 200:
+				printf("Dispense OK\n");
+				break;
+			case 401:
+				printf("Not authenticated\n");
+				break;
+			case 402:
+				printf("Insufficient balance\n");
+				break;
+			case 406:
+				printf("Bad item name, bug report\n");
+				break;
+			case 500:
+				printf("Item failed to dispense, is the slot empty?\n");
+				break;
+			default:
+				printf("Unknown response code %i\n", responseCode);
+				break;
+			}
+			
+			break;
+		}
+	}
+
+	close(sock);
 
 	return 0;
 }
+
+// === HELPERS ===
+int sendf(int Socket, 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);
+		
+		return send(Socket, buf, len, 0);
+	}
+}
+
+int OpenConnection(const char *Host, int Port)
+{
+	struct hostent	*host;
+	struct sockaddr_in	serverAddr;
+	 int	sock;
+	
+	host = gethostbyname(Host);
+	if( !host ) {
+		fprintf(stderr, "Unable to look up '%s'\n", Host);
+		return -1;
+	}
+	
+	memset(&serverAddr, 0, sizeof(serverAddr));
+	
+	serverAddr.sin_family = AF_INET;	// IPv4
+	// NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
+	serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
+	serverAddr.sin_port = htons(Port);
+	
+	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if( sock < 0 ) {
+		fprintf(stderr, "Failed to create socket\n");
+		return -1;
+	}
+	
+	if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
+		fprintf(stderr, "Failed to connect to server\n");
+		return -1;
+	}
+	
+	return sock;
+}
+
+char *trim(char *string)
+{
+	 int	i;
+	
+	while( isspace(*string) )
+		string ++;
+	
+	for( i = strlen(string); i--; )
+	{
+		if( isspace(string[i]) )
+			string[i] = '\0';
+		else
+			break;
+	}
+	
+	return string;
+}
+
+int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
+{
+	 int	ret;
+	
+	ret = regexec(regex, string, nMatches, matches, 0);
+	if( ret ) {
+		size_t  len = regerror(ret, regex, NULL, 0);
+		char    errorStr[len];
+		regerror(ret, regex, errorStr, len);
+		printf("string = '%s'\n", string);
+		fprintf(stderr, "%s\n%s", errorMessage, errorStr);
+		exit(-1);
+	}
+	
+	return ret;
+}
+
+void CompileRegex(regex_t *regex, const char *pattern, int flags)
+{
+	 int	ret = regcomp(regex, pattern, flags);
+	if( ret ) {
+		size_t	len = regerror(ret, regex, NULL, 0);
+		char    errorStr[len];
+		regerror(ret, regex, errorStr, len);
+		fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
+		exit(-1);
+	}
+}
diff --git a/src/server/server.c b/src/server/server.c
index 7866274b07a54cb248315d4f2eb6870ea864936a..44f3fa768b583ee0ad7c83c5f894912f149de06c 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -283,6 +283,7 @@ char *Server_Cmd_USER(tClient *Client, char *Args)
 	Client->Salt[6] = 0x21 + (rand()&0x3F);
 	Client->Salt[7] = 0x21 + (rand()&0x3F);
 	
+	// TODO: Also send hash type to use, (SHA1 or crypt according to [DAA])
 	// "100 Salt xxxxXXXX\n"
 	ret = strdup("100 SALT xxxxXXXX\n");
 	sprintf(ret, "100 SALT %s\n", Client->Salt);