From ca788d1ed62b100c213fa9de432f969d85b136e9 Mon Sep 17 00:00:00 2001
From: John Hodge <tpg@mutabah.net>
Date: Thu, 6 Jan 2011 12:25:45 +0800
Subject: [PATCH] User enumeration implemented

- TODO: Implement <min balance> and <max balance>
- Updated protocol spec for extended ENUM_USERS
- Client Dispense_EnumUsers implemented
 > Changed padding in Dispense_ShowUser
- Fixed client's ReadLine
 > Was not using the remaining data in the buffer
- Created a common "cokebank.h" for server
 > Moved pseudo-account names into this file
- Implemented ENUM_USERS at server side
 > Copied sendf to the server to improve response times (and memory
   usage)
---
 proto.txt                   |  7 ++--
 src/client/main.c           | 73 ++++++++++++++++++++++++++++++-----
 src/cokebank_basic/bank.c   | 18 +++------
 src/cokebank_basic/common.h |  4 +-
 src/cokebank_basic/main.c   |  6 +++
 src/server/common.h         |  8 +---
 src/server/dispense.c       |  6 +--
 src/server/server.c         | 76 ++++++++++++++++++++++++++++++++++++-
 8 files changed, 161 insertions(+), 37 deletions(-)

diff --git a/proto.txt b/proto.txt
index 2565ad0..ac2d139 100644
--- a/proto.txt
+++ b/proto.txt
@@ -7,7 +7,7 @@ All server responses are on one line and are prefixed by a three digit response
 == Response Codes ==
 100	Information
 200	Command succeeded, no extra information
-201	Command succeeded, array follows (<length> <items> <items> ...)
+201	Command succeeded, multiple lines follow (<length>)
 202	Command succeeded, per-command format
 400	Unknown Command
 401	Not Authenticated (or Authentication failure)
@@ -57,11 +57,12 @@ s	201 Items <count> <item_id> <item_id> ...\n
 c	ITEM_INFO <item_id>\n
 s	202 Item <item_id> <price> <description>\n
 --- Get Users' Balances ---
-c	ENUM_USERS[ <max balance>]\n
+ <max balance> and <min balance> can be '-' to indicate "none"
+c	ENUM_USERS[ <min balance> [<max balance>]]\n
 s	201 Users <count>\n
 s	202 User <username> <balance> <flags>\n
     ...
-s   200 List End
+s   200 List End\n
 --- Get a User's Balance ---
 c	USER_INFO\n
 s	202 User <username> <balance> <flags>\n
diff --git a/src/client/main.c b/src/client/main.c
index d4af6dd..e1de523 100644
--- a/src/client/main.c
+++ b/src/client/main.c
@@ -25,6 +25,7 @@
 #include <openssl/sha.h>	// SHA1
 
 #define	USE_NCURSES_INTERFACE	0
+#define DEBUG_TRACE_SERVER	0
 
 // === TYPES ===
 typedef struct sItem {
@@ -855,8 +856,54 @@ int Dispense_SetBalance(int Socket, const char *Username, int Ammount, const cha
 
 int Dispense_EnumUsers(int Socket)
 {
-	printf("TODO: Dispense_EnumUsers\n");
-	return -1;
+	char	*buf;
+	 int	responseCode;
+	 int	nUsers;
+	regmatch_t	matches[4];
+	
+	sendf(Socket, "ENUM_USERS\n");
+	buf = ReadLine(Socket);
+	responseCode = atoi(buf);
+	
+	switch(responseCode)
+	{
+	case 201:	break;	// Ok, length follows
+	
+	default:
+		fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
+		free(buf);
+		return -1;
+	}
+	
+	// Get count (not actually used)
+	RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
+	nUsers = atoi( buf + matches[3].rm_so );
+	printf("%i users returned\n", nUsers);
+	
+	// Free string
+	free(buf);
+	
+	// Read returned users
+	do {
+		buf = ReadLine(Socket);
+		responseCode = atoi(buf);
+		
+		if( responseCode != 202 )	break;
+		
+		_PrintUserLine(buf);
+		free(buf);
+	} while(responseCode == 202);
+	
+	// Check final response
+	if( responseCode != 200 ) {
+		fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
+		free(buf);
+		return -1;
+	}
+	
+	free(buf);
+	
+	return 0;
 }
 
 int Dispense_ShowUser(int Socket, const char *Username)
@@ -913,7 +960,7 @@ void _PrintUserLine(const char *Line)
 		flags[flagsLen] = '\0';
 		
 		bal = atoi(Line + matches[4].rm_so);
-		printf("%-15s: $%i.%02i (%s)\n", username, bal/100, bal%100, flags);
+		printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, bal%100, flags);
 	}
 }
 
@@ -924,12 +971,13 @@ char *ReadLine(int Socket)
 {
 	static char	buf[BUFSIZ];
 	static int	bufPos = 0;
+	static int	bufValid = 0;
 	 int	len;
 	char	*newline = NULL;
 	 int	retLen = 0;
 	char	*ret = malloc(10);
 	
-	#if DBG_TRACE_SERVER
+	#if DEBUG_TRACE_SERVER
 	printf("ReadLine: ");
 	#endif
 	fflush(stdout);
@@ -938,8 +986,13 @@ char *ReadLine(int Socket)
 	
 	while( !newline )
 	{
-		len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
-		buf[bufPos+len] = '\0';
+		if( bufValid ) {
+			len = bufValid;
+		}
+		else {
+			len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
+			buf[bufPos+len] = '\0';
+		}
 		
 		newline = strchr( buf+bufPos, '\n' );
 		if( newline ) {
@@ -951,12 +1004,14 @@ char *ReadLine(int Socket)
 		strcat( ret, buf+bufPos );
 		
 		if( newline ) {
-			bufPos += newline - (buf+bufPos) + 1;
+			 int	newLen = newline - (buf+bufPos) + 1;
+			bufValid = len - newLen;
+			bufPos += newLen;
 		}
 		if( len + bufPos == BUFSIZ - 1 )	bufPos = 0;
 	}
 	
-	#if DBG_TRACE_SERVER
+	#if DEBUG_TRACE_SERVER
 	printf("%i '%s'\n", retLen, ret);
 	#endif
 	
@@ -978,7 +1033,7 @@ int sendf(int Socket, const char *Format, ...)
 		vsnprintf(buf, len+1, Format, args);
 		va_end(args);
 		
-		#if DBG_TRACE_SERVER
+		#if DEBUG_TRACE_SERVER
 		printf("sendf: %s", buf);
 		#endif
 		
diff --git a/src/cokebank_basic/bank.c b/src/cokebank_basic/bank.c
index 1b356e7..86a94b1 100644
--- a/src/cokebank_basic/bank.c
+++ b/src/cokebank_basic/bank.c
@@ -15,14 +15,6 @@
 #include <pwd.h>
 #include "common.h"
 
-enum {
-	FLAG_TYPEMASK    = 0x03,
-	USER_TYPE_NORMAL = 0x00,
-	USER_TYPE_COKE   = 0x01,
-	USER_TYPE_WHEEL  = 0x02,
-	USER_TYPE_GOD    = 0x03
-};
-
 // === PROTOTYPES ===
 static int	GetUnixID(const char *Username);
 
@@ -131,7 +123,7 @@ int Bank_AddUser(const char *Username)
 	gaBank_Users[giBank_NumUsers].Balance = 0;
 	gaBank_Users[giBank_NumUsers].Flags = 0;
 	
-	if( strcmp(Username, ">liability") == 0 ) {
+	if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {
 		gaBank_Users[giBank_NumUsers].Flags = USER_TYPE_GOD;	// No minium
 	}
 	else if( strcmp(Username, "root") == 0 ) {
@@ -160,10 +152,10 @@ char *Bank_GetUserName(int ID)
 		return NULL;
 	
 	if( gaBank_Users[ID].UnixID == -1 )
-		return strdup(">sales");
+		return strdup(COKEBANK_SALES_ACCT);
 
 	if( gaBank_Users[ID].UnixID == -2 )
-		return strdup(">liability");
+		return strdup(COKEBANK_DEBT_ACCT);
 
 	pwd = getpwuid(gaBank_Users[ID].UnixID);
 	if( !pwd )	return NULL;
@@ -175,10 +167,10 @@ static int GetUnixID(const char *Username)
 {
 	 int	uid;
 
-	if( strcmp(Username, ">sales") == 0 ) {	// Pseudo account that sales are made into
+	if( strcmp(Username, COKEBANK_SALES_ACCT) == 0 ) {	// Pseudo account that sales are made into
 		uid = -1;
 	}
-	else if( strcmp(Username, ">liability") == 0 ) {	// Pseudo acount that money is added from
+	else if( strcmp(Username, COKEBANK_DEBT_ACCT) == 0 ) {	// Pseudo acount that money is added from
 		uid = -2;
 	}
 	else {
diff --git a/src/cokebank_basic/common.h b/src/cokebank_basic/common.h
index ce6854f..f7744dd 100644
--- a/src/cokebank_basic/common.h
+++ b/src/cokebank_basic/common.h
@@ -2,7 +2,7 @@
  * OpenDispense 2 
  * UCC (University [of WA] Computer Club) Electronic Accounting System
  *
- * cokebank.c - Coke-Bank management
+ * cokebank_basic/common.h - Coke-Bank management
  *
  * This file is licenced under the 3-clause BSD Licence. See the file COPYING
  * for full details.
@@ -10,6 +10,8 @@
 #ifndef _COKEBANK_COMMON_H_
 #define _COKEBANK_COMMON_H_
 
+#include "../cokebank.h"
+
 typedef struct sUser {
 	 int	UnixID;
 	 int	Balance;
diff --git a/src/cokebank_basic/main.c b/src/cokebank_basic/main.c
index b8aba3f..9892231 100644
--- a/src/cokebank_basic/main.c
+++ b/src/cokebank_basic/main.c
@@ -20,6 +20,7 @@ void	Init_Cokebank(const char *Argument);
 char	*GetUserName(int User);
  int	GetUserID(const char *Username); 
  int	GetUserAuth(const char *Username, const char *Password);
+ int	GetMaxID(void);
 
 // === GLOBALS ===
 FILE	*gBank_LogFile;
@@ -109,3 +110,8 @@ int GetUserID(const char *Username)
 	return ret;
 }
 
+int GetMaxID(void)
+{
+	return giBank_NumUsers;
+}
+
diff --git a/src/server/common.h b/src/server/common.h
index 7020dc8..106597e 100644
--- a/src/server/common.h
+++ b/src/server/common.h
@@ -10,6 +10,7 @@
 #define _COMMON_H_
 
 #include <regex.h>
+#include "../cokebank.h"
 
 // === CONSTANTS ===
 #define	DEFAULT_CONFIG_FILE	"/etc/opendispense/main.cfg"
@@ -83,11 +84,4 @@ extern int	DispenseAdd(int User, int ByUser, int Ammount, const char *ReasonGive
 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	GetFlags(int User);
-extern int	GetBalance(int User);
-extern char	*GetUserName(int User);
-extern int	GetUserID(const char *Username);
-
 #endif
diff --git a/src/server/dispense.c b/src/server/dispense.c
index ae93793..77f07ac 100644
--- a/src/server/dispense.c
+++ b/src/server/dispense.c
@@ -27,7 +27,7 @@ int DispenseItem(int User, tItem *Item)
 	// Subtract the balance
 	reason = mkstr("Dispense - %s:%i %s", handler->Name, Item->ID, Item->Name);
 	if( !reason )	reason = Item->Name;	// TODO: Should I instead return an error?
-	ret = Transfer( User, GetUserID(">sales"), Item->Price, reason);
+	ret = Transfer( User, GetUserID(COKEBANK_SALES_ACCT), Item->Price, reason);
 	free(reason);
 	if(ret)	return 2;	// 2: No balance
 	
@@ -40,7 +40,7 @@ int DispenseItem(int User, tItem *Item)
 		if(ret) {
 			Log_Error("Dispense failed after deducting cost (%s dispensing %s - %ic)",
 				username, Item->Name, Item->Price);
-			Transfer( GetUserID(">sales"), User, Item->Price, "rollback" );
+			Transfer( GetUserID(COKEBANK_SALES_ACCT), User, Item->Price, "rollback" );
 			free( username );
 			return -1;	// 1: Unkown Error again
 		}
@@ -78,7 +78,7 @@ int DispenseAdd(int User, int ByUser, int Ammount, const char *ReasonGiven)
 {
 	 int	ret;
 	
-	ret = Transfer( GetUserID(">liability"), User, Ammount, ReasonGiven );
+	ret = Transfer( GetUserID(COKEBANK_DEBT_ACCT), User, Ammount, ReasonGiven );
 	
 	if(ret)	return 2;
 	
diff --git a/src/server/server.c b/src/server/server.c
index 875dd04..2ee3fa7 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -15,11 +15,15 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 #include <string.h>
+#include <limits.h>
+#include <stdarg.h>
 
 // HACKS
 #define HACK_TPG_NOAUTH	1
 #define HACK_ROOT_NOAUTH	1
 
+#define	DEBUG_TRACE_CLIENT	1
+
 // Statistics
 #define MAX_CONNECTION_QUEUE	5
 #define INPUT_BUFFER_SIZE	256
@@ -32,6 +36,7 @@
 // === TYPES ===
 typedef struct sClient
 {
+	 int	Socket;	// Client socket ID
 	 int	ID;	// Client ID
 	 
 	 int	bIsTrusted;	// Is the connection from a trusted host/port
@@ -57,8 +62,10 @@ char	*Server_Cmd_ITEMINFO(tClient *Client, char *Args);
 char	*Server_Cmd_DISPENSE(tClient *Client, char *Args);
 char	*Server_Cmd_GIVE(tClient *Client, char *Args);
 char	*Server_Cmd_ADD(tClient *Client, char *Args);
+char	*Server_Cmd_ENUMUSERS(tClient *Client, char *Args);
 char	*Server_Cmd_USERINFO(tClient *Client, char *Args);
 // --- Helpers ---
+ int	sendf(int Socket, const char *Format, ...);
  int	GetUserAuth(const char *Salt, const char *Username, const uint8_t *Hash);
 void	HexBin(uint8_t *Dest, char *Src, int BufSize);
 
@@ -78,6 +85,7 @@ struct sClientCommand {
 	{"DISPENSE", Server_Cmd_DISPENSE},
 	{"GIVE", Server_Cmd_GIVE},
 	{"ADD", Server_Cmd_ADD},
+	{"ENUM_USERS", Server_Cmd_ENUMUSERS},
 	{"USER_INFO", Server_Cmd_USERINFO}
 };
 #define NUM_COMMANDS	(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0]))
@@ -185,6 +193,7 @@ void Server_HandleClient(int Socket, int bTrusted)
 	tClient	clientInfo = {0};
 	
 	// Initialise Client info
+	clientInfo.Socket = Socket;
 	clientInfo.ID = giServer_NextClientID ++;
 	clientInfo.bIsTrusted = bTrusted;
 	
@@ -210,7 +219,7 @@ void Server_HandleClient(int Socket, int bTrusted)
 			ret = Server_ParseClientCommand(&clientInfo, start);
 			
 			#if DEBUG_TRACE_CLIENT
-			//printf("ret = %s", ret);
+			printf("send : %s", ret);
 			#endif
 			
 			// `ret` is a string on the heap
@@ -574,6 +583,47 @@ char *Server_Cmd_ADD(tClient *Client, char *Args)
 	}
 }
 
+char *Server_Cmd_ENUMUSERS(tClient *Client, char *Args)
+{
+	 int	i, numRet = 0;
+	 int	maxBal = INT_MAX, minBal = INT_MIN;
+	 int	numUsr = GetMaxID();
+	
+	// Parse arguments
+	//minBal = atoi(Args);
+	
+	// Get return number
+	for( i = 0; i < numUsr; i ++ )
+	{
+		int bal = GetBalance(i);
+		
+		if( bal == INT_MIN )	continue;
+		
+		if( bal < minBal )	continue;
+		if( bal > maxBal )	continue;
+		
+		numRet ++;
+	}
+	
+	// Send count
+	sendf(Client->Socket, "201 Users %i\n", numRet);
+	
+	for( i = 0; i < numUsr; i ++ )
+	{
+		int bal = GetBalance(i);
+		
+		if( bal == INT_MIN )	continue;
+		
+		if( bal < minBal )	continue;
+		if( bal > maxBal )	continue;
+		
+		// TODO: User flags
+		sendf(Client->Socket, "202 User %s %i user\n", GetUserName(i), GetBalance(i));
+	}
+	
+	return strdup("200 List End\n");
+}
+
 char *Server_Cmd_USERINFO(tClient *Client, char *Args)
 {
 	 int	uid;
@@ -587,6 +637,7 @@ char *Server_Cmd_USERINFO(tClient *Client, char *Args)
 	uid = GetUserID(user);
 	if( uid == -1 )	return strdup("404 Invalid user");
 
+	// TODO: User flags/type
 	return mkstr("202 User %s %i user\n", user, GetBalance(uid));
 }
 
@@ -632,6 +683,29 @@ int GetUserAuth(const char *Salt, const char *Username, const uint8_t *ProvidedH
 }
 
 // --- INTERNAL 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);
+		
+		#if DEBUG_TRACE_CLIENT
+		printf("sendf: %s", buf);
+		#endif
+		
+		return send(Socket, buf, len, 0);
+	}
+}
+
 // TODO: Move to another file
 void HexBin(uint8_t *Dest, char *Src, int BufSize)
 {
-- 
GitLab