diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8e6f54303b76106f91362ddeabc45bd68a803eff..42260b3ae439d3fc402e4471eef2be3eadf7898e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -69,6 +69,7 @@ set(common_SRCS
 	connection.cpp
 	environment.cpp
 	server.cpp
+	servercommand.cpp
 	socket.cpp
 	mapblock.cpp
 	mapsector.cpp
diff --git a/src/player.cpp b/src/player.cpp
index 64780de75d7c2402d185b06b1e9362a0d57a4dbf..e568d7deef430cf409e68b602e2265b349c69023 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -23,6 +23,55 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "constants.h"
 #include "utility.h"
 
+// Convert a privileges value into a human-readable string,
+// with each component separated by a comma.
+std::wstring privsToString(u64 privs)
+{
+	std::wostringstream os(std::ios_base::binary);
+	if(privs & PRIV_BUILD)
+		os<<L"build,";
+	if(privs & PRIV_TELEPORT)
+		os<<L"teleport,";
+	if(privs & PRIV_SETTIME)
+		os<<L"settime,";
+	if(privs & PRIV_PRIVS)
+		os<<L"privs,";
+	if(os.tellp())
+	{
+		// Drop the trailing comma. (Why on earth can't
+		// you truncate a C++ stream anyway???)
+		std::wstring tmp = os.str();
+		return tmp.substr(0, tmp.length() -1);
+	}
+	return os.str();
+}
+
+// Converts a comma-seperated list of privilege values into a
+// privileges value. The reverse of privsToString(). Returns
+// PRIV_INVALID if there is anything wrong with the input.
+u64 stringToPrivs(std::wstring str)
+{
+	u64 privs=0;
+	std::vector<std::wstring> pr;
+	pr=str_split(str, ',');
+	for(std::vector<std::wstring>::iterator i = pr.begin();
+		i != pr.end(); ++i)
+	{
+		if(*i == L"build")
+			privs |= PRIV_BUILD;
+		else if(*i == L"teleport")
+			privs |= PRIV_TELEPORT;
+		else if(*i == L"settime")
+			privs |= PRIV_SETTIME;
+		else if(*i == L"privs")
+			privs |= PRIV_PRIVS;
+		else
+			return PRIV_INVALID;
+	}
+	return privs;
+}
+
+
 Player::Player():
 	touching_ground(false),
 	in_water(false),
@@ -34,7 +83,8 @@ Player::Player():
 	m_pitch(0),
 	m_yaw(0),
 	m_speed(0,0,0),
-	m_position(0,0,0)
+	m_position(0,0,0),
+	privs(PRIV_DEFAULT)
 {
 	updateName("<not set>");
 	resetInventory();
@@ -100,6 +150,7 @@ void Player::serialize(std::ostream &os)
 	args.setV3F("position", m_position);
 	args.setBool("craftresult_is_preview", craftresult_is_preview);
 	args.setS32("hp", hp);
+	args.setU64("privs", privs);
 
 	args.writeLines(os);
 
@@ -141,6 +192,20 @@ void Player::deSerialize(std::istream &is)
 	}catch(SettingNotFoundException &e){
 		hp = 20;
 	}
+	try{
+		std::string sprivs = args.get("privs");
+		if(sprivs == "all")
+		{
+			privs = PRIV_ALL;
+		}
+		else
+		{
+			std::istringstream ss(sprivs);
+			ss>>privs;
+		}
+	}catch(SettingNotFoundException &e){
+		privs = PRIV_DEFAULT;
+	}
 
 	inventory.deSerialize(is);
 }
diff --git a/src/player.h b/src/player.h
index f70b52fe737453a6741a8ca339de8f2f8148da27..be93766fd7a949ed2fa30498fc9fd5e9af4abb30 100644
--- a/src/player.h
+++ b/src/player.h
@@ -28,11 +28,38 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,"
 
+// Player privileges. These form a bitmask stored in the privs field
+// of the player, and define things they're allowed to do. See also
+// the static methods Player::privsToString and stringToPrivs that
+// convert these to human-readable form.
+const u64 PRIV_BUILD = 1;	// Can build - i.e. modify the world
+				//  (not enforced yet)
+const u64 PRIV_TELEPORT = 2;	// Can teleport
+const u64 PRIV_SETTIME = 4;	// Can set the time
+const u64 PRIV_PRIVS = 8;	// Can grant and revoke privileges
+const u64 PRIV_SERVER = 16;	// Can manage the server (e.g. shutodwn ,settings)
+
+const u64 PRIV_DEFAULT = PRIV_BUILD;
+const u64 PRIV_ALL = 0x7FFFFFFFFFFFFFFFULL;
+const u64 PRIV_INVALID = 0x8000000000000000ULL;
+
+// Convert a privileges value into a human-readable string,
+// with each component separated by a comma.
+std::wstring privsToString(u64 privs);
+
+// Converts a comma-seperated list of privilege values into a
+// privileges value. The reverse of privsToString(). Returns
+// PRIV_INVALID if there is anything wrong with the input.
+u64 stringToPrivs(std::wstring str);
+
+
 class Map;
 
 class Player
 {
 public:
+
+
 	Player();
 	virtual ~Player();
 
@@ -123,6 +150,9 @@ class Player
 
 	u16 hp;
 
+	// Player's privileges - a bitmaps of PRIV_xxxx.
+	u64 privs;
+
 	u16 peer_id;
 
 protected:
@@ -131,6 +161,9 @@ class Player
 	f32 m_yaw;
 	v3f m_speed;
 	v3f m_position;
+
+public:
+
 };
 
 /*
diff --git a/src/server.cpp b/src/server.cpp
index b5a38aa06b6ec34f946337bd920bc9f1a09eecb8..d3ca32ac7d1a7473de3851da8e78cf8165075226 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "materials.h"
 #include "mineral.h"
 #include "config.h"
+#include "servercommand.h"
 
 #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
 
@@ -1994,6 +1995,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		if(datasize < 13)
 			return;
 
+		if((player->privs & PRIV_BUILD) == 0)
+			return;
+
 		/*
 			[0] u16 command
 			[2] u8 button (0=left, 1=right)
@@ -2075,6 +2079,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		if(datasize < 7)
 			return;
 
+		if((player->privs & PRIV_BUILD) == 0)
+			return;
+
 		/*
 			length: 7
 			[0] u16 command
@@ -2272,6 +2279,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				cannot_remove_node = true;
 			}
 
+			// Make sure the player is allowed to do it
+			if((player->privs & PRIV_BUILD) == 0)
+				cannot_remove_node = true;
+
 			/*
 				If node can't be removed, set block to be re-sent to
 				client and quit.
@@ -2418,7 +2429,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				try{
 					// Don't add a node if this is not a free space
 					MapNode n2 = m_env.getMap().getNode(p_over);
-					if(content_buildable_to(n2.d) == false)
+					if(content_buildable_to(n2.d) == false
+						|| (player->privs & PRIV_BUILD) ==0)
 					{
 						// Client probably has wrong data.
 						// Set block not sent, so that client will get
@@ -2615,6 +2627,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 #endif
 	else if(command == TOSERVER_SIGNTEXT)
 	{
+		if((player->privs & PRIV_BUILD) == 0)
+			return;
 		/*
 			u16 command
 			v3s16 blockpos
@@ -2672,6 +2686,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 	}
 	else if(command == TOSERVER_SIGNNODETEXT)
 	{
+		if((player->privs & PRIV_BUILD) == 0)
+			return;
 		/*
 			u16 command
 			v3s16 p
@@ -2853,71 +2869,19 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			line += L"Server: ";
 
 			message = message.substr(commandprefix.size());
-			// Get player name as narrow string
-			std::string name_s = player->getName();
-			// Convert message to narrow string
-			std::string message_s = wide_to_narrow(message);
-			// Operator is the single name defined in config.
-			std::string operator_name = g_settings.get("name");
-			bool is_operator = (operator_name != "" &&
-					wide_to_narrow(name) == operator_name);
-			bool valid_command = false;
-			if(message_s == "help")
-			{
-				line += L"-!- Available commands: ";
-				line += L"status ";
-				if(is_operator)
-				{
-					line += L"shutdown setting time ";
-				}
-				else
-				{
-				}
-				send_to_sender = true;
-				valid_command = true;
-			}
-			else if(message_s == "status")
-			{
-				line = getStatusString();
-				send_to_sender = true;
-				valid_command = true;
-			}
-			else if(is_operator)
-			{
-				if(message_s == "shutdown")
-				{
-					dstream<<DTIME<<" Server: Operator requested shutdown."
-							<<std::endl;
-					m_shutdown_requested.set(true);
-					
-					line += L"*** Server shutting down (operator request)";
-					send_to_sender = true;
-					valid_command = true;
-				}
-				else if(message_s.substr(0,8) == "setting ")
-				{
-					std::string confline = message_s.substr(8);
-					g_settings.parseConfigLine(confline);
-					line += L"-!- Setting changed.";
-					send_to_sender = true;
-					valid_command = true;
-				}
-				else if(message_s.substr(0,5) == "time ")
-				{
-					u32 time = stoi(message_s.substr(5));
-					m_time_of_day.set(time);
-					m_time_of_day_send_timer = 0;
-					line += L"-!- time_of_day changed.";
-					send_to_sender = true;
-					valid_command = true;
-				}
-			}
-			
-			if(valid_command == false)
-			{
-				line += L"-!- Invalid command: " + message;
-				send_to_sender = true;
-			}
+
+			ServerCommandContext *ctx = new ServerCommandContext(
+				str_split(message, L' '),
+				this,
+				&m_env,
+				player
+				);
+
+			line += processServerCommand(ctx);
+			send_to_sender = ctx->flags & 1;
+			send_to_others = ctx->flags & 2;
+			delete ctx;
+
 		}
 		else
 		{
diff --git a/src/server.h b/src/server.h
index 4603f98ed5eed3b242c10d27ab8f2053af4d5928..d8b47aef9f96396471731bfb73a9107ab2295f5a 100644
--- a/src/server.h
+++ b/src/server.h
@@ -387,6 +387,12 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 		return time_to_daynight_ratio(m_time_of_day.get());
 	}
 
+	void setTimeOfDay(u32 time)
+	{
+		m_time_of_day.set(time);
+		m_time_of_day_send_timer = 0;
+	}
+
 	bool getShutdownRequested()
 	{
 		return m_shutdown_requested.get();
@@ -405,6 +411,19 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	Inventory* getInventory(InventoryContext *c, std::string id);
 	void inventoryModified(InventoryContext *c, std::string id);
 
+	// Connection must be locked when called
+	std::wstring getStatusString();
+
+	void requestShutdown(void)
+	{
+		m_shutdown_requested.set(true);
+	}
+
+
+	// Envlock and conlock should be locked when calling this
+	void SendMovePlayer(Player *player);
+
+
 private:
 
 	// Virtual methods from con::PeerHandler.
@@ -429,7 +448,6 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	void SendChatMessage(u16 peer_id, const std::wstring &message);
 	void BroadcastChatMessage(const std::wstring &message);
 	void SendPlayerHP(Player *player);
-	void SendMovePlayer(Player *player);
 	/*
 		Send a node removal/addition event to all clients except ignore_id.
 		Additionally, if far_players!=NULL, players further away than
@@ -455,9 +473,6 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	// When called, connection mutex should be locked
 	RemoteClient* getClient(u16 peer_id);
 	
-	// Connection must be locked when called
-	std::wstring getStatusString();
-	
 	/*
 		Get a player from memory or creates one.
 		If player is already connected, return NULL
diff --git a/src/servercommand.cpp b/src/servercommand.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..215dc0d275f09ff07efb0071c5620c03812bd8da
--- /dev/null
+++ b/src/servercommand.cpp
@@ -0,0 +1,231 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "servercommand.h"
+#include "utility.h"
+
+void cmd_status(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	os<<ctx->server->getStatusString();
+}
+
+void cmd_privs(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if(ctx->parms.size() == 1)
+	{
+		os<<L"-!- " + privsToString(ctx->player->privs);
+		return;
+	}
+
+	if((ctx->player->privs & PRIV_PRIVS) == 0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+		
+	Player *tp = ctx->env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str());
+	if(tp == NULL)
+	{
+		os<<L"-!- No such player";
+		return;
+	}
+	
+	os<<L"-!- " + privsToString(tp->privs);
+}
+
+void cmd_grantrevoke(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if(ctx->parms.size() != 3)
+	{
+		os<<L"-!- Missing parameter";
+		return;
+	}
+
+	if((ctx->player->privs & PRIV_PRIVS) == 0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+
+	u64 newprivs = stringToPrivs(ctx->parms[2]);
+	if(newprivs == PRIV_INVALID)
+	{
+		os<<L"-!- Invalid privileges specified";
+		return;
+	}
+
+	Player *tp = ctx->env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str());
+	if(tp == NULL)
+	{
+		os<<L"-!- No such player";
+		return;
+	}
+
+	if(ctx->parms[0] == L"grant")
+		tp->privs |= newprivs;
+	else
+		tp->privs &= ~newprivs;
+	
+	os<<L"-!- Privileges change to ";
+	os<<privsToString(tp->privs);
+}
+
+void cmd_time(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if(ctx->parms.size() != 2)
+	{
+		os<<L"-!- Missing parameter";
+		return;
+	}
+
+	if((ctx->player->privs & PRIV_SETTIME) ==0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+
+	u32 time = stoi(wide_to_narrow(ctx->parms[1]));
+	ctx->server->setTimeOfDay(time);
+	os<<L"-!- time_of_day changed.";
+}
+
+void cmd_shutdown(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if((ctx->player->privs & PRIV_SERVER) ==0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+
+	dstream<<DTIME<<" Server: Operator requested shutdown."
+		<<std::endl;
+	ctx->server->requestShutdown();
+					
+	os<<L"*** Server shutting down (operator request)";
+	ctx->flags |= 2;
+}
+
+void cmd_setting(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if((ctx->player->privs & PRIV_SERVER) ==0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+
+	std::string confline = wide_to_narrow(ctx->parms[1] + L" = " + ctx->parms[2]);
+	g_settings.parseConfigLine(confline);
+	os<< L"-!- Setting changed.";
+}
+
+void cmd_teleport(std::wostringstream &os,
+	ServerCommandContext *ctx)
+{
+	if((ctx->player->privs & PRIV_TELEPORT) ==0)
+	{
+		os<<L"-!- You don't have permission to do that";
+		return;
+	}
+
+	if(ctx->parms.size() != 2)
+	{
+		os<<L"-!- Missing parameter";
+		return;
+	}
+
+	std::vector<std::wstring> coords = str_split(ctx->parms[1], L',');
+	if(coords.size() != 3)
+	{
+		os<<L"-!- You can only specify coordinates currently";
+		return;
+	}
+
+	v3f dest(stoi(coords[0])*10, stoi(coords[1])*10, stoi(coords[2])*10);
+	ctx->player->setPosition(dest);
+	ctx->server->SendMovePlayer(ctx->player);
+
+	os<< L"-!- Teleported.";
+}
+
+
+std::wstring processServerCommand(ServerCommandContext *ctx)
+{
+
+	std::wostringstream os(std::ios_base::binary);
+	ctx->flags = 1;	// Default, unless we change it.
+
+	u64 privs = ctx->player->privs;
+
+	if(ctx->parms.size() == 0 || ctx->parms[0] == L"help")
+	{
+		os<<L"-!- Available commands: ";
+		os<<L"status privs ";
+		if(privs & PRIV_SERVER)
+			os<<L"shutdown setting ";
+		if(privs & PRIV_SETTIME)
+			os<<L" time";
+		if(privs & PRIV_TELEPORT)
+			os<<L" teleport";
+		if(privs & PRIV_PRIVS)
+			os<<L" grant revoke";
+	}
+	else if(ctx->parms[0] == L"status")
+	{
+		cmd_status(os, ctx);
+	}
+	else if(ctx->parms[0] == L"privs")
+	{
+		cmd_privs(os, ctx);
+	}
+	else if(ctx->parms[0] == L"grant" || ctx->parms[0] == L"revoke")
+	{
+		cmd_grantrevoke(os, ctx);
+	}
+	else if(ctx->parms[0] == L"time")
+	{
+		cmd_time(os, ctx);
+	}
+	else if(ctx->parms[0] == L"shutdown")
+	{
+		cmd_shutdown(os, ctx);
+	}
+	else if(ctx->parms[0] == L"setting")
+	{
+		cmd_setting(os, ctx);
+	}
+	else if(ctx->parms[0] == L"teleport")
+	{
+		cmd_teleport(os, ctx);
+	}
+	else
+	{
+		os<<L"-!- Invalid command: " + ctx->parms[0];
+	}
+	return os.str();
+}
+
+
diff --git a/src/servercommand.h b/src/servercommand.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc7823c66ed0dc1dfa7241c02f509312f8bec9f8
--- /dev/null
+++ b/src/servercommand.h
@@ -0,0 +1,58 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef SERVERCOMMAND_HEADER
+#define SERVERCOMMAND_HEADER
+
+#include <vector>
+#include <sstream>
+#include "common_irrlicht.h"
+#include "player.h"
+#include "server.h"
+
+struct ServerCommandContext
+{
+
+	std::vector<std::wstring> parms;
+	Server* server;
+	ServerEnvironment *env;
+	Player* player;
+	u32 flags;
+
+	ServerCommandContext(
+		std::vector<std::wstring> parms,
+		Server* server,
+		ServerEnvironment *env,
+		Player* player)
+		: parms(parms), server(server), env(env), player(player)
+	{
+	}
+
+};
+
+// Process a command sent from a client. The environment and connection
+// should be locked when this is called.
+// Returns a response message, to be dealt with according to the flags set
+// in the context.
+std::wstring processServerCommand(ServerCommandContext *ctx);
+
+#endif
+
+
diff --git a/src/utility.h b/src/utility.h
index 12d732bea8d03ffca666787b8730ffc5efe860c3..326ebf16125f002ffe95a63b19dab05925332648 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <fstream>
 #include <string>
 #include <sstream>
+#include <vector>
 #include <jthread.h>
 #include <jmutex.h>
 #include <jmutexautolock.h>
@@ -731,6 +732,19 @@ inline std::string wide_to_narrow(const std::wstring& wcs)
 	return *mbs;
 }
 
+// Split a string using the given delimiter. Returns a vector containing
+// the component parts.
+inline std::vector<std::wstring> str_split(const std::wstring &str, wchar_t delimiter)
+{
+	std::vector<std::wstring> parts;
+	std::wstringstream sstr(str);
+	std::wstring part;
+	while(std::getline(sstr, part, delimiter))
+		parts.push_back(part);
+	return parts;
+}
+
+
 /*
 	See test.cpp for example cases.
 	wraps degrees to the range of -360...360
@@ -791,6 +805,11 @@ inline s32 stoi(std::string s)
 	return atoi(s.c_str());
 }
 
+inline s32 stoi(std::wstring s)
+{
+	return atoi(wide_to_narrow(s).c_str());
+}
+
 inline float stof(std::string s)
 {
 	float f;