Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision f3025861da728fb4754f9d1cdcfeda5675ac98f3)
+++ boot/Makefile.common	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -105,4 +105,5 @@
 	$(USPACE_PATH)/srv/fs/exfat/exfat \
 	$(USPACE_PATH)/srv/fs/ext2fs/ext2fs \
+	$(USPACE_PATH)/srv/hid/remcons/remcons \
 	$(USPACE_PATH)/srv/taskmon/taskmon \
 	$(USPACE_PATH)/srv/net/nil/eth/eth \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision f3025861da728fb4754f9d1cdcfeda5675ac98f3)
+++ uspace/Makefile	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -93,4 +93,5 @@
 	srv/hid/fb \
 	srv/hid/input \
+	srv/hid/remcons \
 	srv/hw/char/s3c24xx_uart \
 	srv/net/il/arp \
Index: uspace/srv/hid/remcons/Makefile
===================================================================
--- uspace/srv/hid/remcons/Makefile	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/Makefile	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2012 Vojtech Horky
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../../..
+BINARY = remcons
+
+SOURCES = \
+	remcons.c \
+	user.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/hid/remcons/remcons.c
===================================================================
--- uspace/srv/hid/remcons/remcons.c	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/remcons.c	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2012 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup remcons
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <stdio.h>
+#include <adt/prodcons.h>
+#include <ipc/input.h>
+#include <ipc/console.h>
+#include <ipc/vfs.h>
+#include <errno.h>
+#include <str_error.h>
+#include <loc.h>
+#include <event.h>
+#include <io/keycode.h>
+#include <align.h>
+#include <malloc.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <task.h>
+#include <net/in.h>
+#include <net/inet.h>
+#include <net/socket.h>
+#include <io/console.h>
+#include <inttypes.h>
+#include "telnet.h"
+#include "user.h"
+
+#define APP_GETTERM  "/app/getterm"
+#define APP_SHELL "/app/bdsh"
+
+/** Telnet commands to force character mode
+ * (redundant to be on the safe side).
+ * See
+ * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
+ * for discussion.
+ */
+static const telnet_cmd_t telnet_force_character_mode_command[] = {
+	TELNET_IAC, TELNET_WILL, TELNET_ECHO,
+	TELNET_IAC, TELNET_WILL, TELNET_SUPPRESS_GO_AHEAD,
+	TELNET_IAC, TELNET_WONT, TELNET_LINEMODE
+};
+static const size_t telnet_force_character_mode_command_count =
+    sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
+
+
+/** Handling client requests (VFS and console interface).
+ *
+ * @param user Telnet user the requests belong to.
+ */
+static void client_connection_message_loop(telnet_user_t *user)
+{
+	while (true) {
+		ipc_call_t call;
+		ipc_callid_t callid = 0;
+
+		/*
+		 * The getterm task might terminate while we are here,
+		 * waiting for a call. Also, the socket might be closed
+		 * meanwhile.
+		 * We want to detect this situation early, so we use a
+		 * timeout variant of async_get_call().
+		 */
+		while (callid == 0) {
+			callid = async_get_call_timeout(&call, 1000);
+
+			if (telnet_user_is_zombie(user)) {
+				if (callid != 0) {
+					async_answer_0(callid, EINTR);
+				}
+				return;
+			}
+		}
+		
+		if (!IPC_GET_IMETHOD(call)) {
+			return;
+		}
+
+		switch (IPC_GET_IMETHOD(call)) {
+		case CONSOLE_GET_SIZE:
+			async_answer_2(callid, EOK, 100, 1);
+			break;
+		case CONSOLE_GET_POS:
+			fibril_mutex_lock(&user->guard);
+			async_answer_2(callid, EOK, user->cursor_x, 0);
+			fibril_mutex_unlock(&user->guard);
+			break;
+		case CONSOLE_GET_EVENT: {
+			kbd_event_t event;
+			int rc = telnet_user_get_next_keyboard_event(user, &event);
+			if (rc != EOK) {
+				/* Silently ignore. */
+				async_answer_0(callid, EOK);
+				break;
+			}
+			async_answer_4(callid, EOK, event.type, event.key, event.mods, event.c);
+			break;
+		}
+		case CONSOLE_GOTO: {
+			int new_x = IPC_GET_ARG1(call);
+			telnet_user_update_cursor_x(user, new_x);
+			async_answer_0(callid, ENOTSUP);
+			break;
+		}
+		case VFS_OUT_READ:
+			async_answer_0(callid, ENOTSUP);
+			break;
+		case VFS_OUT_WRITE: {
+			uint8_t *buf;
+			size_t size;
+			int rc = async_data_write_accept((void **)&buf, false, 0, 0, 0, &size);
+
+			if (rc != EOK) {
+				async_answer_0(callid, rc);
+				break;
+			}
+
+			rc = telnet_user_send_data(user, buf, size);
+			free(buf);
+
+			if (rc != EOK) {
+				async_answer_0(callid, rc);
+				break;
+			}
+
+			async_answer_1(callid, EOK, size);
+			break;
+		}
+		case VFS_OUT_SYNC:
+			async_answer_0(callid, EOK);
+			break;
+		case CONSOLE_CLEAR:
+			async_answer_0(callid, EOK);
+			break;
+
+		case CONSOLE_GET_COLOR_CAP:
+			async_answer_1(callid, EOK, CONSOLE_CAP_NONE);
+			break;
+		case CONSOLE_SET_STYLE:
+			async_answer_0(callid, ENOTSUP);
+			break;
+		case CONSOLE_SET_COLOR:
+			async_answer_0(callid, ENOTSUP);
+			break;
+		case CONSOLE_SET_RGB_COLOR:
+			async_answer_0(callid, ENOTSUP);
+			break;
+
+		case CONSOLE_CURSOR_VISIBILITY:
+			async_answer_0(callid, ENOTSUP);
+			break;
+
+		default:
+			async_answer_0(callid, EINVAL);
+			break;
+		}
+	}
+}
+
+/** Callback when client connects to a telnet terminal. */
+static void client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	/* Find the user. */
+	telnet_user_t *user = telnet_user_get_for_client_connection(IPC_GET_ARG1(*icall));
+	if (user == NULL) {
+		async_answer_0(iid, ENOENT);
+		return;
+	}
+	async_answer_0(iid, EOK);
+
+	telnet_user_log(user, "New client connected (%" PRIxn").", iid);
+
+	/* Force character mode. */
+	send(user->socket, (void *)telnet_force_character_mode_command,
+	    telnet_force_character_mode_command_count, 0);
+
+	/* Handle messages. */
+	client_connection_message_loop(user);
+
+	telnet_user_notify_client_disconnected(user);
+	telnet_user_log(user, "Client disconnected (%" PRIxn").", iid);
+}
+
+/** Fibril for spawning the task running after user connects.
+ *
+ * @param arg Corresponding @c telnet_user_t structure.
+ */
+static int spawn_task_fibril(void *arg)
+{
+	telnet_user_t *user = arg;
+	int rc;
+
+	char term[LOC_NAME_MAXLEN];
+	snprintf(term, LOC_NAME_MAXLEN, "%s/%s", "/loc", user->service_name);
+
+	task_id_t task;
+	rc = task_spawnl(&task, APP_GETTERM, APP_GETTERM, "-w", term, APP_SHELL, NULL);
+	if (rc != EOK) {
+		telnet_user_error(user, "Spawning `%s -w %s %s' failed: %s.",
+		    APP_GETTERM, term, APP_SHELL, str_error(rc));
+		fibril_mutex_lock(&user->guard);
+		user->task_finished = true;
+		fibril_condvar_signal(&user->refcount_cv);
+		fibril_mutex_unlock(&user->guard);
+		return EOK;
+	}
+
+	fibril_mutex_lock(&user->guard);
+	user->task_id = task;
+	fibril_mutex_unlock(&user->guard);
+
+	task_exit_t task_exit;
+	int task_retval;
+	task_wait(task, &task_exit, &task_retval);
+	telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
+	    task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
+	    task_retval);
+
+	/* Announce destruction. */
+	fibril_mutex_lock(&user->guard);
+	user->task_finished = true;
+	fibril_condvar_signal(&user->refcount_cv);
+	fibril_mutex_unlock(&user->guard);
+
+	return EOK;
+}
+
+/** Tell whether given user can be destroyed (has no active clients).
+ *
+ * @param user The telnet user in question.
+ */
+static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
+{
+	return user->task_finished && user->socket_closed &&
+	    (user->locsrv_connection_count == 0);
+}
+
+/** Fibril for each accepted socket.
+ *
+ * @param arg Corresponding @c telnet_user_t structure.
+ */
+static int network_user_fibril(void *arg)
+{
+	int rc;
+	telnet_user_t *user = arg;
+
+	rc = loc_service_register(user->service_name, &user->service_id);
+	if (rc != EOK) {
+		telnet_user_error(user, "Unable to register %s with loc: %s.",
+		    user->service_name, str_error(rc));
+		return EOK;
+	}
+
+	telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
+	    user->service_name, user->service_id);
+
+	fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
+	assert(spawn_fibril);
+	fibril_add_ready(spawn_fibril);
+
+	/* Wait for all clients to exit. */
+	fibril_mutex_lock(&user->guard);
+	while (!user_can_be_destroyed_no_lock(user)) {
+		if (user->task_finished) {
+			closesocket(user->socket);
+			user->socket_closed = true;
+			continue;
+		} else if (user->socket_closed) {
+			if (user->task_id != 0) {
+				task_kill(user->task_id);
+			}
+		}
+		fibril_condvar_wait_timeout(&user->refcount_cv, &user->guard, 1000);
+	}
+	fibril_mutex_unlock(&user->guard);
+
+	rc = loc_service_unregister(user->service_id);
+	if (rc != EOK) {
+		telnet_user_error(user,
+		    "Unable to unregister %s from loc: %s (ignored).",
+		    user->service_name, str_error(rc));
+	}
+
+	telnet_user_log(user, "Destroying...");
+	telnet_user_destroy(user);
+
+	return EOK;
+}
+
+int main(int argc, char *argv[])
+{
+	int port = 2223;
+	
+	async_set_client_connection(client_connection);
+	int rc = loc_server_register(NAME);
+	if (rc < 0) {
+		fprintf(stderr, NAME ": Unable to register server: %s.\n",
+		    str_error(rc));
+		return 1;
+	}
+
+	struct sockaddr_in addr;
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+
+	rc = inet_pton(AF_INET, "127.0.0.1", (void *)
+	    &addr.sin_addr.s_addr);
+	if (rc != EOK) {
+		fprintf(stderr, "Error parsing network address: %s.\n",
+		    str_error(rc));
+		return 2;
+	}
+
+	int listen_sd = socket(PF_INET, SOCK_STREAM, 0);
+	if (listen_sd < 0) {
+		fprintf(stderr, "Error creating listening socket: %s.\n",
+		    str_error(listen_sd));
+		return 3;
+	}
+
+	rc = bind(listen_sd, (struct sockaddr *) &addr, sizeof(addr));
+	if (rc != EOK) {
+		fprintf(stderr, "Error binding socket: %s.\n",
+		    str_error(rc));
+		return 4;
+	}
+
+	rc = listen(listen_sd, BACKLOG_SIZE);
+	if (rc != EOK) {
+		fprintf(stderr, "listen() failed: %s.\n", str_error(rc));
+		return 5;
+	}
+
+	printf("%s: HelenOS Remote console service\n", NAME);
+
+	while (true) {
+		struct sockaddr_in raddr;
+		socklen_t raddr_len = sizeof(raddr);
+		int conn_sd = accept(listen_sd, (struct sockaddr *) &raddr,
+		    &raddr_len);
+
+		if (conn_sd < 0) {
+			fprintf(stderr, "accept() failed: %s.\n",
+			    str_error(rc));
+			continue;
+		}
+
+		telnet_user_t *user = telnet_user_create(conn_sd);
+		assert(user);
+
+		fid_t fid = fibril_create(network_user_fibril, user);
+		assert(fid);
+		fibril_add_ready(fid);
+	}
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/remcons/remcons.h
===================================================================
--- uspace/srv/hid/remcons/remcons.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/remcons.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup remcons
+ * @{
+ */
+/** @file
+ */
+
+#ifndef REMCONS_H_
+#define REMCONS_H_
+
+#define NAME       "remcons"
+#define NAMESPACE  "term"
+#define BACKLOG_SIZE  5
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/hid/remcons/telnet.h
===================================================================
--- uspace/srv/hid/remcons/telnet.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/telnet.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup remcons
+ * @{
+ */
+/** @file
+ */
+
+#ifndef TELNET_H_
+#define TELNET_H_
+
+#include <inttypes.h>
+
+typedef uint8_t telnet_cmd_t;
+
+/*
+ * Telnet commands.
+ */
+
+#define TELNET_IAC 255
+
+#define TELNET_WILL 251
+#define TELNET_WONT 252
+#define TELNET_DO 253
+#define TELNET_DONT 254
+
+#define TELNET_IS_OPTION_CODE(code) (((code) >= 251) && ((code) <= 254))
+
+#define TELNET_ECHO 1
+#define TELNET_SUPPRESS_GO_AHEAD 3
+#define TELNET_LINEMODE 34
+
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/hid/remcons/user.c
===================================================================
--- uspace/srv/hid/remcons/user.c	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/user.c	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2012 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup remcons
+ * @{
+ */
+/** @file
+ */
+#include <async.h>
+#include <stdio.h>
+#include <adt/prodcons.h>
+#include <ipc/input.h>
+#include <ipc/console.h>
+#include <ipc/vfs.h>
+#include <errno.h>
+#include <str_error.h>
+#include <loc.h>
+#include <event.h>
+#include <io/keycode.h>
+#include <align.h>
+#include <malloc.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <task.h>
+#include <net/in.h>
+#include <net/inet.h>
+#include <net/socket.h>
+#include <io/console.h>
+#include <inttypes.h>
+#include <assert.h>
+#include "user.h"
+#include "telnet.h"
+
+static FIBRIL_MUTEX_INITIALIZE(users_guard);
+static LIST_INITIALIZE(users);
+
+/** Create new telnet user.
+ *
+ * @param socket Socket the user communicates through.
+ * @return New telnet user or NULL when out of memory.
+ */
+telnet_user_t *telnet_user_create(int socket)
+{
+	static int telnet_user_id_counter = 0;
+
+	telnet_user_t *user = malloc(sizeof(telnet_user_t));
+	if (user == NULL) {
+		return NULL;
+	}
+
+	user->id = ++telnet_user_id_counter;
+
+	int rc = asprintf(&user->service_name, "%s/telnet%d", NAMESPACE, user->id);
+	if (rc < 0) {
+		free(user);
+		return NULL;
+	}
+
+	user->socket = socket;
+	user->service_id = (service_id_t) -1;
+	prodcons_initialize(&user->in_events);
+	link_initialize(&user->link);
+	user->socket_buffer_len = 0;
+	user->socket_buffer_pos = 0;
+
+	fibril_condvar_initialize(&user->refcount_cv);
+	fibril_mutex_initialize(&user->guard);
+	user->task_finished = false;
+	user->socket_closed = false;
+	user->locsrv_connection_count = 0;
+
+
+	fibril_mutex_lock(&users_guard);
+	list_append(&user->link, &users);
+	fibril_mutex_unlock(&users_guard);
+
+	user->cursor_x = 0;
+
+	return user;
+}
+
+/** Destroy telnet user structure.
+ *
+ * @param user User to be destroyed.
+ */
+void telnet_user_destroy(telnet_user_t *user)
+{
+	assert(user);
+
+	fibril_mutex_lock(&users_guard);
+	list_remove(&user->link);
+	fibril_mutex_unlock(&users_guard);
+
+	free(user);
+}
+
+/** Find user by service id and increments reference counter.
+ *
+ * @param id Location service id of the telnet user's terminal.
+ */
+telnet_user_t *telnet_user_get_for_client_connection(service_id_t id)
+{
+	telnet_user_t *user = NULL;
+
+	fibril_mutex_lock(&users_guard);
+	list_foreach(users, link) {
+		telnet_user_t *tmp = list_get_instance(link, telnet_user_t, link);
+		if (tmp->service_id == id) {
+			user = tmp;
+			break;
+		}
+	}
+	if (user == NULL) {
+		fibril_mutex_unlock(&users_guard);
+		return NULL;
+	}
+
+	telnet_user_t *tmp = user;
+	fibril_mutex_lock(&tmp->guard);
+	user->locsrv_connection_count++;
+
+	/*
+	 * Refuse to return user whose task already finished or when
+	 * the socket is already closed().
+	 */
+	if (user->task_finished || user->socket_closed) {
+		user = NULL;
+		user->locsrv_connection_count--;
+	}
+
+	fibril_mutex_unlock(&tmp->guard);
+
+
+	fibril_mutex_unlock(&users_guard);
+
+	return user;
+}
+
+/** Notify that client disconnected from the remote terminal.
+ *
+ * @param user To which user the client was connected.
+ */
+void telnet_user_notify_client_disconnected(telnet_user_t *user)
+{
+	fibril_mutex_lock(&user->guard);
+	assert(user->locsrv_connection_count > 0);
+	user->locsrv_connection_count--;
+	fibril_condvar_signal(&user->refcount_cv);
+	fibril_mutex_unlock(&user->guard);
+}
+
+/** Tell whether the launched task already exited and socket is already closed.
+ *
+ * @param user Telnet user in question.
+ */
+bool telnet_user_is_zombie(telnet_user_t *user)
+{
+	fibril_mutex_lock(&user->guard);
+	bool zombie = user->socket_closed || user->task_finished;
+	fibril_mutex_unlock(&user->guard);
+
+	return zombie;
+}
+
+/** Receive next byte from a socket (use buffering.
+ * We need to return the value via extra argument because the read byte
+ * might be negative.
+ */
+static int telnet_user_recv_next_byte_no_lock(telnet_user_t *user, char *byte)
+{
+	/* No more buffered data? */
+	if (user->socket_buffer_len <= user->socket_buffer_pos) {
+		int recv_length = recv(user->socket, user->socket_buffer, BUFFER_SIZE, 0);
+		if ((recv_length == 0) || (recv_length == ENOTCONN)) {
+			user->socket_closed = true;
+			return ENOENT;
+		}
+		if (recv_length < 0) {
+			return recv_length;
+		}
+		user->socket_buffer_len = recv_length;
+		user->socket_buffer_pos = 0;
+	}
+
+	*byte = user->socket_buffer[user->socket_buffer_pos++];
+
+	return EOK;
+}
+
+/** Creates new keyboard event from given char.
+ *
+ * @param type Event type (press / release).
+ * @param c Pressed character.
+ */
+static kbd_event_t* new_kbd_event(kbd_event_type_t type, wchar_t c) {
+	kbd_event_t *event = malloc(sizeof(kbd_event_t));
+	assert(event);
+
+	link_initialize(&event->link);
+	event->type = type;
+	event->c = c;
+	event->mods = 0;
+
+	switch (c) {
+	case '\n':
+		event->key = KC_ENTER;
+		break;
+	case '\t':
+		event->key = KC_TAB;
+		break;
+	case '\b':
+	case 127: /* This is what Linux telnet sends. */
+		event->key = KC_BACKSPACE;
+		event->c = '\b';
+		break;
+	default:
+		event->key = KC_A;
+		break;
+	}
+
+	return event;
+}
+
+/** Process telnet command (currently only print to screen).
+ *
+ * @param user Telnet user structure.
+ * @param option_code Command option code.
+ * @param cmd Telnet command.
+ */
+static void process_telnet_command(telnet_user_t *user,
+    telnet_cmd_t option_code, telnet_cmd_t cmd)
+{
+	if (option_code != 0) {
+		telnet_user_log(user, "Ignoring telnet command %u %u %u.",
+		    TELNET_IAC, option_code, cmd);
+	} else {
+		telnet_user_log(user, "Ignoring telnet command %u %u.",
+		    TELNET_IAC, cmd);
+	}
+}
+
+/** Get next keyboard event.
+ *
+ * @param user Telnet user.
+ * @param event Where to store the keyboard event.
+ * @return Error code.
+ */
+int telnet_user_get_next_keyboard_event(telnet_user_t *user, kbd_event_t *event)
+{
+	fibril_mutex_lock(&user->guard);
+	if (list_empty(&user->in_events.list)) {
+		char next_byte = 0;
+		bool inside_telnet_command = false;
+
+		telnet_cmd_t telnet_option_code = 0;
+
+		/* Skip zeros, bail-out on error. */
+		while (next_byte == 0) {
+			int rc = telnet_user_recv_next_byte_no_lock(user, &next_byte);
+			if (rc != EOK) {
+				fibril_mutex_unlock(&user->guard);
+				return rc;
+			}
+			uint8_t byte = (uint8_t) next_byte;
+
+			/* Skip telnet commands. */
+			if (inside_telnet_command) {
+				inside_telnet_command = false;
+				next_byte = 0;
+				if (TELNET_IS_OPTION_CODE(byte)) {
+					telnet_option_code = byte;
+					inside_telnet_command = true;
+				} else {
+					process_telnet_command(user,
+					    telnet_option_code, byte);
+				}
+			}
+			if (byte == TELNET_IAC) {
+				inside_telnet_command = true;
+				next_byte = 0;
+			}
+		}
+
+		/* CR-LF conversions. */
+		if (next_byte == 13) {
+			next_byte = 10;
+		}
+
+		kbd_event_t *down = new_kbd_event(KEY_PRESS, next_byte);
+		kbd_event_t *up = new_kbd_event(KEY_RELEASE, next_byte);
+		assert(down);
+		assert(up);
+		prodcons_produce(&user->in_events, &down->link);
+		prodcons_produce(&user->in_events, &up->link);
+	}
+
+	link_t *link = prodcons_consume(&user->in_events);
+	kbd_event_t *tmp = list_get_instance(link, kbd_event_t, link);
+
+	fibril_mutex_unlock(&user->guard);
+
+	*event = *tmp;
+
+	free(tmp);
+
+	return EOK;
+}
+
+/** Send data (convert them first) to the socket, no locking.
+ *
+ * @param user Telnet user.
+ * @param data Data buffer (not zero terminated).
+ * @param size Size of @p data buffer in bytes.
+ */
+static int telnet_user_send_data_no_lock(telnet_user_t *user, uint8_t *data, size_t size)
+{
+	uint8_t *converted = malloc(3 * size + 1);
+	assert(converted);
+	int converted_size = 0;
+
+	/* Convert new-lines. */
+	for (size_t i = 0; i < size; i++) {
+		if (data[i] == 10) {
+			converted[converted_size++] = 13;
+			converted[converted_size++] = 10;
+			user->cursor_x = 0;
+		} else {
+			converted[converted_size++] = data[i];
+			if (data[i] == '\b') {
+				user->cursor_x--;
+			} else {
+				user->cursor_x++;
+			}
+		}
+	}
+
+
+	int rc = send(user->socket, converted, converted_size, 0);
+	free(converted);
+
+	return rc;
+}
+
+/** Send data (convert them first) to the socket.
+ *
+ * @param user Telnet user.
+ * @param data Data buffer (not zero terminated).
+ * @param size Size of @p data buffer in bytes.
+ */
+int telnet_user_send_data(telnet_user_t *user, uint8_t *data, size_t size)
+{
+	fibril_mutex_lock(&user->guard);
+
+	int rc = telnet_user_send_data_no_lock(user, data, size);
+
+	fibril_mutex_unlock(&user->guard);
+
+	return rc;
+}
+
+/** Update cursor X position.
+ *
+ * This call may result in sending control commands over socket.
+ *
+ * @param user Telnet user.
+ * @param new_x New cursor location.
+ */
+void telnet_user_update_cursor_x(telnet_user_t *user, int new_x)
+{
+	fibril_mutex_lock(&user->guard);
+	if (user->cursor_x - 1 == new_x) {
+		uint8_t data = '\b';
+		/* Ignore errors. */
+		telnet_user_send_data_no_lock(user, &data, 1);
+	}
+	user->cursor_x = new_x;
+	fibril_mutex_unlock(&user->guard);
+
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/hid/remcons/user.h
===================================================================
--- uspace/srv/hid/remcons/user.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
+++ uspace/srv/hid/remcons/user.h	(revision a1347a7d9d2bcb49b7e781408978b65acb44ea89)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2012 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup remcons
+ * @{
+ */
+/** @file
+ */
+
+#ifndef TELNET_USER_H_
+#define TELNET_USER_H_
+
+#include <fibril_synch.h>
+#include <inttypes.h>
+#include "remcons.h"
+
+#define BUFFER_SIZE 32
+
+/** Representation of a connected (human) user. */
+typedef struct {
+	/** Mutex guarding the whole structure. */
+	fibril_mutex_t guard;
+
+	/** Internal id, used for creating locfs entries. */
+	int id;
+	/** Associated socket. */
+	int socket;
+	/** Location service id assigned to the virtual terminal. */
+	service_id_t service_id;
+	/** Path name of the service. */
+	char *service_name;
+
+	/** Producer-consumer of kbd_event_t. */
+	prodcons_t in_events;
+	link_t link;
+	char socket_buffer[BUFFER_SIZE];
+	size_t socket_buffer_len;
+	size_t socket_buffer_pos;
+
+	/** Task id of the launched application. */
+	task_id_t task_id;
+
+	/* Reference counting. */
+	fibril_condvar_t refcount_cv;
+	bool task_finished;
+	int locsrv_connection_count;
+	bool socket_closed;
+
+	/** X position of the cursor. */
+	int cursor_x;
+} telnet_user_t;
+
+telnet_user_t *telnet_user_create(int socket);
+void telnet_user_destroy(telnet_user_t *user);
+telnet_user_t *telnet_user_get_for_client_connection(service_id_t id);
+bool telnet_user_is_zombie(telnet_user_t *user);
+void telnet_user_notify_client_disconnected(telnet_user_t *user);
+int telnet_user_get_next_keyboard_event(telnet_user_t *user, kbd_event_t *event);
+int telnet_user_send_data(telnet_user_t *user, uint8_t *data, size_t size);
+void telnet_user_update_cursor_x(telnet_user_t *user, int new_x);
+
+/** Print informational message about connected user. */
+#define telnet_user_log(user, fmt, ...) \
+	printf(NAME " [console %d (%d)]: " fmt "\n", user->id, (int) user->service_id, ##__VA_ARGS__)
+
+/** Print error message associated with connected user. */
+#define telnet_user_error(user, fmt, ...) \
+	fprintf(stderr, NAME " [console %d (%d)]: ERROR: " fmt "\n", user->id, (int) user->service_id, ##__VA_ARGS__)
+
+#endif
+
+/**
+ * @}
+ */
