diff --git cmake/SimutransSourceList.cmake cmake/SimutransSourceList.cmake
index ba78610c0..315789d4f 100644
--- cmake/SimutransSourceList.cmake
+++ cmake/SimutransSourceList.cmake
@@ -171,6 +171,7 @@ target_sources(simutrans PRIVATE
 		src/simutrans/gui/pakinstaller.cc
 		src/simutrans/gui/pakselector.cc
 		src/simutrans/gui/password_frame.cc
+		src/simutrans/gui/permission_frame.cc
 		src/simutrans/gui/player_frame.cc
 		src/simutrans/gui/player_ranking_frame.cc
 		src/simutrans/gui/privatesign_info.cc
diff --git simutrans/history.txt simutrans/history.txt
index 2fa8191d8..254a7f4a6 100644
--- simutrans/history.txt
+++ simutrans/history.txt
@@ -12,6 +12,7 @@
 	CHG: (poppo) producing factories can also generate electricity (use with care)
 	ADD: Optional 'electricity_producer' flag (can be 0 or 1) for factories to override electricity producer status based on object name
 	ADD: (makie) more settings for inital climates in simuconf.tab
+   ADD: (nazalassa) permissions for halts, to let other players serve specific halts
 	FIX: (janry) admin packets only read or write for admin tool/server
 	FIX: drive again to the end of the platform after choose signal selection
 	FIX: pak specific config in user_dir/addons/PAK_NAM/config/simuconf.tab for reset extended settings
diff --git src/simutrans/gui/halt_info.cc src/simutrans/gui/halt_info.cc
index 66c83aaed..0b0f1b7a5 100644
--- src/simutrans/gui/halt_info.cc
+++ src/simutrans/gui/halt_info.cc
@@ -6,6 +6,7 @@
 #include <algorithm>
 
 #include "halt_info.h"
+#include "permission_frame.h"
 #include "components/gui_button_to_chart.h"
 
 #include "../world/simworld.h"
@@ -174,8 +175,14 @@ private:
 public:
 	uint8 destination_counter; // last destination counter of the halt; if mismatch to current, then redraw destinations
 
+	/**
+	 * Pops up the "permissions" dialog. This is directly managed by the halt info frame. (So not sure it really fits here.)
+	 */
+	button_t permission_details;
+
 	gui_halt_detail_t(halthandle_t h) : gui_aligned_container_t()
 	{
+		permission_details.init(button_t::roundbox, "Permissions");
 		destination_counter = 0xFF;
 		cached_line_count = 0xFFFFFFFFul;
 		cached_convoy_count = 0xFFFFFFFFul;
@@ -292,7 +299,8 @@ void halt_info_t::init(halthandle_t halt)
 {
 	this->halt = halt;
 	set_name(halt->get_name());
-	set_owner(halt->get_owner() );
+	set_owner(halt->get_owner());
+	permission_frame = NULL;
 
 	set_table_layout(1,0);
 
@@ -388,6 +396,7 @@ void halt_info_t::init(halthandle_t halt)
 
 	// connection info
 	switch_mode.add_tab(&scrolly_details, translator::translate("Connections"));
+	halt_detail->permission_details.add_listener(this);
 	halt_detail->update_connections(halt);
 	halt_detail->set_size( halt_detail->get_min_size() );
 
@@ -432,6 +441,9 @@ halt_info_t::~halt_info_t()
 	}
 	delete departure_board;
 	delete halt_detail;
+	if (permission_frame) {
+		destroy_win(permission_frame);
+	}
 }
 
 
@@ -543,6 +555,7 @@ void gui_halt_detail_t::update_connections( halthandle_t halt )
 	slist_tpl<const goods_desc_t *> nimmt_an;
 
 	set_table_layout(2,0);
+	add_component(&permission_details, 2);
 	new_component_span<gui_label_t>("Fabrikanschluss", 2);
 
 	if (!fab_list.empty()) {
@@ -978,6 +991,15 @@ bool halt_info_t::action_triggered( gui_action_creator_t *comp, value_t )
 	else if (comp == &switch_mode) {
 		departure_board->next_refresh = -1;
 	}
+	else if (comp == &halt_detail->permission_details) {
+		if (permission_frame) {
+			top_win(permission_frame);
+		}
+		else {
+			permission_frame = new permission_frame_t(this, halt);
+			create_win(permission_frame, w_info, magic_none);
+		}
+	}
 
 	return true;
 }
diff --git src/simutrans/gui/halt_info.h src/simutrans/gui/halt_info.h
index a2a26e04c..b9390484f 100644
--- src/simutrans/gui/halt_info.h
+++ src/simutrans/gui/halt_info.h
@@ -81,6 +81,11 @@ private:
 
 	gui_tab_panel_t switch_mode;
 
+	/**
+	 * The permission frame for this halt, if any
+	 */
+	gui_frame_t *permission_frame;
+
 	halthandle_t halt;
 	char edit_name[256];
 
@@ -99,6 +104,11 @@ public:
 	 */
 	const char *get_help_filename() const OVERRIDE {return "station.txt";}
 
+	/**
+	 * The permission frame tells us when it is closed.
+	 */
+	void permission_frame_closed() { permission_frame = NULL; }
+
 	/**
 	 * Draw new component. The values to be passed refer to the window
 	 * i.e. It's the screen coordinates of the window where the
diff --git src/simutrans/gui/permission_frame.cc src/simutrans/gui/permission_frame.cc
new file mode 100644
index 000000000..39dc8ed6c
--- /dev/null
+++ src/simutrans/gui/permission_frame.cc
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the Simutrans project under the Artistic License.
+ * (see LICENSE.txt)
+ */
+
+#include "permission_frame.h"
+
+#include "../world/simworld.h"
+#include "../tool/simmenu.h"
+#include "../player/simplay.h"
+
+#include "../dataobj/translator.h"
+
+
+permission_frame_t::permission_frame_t(halt_info_t *main_frame, halthandle_t halt) :
+	gui_frame_t( translator::translate("Set permissions"), halt->get_owner() )
+{
+	this->main_frame = main_frame;
+	this->halt = halt;
+
+	set_table_layout(1,0);
+
+	uint16 mask = halt->get_permissions();
+	for (int i=0;  i<PLAYER_UNOWNED;  i++) {
+		if (welt->get_player(i)) {
+			players[i].init(button_t::square_state, welt->get_player(i)->get_name());
+			players[i].add_listener(this);
+			players[i].pressed = (mask & (1<<i)) != 0;
+		} else {
+			players[i].init(button_t::square_state, "");
+			players[i].disable();
+		}
+		add_component(&players[i]);
+	}
+
+	reset_min_windowsize();
+	set_windowsize(get_min_windowsize());
+}
+
+
+permission_frame_t::~permission_frame_t()
+{
+	main_frame->permission_frame_closed();
+}
+
+
+/**
+ * Draw new component. The values to be passed refer to the window
+ * i.e. It's the screen coordinates of the window where the
+ * component is displayed.
+ */
+void permission_frame_t::draw(scr_coord pos, scr_size size)
+{
+	assert(halt.is_bound());
+
+	uint16 mask = halt->get_permissions();
+	for (int i=0;  i<PLAYER_UNOWNED;  i++) {
+		const bool new_state = (mask & (1<<i)) != 0;
+		if (players[i].pressed != new_state) {
+			players[i].pressed = new_state;
+			set_dirty();
+		}
+	}
+
+	gui_frame_t::draw(pos, size);
+}
+
+
+/**
+ * This method is called if an action is triggered
+ */
+bool permission_frame_t::action_triggered(gui_action_creator_t *comp, value_t /* */)
+{
+	for (int i=0;  i<PLAYER_UNOWNED;  i++) {
+		if (comp == &players[i]) {
+			uint16_t mask = halt->get_permissions();
+			mask ^= (1 << i);
+			char param[64];
+			sprintf(param, "%u,%u", halt.get_id(), mask);
+			tool_t *tool = create_tool( TOOL_SET_PERMISSION | SIMPLE_TOOL );
+			tool->set_default_param( param );
+			welt->set_tool( tool, welt->get_active_player() );
+			// since init always returns false, it is safe to delete immediately
+			delete tool;
+		}
+	}
+	return true;
+}
diff --git src/simutrans/gui/permission_frame.h src/simutrans/gui/permission_frame.h
new file mode 100644
index 000000000..1cf5d2101
--- /dev/null
+++ src/simutrans/gui/permission_frame.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the Simutrans project under the Artistic License.
+ * (see LICENSE.txt)
+ */
+
+#ifndef GUI_PERMISSION_FRAME_H
+#define GUI_PERMISSION_FRAME_H
+
+
+#include "components/action_listener.h"
+#include "components/gui_button.h"
+#include "gui_frame.h"
+#include "halt_info.h"
+
+
+class permission_frame_t : public gui_frame_t, action_listener_t
+{
+	halt_info_t *main_frame;
+	halthandle_t halt;
+	button_t players[16];
+
+public:
+	permission_frame_t(halt_info_t *main_frame, halthandle_t halt);
+	~permission_frame_t();
+
+	void draw(scr_coord pos, scr_size size) OVERRIDE;
+
+	bool action_triggered(gui_action_creator_t*, value_t) OVERRIDE;
+};
+
+#endif
diff --git src/simutrans/gui/simwin.h src/simutrans/gui/simwin.h
index 3b579250b..15e5a4f54 100644
--- src/simutrans/gui/simwin.h
+++ src/simutrans/gui/simwin.h
@@ -120,7 +120,7 @@ enum magic_numbers {
 	magic_haltlist_filter,
 	magic_depot, // only used to load/save
 	magic_depotlist   = magic_depot + MAX_PLAYER_COUNT,
-	magic_vehiclelist = magic_depotlist   + MAX_PLAYER_COUNT,
+	magic_vehiclelist = magic_depotlist + MAX_PLAYER_COUNT,
 	magic_pakinstall,
 	magic_chatframe,
 	magic_player_ranking,
diff --git src/simutrans/simhalt.cc src/simutrans/simhalt.cc
index 170690fd2..68406fa9d 100644
--- src/simutrans/simhalt.cc
+++ src/simutrans/simhalt.cc
@@ -194,13 +194,13 @@ halthandle_t haltestelle_t::get_halt_koord_index(koord k)
 
 
 /* we allow only for a single stop per grund
- * this will only return something if this stop belongs to same player or is public, or is a dock (when on water)
+ * this will only return something if this stop belongs to same player or is public or its permissions allow access, or is a dock (when on water)
  */
 halthandle_t haltestelle_t::get_halt(const koord3d pos, const player_t *player )
 {
 	const grund_t *gr = welt->lookup(pos);
 	if(gr) {
-		if(gr->get_halt().is_bound()  &&  player_t::check_owner(player,gr->get_halt()->get_owner())  ) {
+		if(gr->get_halt().is_bound()  &&  gr->get_halt()->can_player_stop(player)) {
 			return gr->get_halt();
 		}
 		// no halt? => we do the water check
@@ -218,7 +218,7 @@ halthandle_t haltestelle_t::get_halt(const koord3d pos, const player_t *player )
 			// then for public stop
 			for(  uint8 i=0;  i<cnt;  i++  ) {
 				halthandle_t halt = plan->get_haltlist()[i];
-				if(  halt->get_owner()==welt->get_public_player()  &&  halt->get_station_type()&dock  ) {
+				if(  (halt->get_owner()==welt->get_public_player()  ||  (halt->get_permissions() & (1 << player->get_player_nr())))  &&  halt->get_station_type()&dock  ) {
 					return halt;
 				}
 			}
@@ -477,6 +477,8 @@ haltestelle_t::haltestelle_t(loadsave_t* file)
 	status_color = SYSCOL_TEXT_UNUSED;
 	last_status_color = gfx->palette_lookup(COL_PURPLE);
 	last_bar_count = 0;
+	last_permissions = 0;
+	last_player_count = 0;
 
 	reconnect_counter = welt->get_schedule_counter()-1;
 
@@ -519,6 +521,7 @@ haltestelle_t::haltestelle_t(koord k, player_t* player)
 	last_bar_count = 0;
 
 	init_financial_history();
+	set_permissions(0, false);
 }
 
 
@@ -617,6 +620,26 @@ void haltestelle_t::rotate90( const sint16 y_size )
 }
 
 
+void haltestelle_t::set_permissions(uint16 perms, bool reroute) {
+	uint16 old_permissions = permissions;
+	// Owner and public service are always allowed here
+	// If public, everyone is allowed here
+	permissions = owner->is_public_service() ? 0xFFFF : (perms | (1<<owner->get_player_nr()) | (1<<welt->get_public_player()->get_player_nr()));
+	if (reroute && old_permissions != permissions) {
+		check_missing_convoys();
+		rebuild_connections();
+		rebuild_linked_connections();
+		rebuild_connected_components();
+	}
+}
+
+
+bool haltestelle_t::can_player_stop(const player_t *player) const
+{
+	return player_t::check_owner(player, owner) || (permissions&(1<<player->get_player_nr()));
+}
+
+
 
 const char* haltestelle_t::get_name() const
 {
@@ -1211,6 +1234,51 @@ void haltestelle_t::remove_fabriken(fabrik_t *fab)
 }
 
 
+/**
+ * Updates the list of convoys and lines
+ * Since it asks every line and convoy, this may take a while...
+ */
+void haltestelle_t::check_missing_convoys()
+{
+
+	// check if we have to register line(s) and/or lineless convoy(s) which serve this halt
+	vector_tpl<linehandle_t> check_line(0);
+
+	// iterate over all lines
+	for(  uint8 i=0;  i<MAX_PLAYER_COUNT;  i++  ) {
+		player_t *player = welt->get_player(i);
+		if(  player  &&  can_player_stop(player)  ) {
+			player->simlinemgmt.get_lines(simline_t::line, &check_line);
+			for(linehandle_t const j : check_line  ) {
+				// only add unknown lines
+				if(  !registered_lines.is_contained(j)  &&  j->count_convoys() > 0  ) {
+					for(schedule_entry_t const& k : j->get_schedule()->entries  ) {
+						if(  get_halt(k.pos, player) == self  ) {
+							registered_lines.append(j);
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+	// iterate over all convoys
+	for(convoihandle_t const cnv : welt->convoys()) {
+		// only check lineless convoys which have matching ownership and which are not yet registered
+		if(  !cnv->get_line().is_bound()  &&  can_player_stop(cnv->get_owner())  &&  !registered_convoys.is_contained(cnv)  ) {
+			if(  const schedule_t *const schedule = cnv->get_schedule()  ) {
+				for(schedule_entry_t const& k : schedule->entries) {
+					if (get_halt(k.pos, cnv->get_owner()) == self) {
+						registered_convoys.append(cnv);
+						break;
+					}
+				}
+			}
+		}
+	}
+}
+
+
 /**
  * Rebuilds the list of connections to directly reachable halts
  * Returns the number of stops considered
@@ -1291,6 +1359,23 @@ sint32 haltestelle_t::rebuild_connections()
 		}
 		++start_index; // the next index after self halt; it's okay to be out-of-range
 
+		if(  start_index > schedule->get_count()  ) {
+			DBG_MESSAGE("haltestelle_t::rebuild_connections()","stray line or convoy at id=%u", self.get_id());
+			// self halt not found in schedule? what is this line/convoi doing here?
+			// let's remove it
+			--current_index;
+			if(  lines  ) {
+				stale_lines.append_unique(registered_lines[current_index]);
+				registered_lines.remove_at(current_index);
+			}
+			else {
+				stale_convois.append_unique(registered_convoys[current_index]);
+				registered_convoys.remove_at(current_index);
+			}
+			reset_routing();
+			continue;
+		}
+
 		// determine goods category indices supported by this halt
 		supported_catg_index.clear();
 		for(uint8 const catg_index : *goods_catg_index) {
@@ -2566,6 +2651,12 @@ void haltestelle_t::change_owner( player_t *player )
 
 	// now finally change owner
 	owner = player;
+	// Old player is still allowed to serve this stop - FIXME?
+	// Should not matter as stops can only be made public (for now)
+	set_permissions(permissions, false);
+	// Maybe other players had vehicles stopping here, but it was a waypoint
+	// so check for then too
+	check_missing_convoys();
 	rebuild_connections();
 	rebuild_linked_connections();
 	rebuild_connected_components();
@@ -2625,10 +2716,17 @@ void haltestelle_t::merge_halt( halthandle_t halt_merged )
 	halt_merged->transfer_goods(self);
 	destroy(halt_merged);
 
+	// Allow everyone who could serve either halt to continue serving it
+	bool permissions_changed = halt_merged->get_permissions() != permissions;
+	set_permissions(halt_merged->get_permissions() | permissions, false);
+
 	recalc_basis_pos();
 
 	// also rebuild our connections
 	recalc_station_type();
+	if (permissions_changed) {
+		check_missing_convoys();
+	}
 	rebuild_connections();
 	rebuild_linked_connections();
 	rebuild_connected_components();
@@ -2961,6 +3059,15 @@ void haltestelle_t::rdwr(loadsave_t *file)
 			financial_history[k][HALT_WALKED] = 0;
 		}
 	}
+	if(  file->is_version_atleast(124, 5)  ) {
+		file->rdwr_short(permissions);
+	} else {
+		permissions = 0;
+	}
+	if (file->is_loading()) {
+		// Ensure the permissions have all bits correctly set
+		set_permissions(permissions, false);
+	}
 }
 
 
@@ -3165,6 +3272,16 @@ void haltestelle_t::recalc_status()
  */
 void haltestelle_t::display_status(sint16 xpos, sint16 ypos)
 {
+	// Do we need to display permissions?
+	uint16 player_count = 0;
+	if(  !owner->is_public_service()  ) {  // Not if public stop
+		for(  uint16 i = 0;  i <	PLAYER_UNOWNED;  i++  ) {
+			if(  (permissions&(1<<i))  &&  welt->get_player(i)  &&  !welt->get_player(i)->is_public_service()  ) {
+				player_count += 1;
+			}
+		}
+	}
+
 	// ignore freight that cannot reach to this station
 	sint16 count = 0;
 	for(  uint16 i = 0;  i < goods_manager_t::get_count();  i++  ) {
@@ -3177,8 +3294,32 @@ void haltestelle_t::display_status(sint16 xpos, sint16 ypos)
 	}
 	ypos += -D_WAITINGBAR_WIDTH - LINESPACE/6;
 
-	if(  count != last_bar_count  ) {
-		// bars will shift x positions, mark entire station bar region dirty
+	bool players_dirty = false;
+	if(  permissions != last_permissions  ) {
+		if(  last_player_count  ) {
+			// erase old permissions display
+			const sint16 x = xpos - (last_player_count * 17 - gfx->get_tile_raster_width()) / 2;
+			gfx->mark_rect_dirty_wc( x, ypos, x + last_player_count * 17, ypos + D_WAITINGBAR_WIDTH );
+		}
+		last_permissions = permissions;
+		last_player_count = player_count;
+		players_dirty = true;
+	}
+	if(  player_count > 1  ) {
+		sint16 x = xpos - (player_count * 17 - 1 - gfx->get_tile_raster_width()) / 2;
+		for(  uint16 i = 0;  i <PLAYER_UNOWNED;  i++  ) {
+			if(  (permissions&(1<<i))  &&  welt->get_player(i)  &&  !welt->get_player(i)->is_public_service()  ) {
+				const PIXVAL color = gfx->palette_lookup(welt->get_player(i)->get_player_color1()+4);
+				gfx->draw_rect_clipped( x, ypos, 16, D_WAITINGBAR_WIDTH, color, false CLIP_NUM_DEFAULT);
+				x += 17;
+			}
+		}
+		ypos += -D_WAITINGBAR_WIDTH - 1;
+	}
+
+	if(  count != last_bar_count  ||  players_dirty  ) {
+		// bars will shift x positions, extra players may shift y positions,
+		// mark entire station bar region dirty
 		scr_coord_val max_bar_height = 0;
 		for(  sint16 i = 0;  i < last_bar_count;  i++  ) {
 			if(  last_bar_height[i] > max_bar_height  ) {
@@ -3302,45 +3443,7 @@ bool haltestelle_t::add_grund(grund_t *gr, bool relink_factories)
 		verbinde_fabriken();
 	}
 
-	// check if we have to register line(s) and/or lineless convoy(s) which serve this halt
-	vector_tpl<linehandle_t> check_line(0);
-
-	// public halt: must iterate over all players lines / convoys
-	bool public_halt = get_owner() == welt->get_public_player();
-
-	uint8 const pl_min = public_halt ? 0                : get_owner()->get_player_nr();
-	uint8 const pl_max = public_halt ? MAX_PLAYER_COUNT : get_owner()->get_player_nr()+1;
-	// iterate over all lines (public halt: all lines, other: only player's lines)
-	for(  uint8 i=pl_min;  i<pl_max;  i++  ) {
-		if(  player_t *player = welt->get_player(i)  ) {
-			player->simlinemgmt.get_lines(simline_t::line, &check_line);
-			for(linehandle_t const j : check_line  ) {
-				// only add unknown lines
-				if(  !registered_lines.is_contained(j)  &&  j->count_convoys() > 0  ) {
-					for(schedule_entry_t const& k : j->get_schedule()->entries  ) {
-						if(  get_halt(k.pos, player) == self  ) {
-							registered_lines.append(j);
-							break;
-						}
-					}
-				}
-			}
-		}
-	}
-	// iterate over all convoys
-	for(convoihandle_t const cnv : welt->convoys()) {
-		// only check lineless convoys which have matching ownership and which are not yet registered
-		if(  !cnv->get_line().is_bound()  &&  (public_halt  ||  cnv->get_owner()==get_owner())  &&  !registered_convoys.is_contained(cnv)  ) {
-			if(  const schedule_t *const schedule = cnv->get_schedule()  ) {
-				for(schedule_entry_t const& k : schedule->entries) {
-					if (get_halt(k.pos, cnv->get_owner()) == self) {
-						registered_convoys.append(cnv);
-						break;
-					}
-				}
-			}
-		}
-	}
+	check_missing_convoys();
 
 	// This entire loop is just for the assertion below.
 	// Consider deleting the assertion --neroden
diff --git src/simutrans/simhalt.h src/simutrans/simhalt.h
index 0e434ad80..25b15213c 100644
--- src/simutrans/simhalt.h
+++ src/simutrans/simhalt.h
@@ -116,6 +116,8 @@ private:
 	void init_financial_history();
 
 	PIXVAL status_color, last_status_color;
+	uint16 last_permissions;
+	uint16 last_player_count;
 	sint16 last_bar_count;
 	vector_tpl<scr_coord_val> last_bar_height; // caches the last height of the station bar for each good type drawn in display_status(). used for dirty tile management
 	uint32 capacity[3]; // passenger, mail, goods
@@ -313,6 +315,11 @@ private:
 	player_t *owner;
 	static karte_ptr_t welt;
 
+	/**
+	 * What players are allowed to stop here
+	 */
+	uint16 permissions;
+
 	/**
 	 * What is that for a station (for the image)
 	 */
@@ -391,6 +398,11 @@ public:
 
 	void remove_fabriken(fabrik_t *fab);
 
+	/**
+	 * Looks for unregistered lines/convoys stopping at halt
+	 */
+	void check_missing_convoys();
+
 	/**
 	 * Rebuilds the list of connections to reachable halts
 	 * returns the search number of connections
@@ -700,6 +712,20 @@ public:
 
 	void set_name(const char *name);
 
+	uint16 get_permissions() const { return permissions; }
+
+	/**
+	 * @return whether a player is allowed to stop here
+	 */
+	bool can_player_stop(const player_t *player) const;
+
+	/**
+	 * Sets permissions
+	 * Owner and public service are always allowed
+	 * if @p reroute is true, the halt's connections are rebuilt
+	 */
+	void set_permissions(uint16 perms, bool reroute=true);
+
 	// create an unique name: better to be called with valid handle, although it will work without
 	char* create_name(koord k, char const* typ);
 
diff --git src/simutrans/tool/simmenu.cc src/simutrans/tool/simmenu.cc
index c9938eb4f..7061f77cf 100644
--- src/simutrans/tool/simmenu.cc
+++ src/simutrans/tool/simmenu.cc
@@ -157,6 +157,7 @@ const char* tool_t::id_to_string(uint16 id)
 			CASE_TO_STRING(TOOL_CHANGE_TRAFFIC_LIGHT);
 			CASE_TO_STRING(TOOL_CHANGE_CITY);
 			CASE_TO_STRING(TOOL_RENAME);
+			CASE_TO_STRING(TOOL_SET_PERMISSION);
 			CASE_TO_STRING(TOOL_TOGGLE_RESERVATION);
 			CASE_TO_STRING(TOOL_VIEW_OWNER);
 			CASE_TO_STRING(TOOL_HIDE_UNDER_CURSOR);
@@ -340,6 +341,7 @@ tool_t* create_simple_tool(int toolnr)
 	case TOOL_LOAD_SCENARIO:        tool = new tool_load_scenario_t();        break;
 	case TOOL_DAY_NIGHT_TOGGLE:     tool = new tool_day_night_toggle_t();     break;
 	case TOOL_SINGLE_WAY_TOOGLE:    tool = new tool_show_single_ways_t();     break;
+	case TOOL_SET_PERMISSION:       tool = new tool_change_permission_t();     break;
 	case UNUSED_TOOL_ADD_MESSAGE: // fall-through - intended!!!111elf
 	case UNUSED_WKZ_PWDHASH_TOOL:
 		dbg->warning("create_simple_tool()", "Deprecated tool [%i] requested", toolnr);
@@ -1279,7 +1281,7 @@ void toolbar_last_used_t::clear()
 // currently only needed for last used tools
 void toolbar_last_used_t::append(tool_t* t, player_t* sp)
 {
-	static int exclude_from_adding[8] = {
+	static int exclude_from_adding[9] = {
 		TOOL_SCHEDULE_ADD | GENERAL_TOOL,
 		TOOL_SCHEDULE_INS | GENERAL_TOOL,
 		TOOL_CHANGE_CONVOI | SIMPLE_TOOL,
@@ -1287,7 +1289,8 @@ void toolbar_last_used_t::append(tool_t* t, player_t* sp)
 		TOOL_CHANGE_DEPOT | SIMPLE_TOOL,
 		UNUSED_WKZ_PWDHASH_TOOL | SIMPLE_TOOL,
 		TOOL_CHANGE_PLAYER | SIMPLE_TOOL,
-		TOOL_RENAME | SIMPLE_TOOL
+		TOOL_RENAME | SIMPLE_TOOL,
+		TOOL_SET_PERMISSION | SIMPLE_TOOL
 	};
 
 	if (!sp || t->get_icon(sp) == IMG_EMPTY) {
diff --git src/simutrans/tool/simmenu.h src/simutrans/tool/simmenu.h
index 7522550d7..c1adaaa1c 100644
--- src/simutrans/tool/simmenu.h
+++ src/simutrans/tool/simmenu.h
@@ -133,6 +133,7 @@ enum {
 	TOOL_DAY_NIGHT_TOGGLE,
 	TOOL_SINGLE_WAY_TOOGLE,
 	TOOL_WORK_MAP,
+	TOOL_SET_PERMISSION,
 	SIMPLE_TOOL_COUNT,
 	SIMPLE_TOOL = 0x2000
 };
diff --git src/simutrans/tool/simtool.cc src/simutrans/tool/simtool.cc
index 7e311a8c2..aa744d944 100644
--- src/simutrans/tool/simtool.cc
+++ src/simutrans/tool/simtool.cc
@@ -2504,6 +2504,7 @@ const char *tool_plant_groundobj_t::work( player_t *player, koord3d pos )
  * the following routines add waypoints/halts to a schedule
  * because we do not like to stop at AIs stop, but we still want to force the truck to use AI roads
  * So if there is a halt, then it must be either public or ours!
+ * (Except if permission has been explicitely granted.)
  */
 static const char *tool_schedule_insert_aux(karte_t *welt, player_t *player, koord3d pos, schedule_t *schedule, bool append)
 {
@@ -2535,7 +2536,7 @@ static const char *tool_schedule_insert_aux(karte_t *welt, player_t *player, koo
 				return "Das Feld gehoert\neinem anderen Spieler\n";
 			}
 		}
-		if(  bd->is_halt()  &&  !player_t::check_owner( player, bd->get_halt()->get_owner()) ) {
+		if(  bd->is_halt()  &&  !bd->get_halt()->can_player_stop(player)) {
 			return "Das Feld gehoert\neinem anderen Spieler\n";
 		}
 		// ok, now we have a valid ground
@@ -8702,6 +8703,23 @@ bool tool_rename_t::init(player_t *player)
 	return false;
 }
 
+bool tool_change_permission_t::init(player_t *player)
+{
+	uint16 id = 0, perms = 0;
+	const char *p = default_param;
+
+	id = atoi(p);
+	while(  *p>0  &&  *p++!=','  );
+	perms = atoi(p);
+
+	halthandle_t halt;
+	halt.set_id(id);
+	if(  halt.is_bound()  &&  player_t::check_owner(halt->get_owner(), player)  ) {
+		halt->set_permissions(perms);
+	}
+	return false;
+}
+
 bool tool_recolour_t::init(player_t *)
 {
 	// skip the rest of the command
@@ -6650,7 +6650,7 @@ uint8 tool_stop_mover_t::is_valid_pos(  player_t *player, const koord3d &pos, co
 	}
 	// check halt ownership
 	halthandle_t h = haltestelle_t::get_halt(pos,player);
-	if(  h.is_bound()  &&  !player_t::check_owner( player, h->get_owner() )  ) {
+	if(  h.is_bound()  &&  !h->can_player_stop(player)  ) {
 		error = "Das Feld gehoert\neinem anderen Spieler\n";
 		return 0;
 	}
diff --git src/simutrans/tool/simtool.h src/simutrans/tool/simtool.h
index fed73cfcd..a22f900eb 100644
--- src/simutrans/tool/simtool.h
+++ src/simutrans/tool/simtool.h
@@ -1313,7 +1313,7 @@ public:
 	// work is not safe, has to be send over network
 };
 
-// internal tool: rename stuff
+// internal tool: set owner
 class tool_change_owner_t : public tool_t {
 public:
 	tool_change_owner_t() : tool_t(TOOL_SET_OWNER | GENERAL_TOOL) {}
@@ -1322,4 +1322,12 @@ public:
 	// work is not safe, has to be send over network
 };
 
+// internal tool: set permissions
+class tool_change_permission_t : public tool_t {
+public:
+	tool_change_permission_t() : tool_t(TOOL_SET_PERMISSION | SIMPLE_TOOL) {}
+	bool init(player_t*) OVERRIDE;
+	bool is_init_keeps_game_state() const OVERRIDE { return false; }
+};
+
 #endif
