diff --git a/src/cokebank.h b/src/cokebank.h
index c3c7c6f36de618329a27646c38d130e6d8107cca..13d0bb5c37e5a917597a872fd1fd17ec636ae674 100644
--- a/src/cokebank.h
+++ b/src/cokebank.h
@@ -180,6 +180,21 @@ extern void	Bank_DelIterator(tAcctIterator *It);
  */
 extern int	Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password);
 
+/**
+ * \brief Checks the validity of a pin against a username
+ * \param AcctID	Account ID
+ * \param Pin  	Integer version of the pin
+ * \return Boolean correct
+ */
+extern int	Bank_IsPinValid(int AcctID, int Pin);
+
+/**
+ * \brief Update a user's pin
+ * \param AcctID	Account ID
+ * \param NewPin	New pin for the account
+ */
+extern void	Bank_SetPin(int AcctID, int NewPin);
+
 /**
  * \brief Get an account ID from a MIFARE card ID
  * \param CardID	MIFARE card ID
diff --git a/src/cokebank_sqlite/main.c b/src/cokebank_sqlite/main.c
index 34761b2ebf9f0ab36637718d609c7681873159a8..464cee388663fdb37e26db51e0c5dfb7404fe0f4 100644
--- a/src/cokebank_sqlite/main.c
+++ b/src/cokebank_sqlite/main.c
@@ -63,6 +63,8 @@ struct sAcctIterator	// Unused really, just used as a void type
  int	Bank_SetFlags(int AcctID, int Mask, int Value);
  int	Bank_GetBalance(int AcctID);
 char	*Bank_GetAcctName(int AcctID);
+ int	Bank_IsPinValid(int AcctID, int Pin);
+void	Bank_SetPin(int AcctID, int Pin);
 sqlite3_stmt	*Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query);
  int	Bank_int_QueryNone(sqlite3 *Database, const char *Query, char **ErrorMessage);
 sqlite3_stmt	*Bank_int_QuerySingle(sqlite3 *Database, const char *Query);
@@ -354,6 +356,34 @@ int Bank_CreateAcct(const char *Name)
 	return sqlite3_last_insert_rowid(gBank_Database);
 }
 
+int Bank_IsPinValid(int AcctID, int Pin)
+{
+	char *query = mkstr("SELECT acct_id FROM accounts WHERE acct_id=%i AND acct_pin=%i LIMIT 1", AcctID, Pin);
+	sqlite3_stmt *statement = Bank_int_QuerySingle(gBank_Database, query);
+	free(query);
+	
+	if( statement ) {
+		sqlite3_finalize(statement);
+	}
+
+	return (statement != NULL);
+}
+
+void Bank_SetPin(int AcctID, int Pin)
+{
+	char *errmsg;
+	char *query = mkstr("UPDATE accounts SET acct_pin=%i WHERE acct_id=%i", Pin, AcctID);
+	int rv = Bank_int_QueryNone(gBank_Database, query, &errmsg);
+	if( rv != SQLITE_OK )
+	{
+		fprintf(stderr, "Bank_CreateAcct - SQLite Error: '%s'\n", errmsg);
+		fprintf(stderr, "Query = '%s'\n", query);
+		sqlite3_free(errmsg);
+		free(query);
+		return ;
+	}
+	free(query);
+}
 /*
  * Create an iterator for user accounts
  */
diff --git a/src/server/main.c b/src/server/main.c
index 20520fcbdc5fcb12be8ce4fa2363769e0ac4f42d..6c2f69142864a48990730442a495348cbee21a62 100644
--- a/src/server/main.c
+++ b/src/server/main.c
@@ -54,6 +54,8 @@ void sigint_handler()
 void PrintUsage(const char *progname)
 {
 	fprintf(stderr, "Usage: %s\n", progname);
+	fprintf(stderr, "  -f,--configfile\n");
+	fprintf(stderr, "        Set the config file path (default `dispsrv.conf')\n");
 	fprintf(stderr, "  -d    Set debug level (0 - 2, default 0)\n");
 	fprintf(stderr, "  --[dont-]daemonise\n");
 	fprintf(stderr, "        Run (or explicitly don't run) the server disconnected from the terminal\n");
@@ -83,6 +85,7 @@ int main(int argc, char *argv[])
 				break;
 			default:
 				// Usage Error
+				fprintf(stderr, "Unknown option '-%c'\n", arg[1]);
 				PrintUsage(argv[0]);
 				return -1;
 			}
@@ -101,6 +104,7 @@ int main(int argc, char *argv[])
 			}
 			else {
 				// Usage error
+				fprintf(stderr, "Unknown option '%s'\n", arg);
 				PrintUsage(argv[0]);
 				return -1;
 			}
diff --git a/src/server/server.c b/src/server/server.c
index 97800588ea3daf783f5474ee11bcab563f614d6f..9bf9eba2a71715dce302032a3bd83288460b7f44 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -21,6 +21,7 @@
 #include <signal.h>	// Signal handling
 #include <ident.h>	// AUTHIDENT
 #include <time.h>	// time(2)
+#include <ctype.h>
 
 #define	DEBUG_TRACE_CLIENT	0
 #define HACK_NO_REFUNDS	1
@@ -82,6 +83,8 @@ void	_SendUserInfo(tClient *Client, int UserID);
 void	Server_Cmd_USERADD(tClient *Client, char *Args);
 void	Server_Cmd_USERFLAGS(tClient *Client, char *Args);
 void	Server_Cmd_UPDATEITEM(tClient *Client, char *Args);
+void	Server_Cmd_PINCHECK(tClient *Client, char *Args);
+void	Server_Cmd_PINSET(tClient *Client, char *Args);
 // --- Helpers ---
 void	Debug(tClient *Client, const char *Format, ...);
  int	sendf(int Socket, const char *Format, ...);
@@ -111,7 +114,9 @@ const struct sClientCommand {
 	{"USER_INFO", Server_Cmd_USERINFO},
 	{"USER_ADD", Server_Cmd_USERADD},
 	{"USER_FLAGS", Server_Cmd_USERFLAGS},
-	{"UPDATE_ITEM", Server_Cmd_UPDATEITEM}
+	{"UPDATE_ITEM", Server_Cmd_UPDATEITEM},
+	{"PIN_CHECK", Server_Cmd_PINCHECK},
+	{"PIN_SET", Server_Cmd_PINSET}
 };
 #define NUM_COMMANDS	((int)(sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])))
 
@@ -1540,6 +1545,95 @@ void Server_Cmd_UPDATEITEM(tClient *Client, char *Args)
 	}
 }
 
+void Server_Cmd_PINCHECK(tClient *Client, char *Args)
+{
+	char	*username, *pinstr;
+	 int	pin;
+
+	if( Server_int_ParseArgs(0, Args, &username, &pinstr, NULL) ) {
+		sendf(Client->Socket, "407 PIN_CHECK takes 2 arguments\n");
+		return ;
+	}
+	
+	if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
+		sendf(Client->Socket, "407 PIN should be four digits\n");
+		return ;
+	}
+	pin = atoi(pinstr);
+
+	// Not strictly needed, but ensures that randoms don't do brute forcing
+	if( !Client->bIsAuthed ) {
+		sendf(Client->Socket, "401 Not Authenticated\n");
+		return ;
+	}
+	
+	// Check user permissions
+	if( !(Bank_GetFlags(Client->UID) & (USER_FLAG_COKE|USER_FLAG_ADMIN))  ) {
+		sendf(Client->Socket, "403 Not in coke\n");
+		return ;
+	}
+	
+	// Get user
+	int uid = Bank_GetAcctByName(username, 0);
+	if( uid == -1 ) {
+		sendf(Client->Socket, "404 User '%s' not found\n", username);
+		return ;
+	}
+	
+	// Get the pin
+	static time_t	last_wrong_pin_time;
+	static int	backoff = 1;
+	if( time(NULL) - last_wrong_pin_time < backoff ) {
+		sendf(Client->Socket, "407 Rate limited (%i seconds remaining)\n",
+			backoff - (time(NULL) - last_wrong_pin_time));
+		return ;
+	}	
+	last_wrong_pin_time = time(NULL);
+	if( !Bank_IsPinValid(uid, pin) )
+	{
+		sendf(Client->Socket, "403 Pin incorrect\n");
+		if( backoff < 5)
+			backoff ++;
+		return ;
+	}
+
+	last_wrong_pin_time = 0;
+	backoff = 1;
+	sendf(Client->Socket, "200 Pin correct\n");
+	return ;
+}
+void Server_Cmd_PINSET(tClient *Client, char *Args)
+{
+	char	*pinstr;
+	 int	pin;
+	
+
+	if( Server_int_ParseArgs(0, Args, &pinstr, NULL) ) {
+		sendf(Client->Socket, "407 PIN_SET takes 2 arguments\n");
+		return ;
+	}
+	
+	if( !isdigit(pinstr[0]) || !isdigit(pinstr[1]) || !isdigit(pinstr[2]) || !isdigit(pinstr[3]) || pinstr[4] != '\0' ) {
+		sendf(Client->Socket, "407 PIN should be four digits\n");
+		return ;
+	}
+	pin = atoi(pinstr);
+
+	// Not strictly needed, but ensures that randoms don't do brute forcing
+	if( !Client->bIsAuthed ) {
+		sendf(Client->Socket, "401 Not Authenticated\n");
+		return ;
+	}
+	
+	int uid = Client->EffectiveUID;
+	if(uid == -1)
+		uid = Client->UID;
+	// Can only pinset yourself (well, the effective user)
+	Bank_SetPin(uid, pin);
+	sendf(Client->Socket, "200 Pin updated\n");
+	return ;
+}
+
 // --- INTERNAL HELPERS ---
 void Debug(tClient *Client, const char *Format, ...)
 {
@@ -1716,3 +1810,4 @@ int Server_int_ParseFlags(tClient *Client, const char *Str, int *Mask, int *Valu
 	
 	return 0;
 }
+