summaryrefslogtreecommitdiff
path: root/Servers/TelnetServer
diff options
context:
space:
mode:
authorConrad Pankoff <deoxxa@fknsrs.biz>2019-09-08 17:51:28 +1000
committerAndreas Kling <awesomekling@gmail.com>2019-09-08 10:56:34 +0200
commit040947ee47270fa45a046f90bd4d4498b1c0ee91 (patch)
tree8f0cbbf5be2ca2c7a0d6707f3bb82762106eb4a5 /Servers/TelnetServer
parent423807d772ba31d847940ec5096a8fda60e99c41 (diff)
downloadserenity-040947ee47270fa45a046f90bd4d4498b1c0ee91.zip
TelnetServer: Implement basic telnet server
Fixes #407 Depends on #530 to run reliably.
Diffstat (limited to 'Servers/TelnetServer')
-rw-r--r--Servers/TelnetServer/Client.cpp162
-rw-r--r--Servers/TelnetServer/Client.h43
-rw-r--r--Servers/TelnetServer/Command.h56
-rw-r--r--Servers/TelnetServer/Makefile25
-rw-r--r--Servers/TelnetServer/Parser.cpp63
-rw-r--r--Servers/TelnetServer/Parser.h33
-rw-r--r--Servers/TelnetServer/main.cpp137
7 files changed, 519 insertions, 0 deletions
diff --git a/Servers/TelnetServer/Client.cpp b/Servers/TelnetServer/Client.cpp
new file mode 100644
index 0000000000..485bb23134
--- /dev/null
+++ b/Servers/TelnetServer/Client.cpp
@@ -0,0 +1,162 @@
+#include <AK/BufferStream.h>
+#include <AK/ByteBuffer.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+#include <LibCore/CNotifier.h>
+#include <LibCore/CTCPSocket.h>
+#include <stdio.h>
+
+#include "Client.h"
+
+Client::Client(int id, CTCPSocket* socket, int ptm_fd)
+ : m_id(id)
+ , m_socket(socket)
+ , m_ptm_fd(ptm_fd)
+ , m_ptm_notifier(ptm_fd, CNotifier::Read)
+{
+ m_socket->on_ready_to_read = [this] { drain_socket(); };
+ m_ptm_notifier.on_ready_to_read = [this] { drain_pty(); };
+ m_parser.on_command = [this](const Command& command) { handle_command(command); };
+ m_parser.on_data = [this](const StringView& data) { handle_data(data); };
+ m_parser.on_error = [this]() { handle_error(); };
+ send_commands({
+ { CMD_WILL, SUB_SUPPRESS_GO_AHEAD },
+ { CMD_WILL, SUB_ECHO },
+ { CMD_DO, SUB_SUPPRESS_GO_AHEAD },
+ { CMD_DONT, SUB_ECHO },
+ });
+}
+
+void Client::drain_socket()
+{
+ while (m_socket->can_read()) {
+ auto buf = m_socket->read(1024);
+
+ m_parser.write(buf);
+
+ if (m_socket->eof()) {
+ quit();
+ break;
+ }
+ }
+}
+
+void Client::drain_pty()
+{
+ u8 buffer[BUFSIZ];
+ ssize_t nread = read(m_ptm_fd, buffer, sizeof(buffer));
+ if (nread < 0) {
+ perror("read(ptm)");
+ quit();
+ return;
+ }
+ if (nread == 0) {
+ quit();
+ return;
+ }
+ send_data(StringView(buffer, nread));
+}
+
+void Client::handle_data(const StringView& data)
+{
+ write(m_ptm_fd, data.characters_without_null_termination(), data.length());
+}
+
+void Client::handle_command(const Command& command)
+{
+ switch (command.command) {
+ case CMD_DO:
+ // no response - we've already advertised our options, and none of
+ // them can be disabled (or re-enabled) after connecting.
+ break;
+ case CMD_DONT:
+ // no response - we only "support" two options (echo and suppres
+ // go-ahead), and both of them are always enabled.
+ break;
+ case CMD_WILL:
+ switch (command.subcommand) {
+ case SUB_ECHO:
+ // we always want to be the ones in control of the output. tell
+ // the client to disable local echo.
+ send_command({ CMD_DONT, SUB_ECHO });
+ break;
+ case SUB_SUPPRESS_GO_AHEAD:
+ send_command({ CMD_DO, SUB_SUPPRESS_GO_AHEAD });
+ break;
+ default:
+ // don't respond to unknown commands
+ break;
+ }
+ break;
+ case CMD_WONT:
+ // no response - we don't care about anything the client says they
+ // won't do.
+ break;
+ }
+}
+
+void Client::handle_error()
+{
+ quit();
+}
+
+void Client::send_data(StringView data)
+{
+ bool fast = true;
+ for (int i = 0; i < data.length(); i++) {
+ u8 c = data[i];
+ if (c == '\n' || c == 0xff)
+ fast = false;
+ }
+
+ if (fast) {
+ m_socket->write(data);
+ return;
+ }
+
+ StringBuilder builder;
+ for (int i = 0; i < data.length(); i++) {
+ u8 c = data[i];
+
+ switch (c) {
+ case '\n':
+ builder.append("\r\n");
+ break;
+ case IAC:
+ builder.append("\xff\xff");
+ break;
+ default:
+ builder.append(c);
+ break;
+ }
+ }
+
+ m_socket->write(builder.to_string());
+}
+
+
+void Client::send_command(Command command)
+{
+ send_commands({ command });
+}
+
+void Client::send_commands(Vector<Command> commands)
+{
+ auto buffer = ByteBuffer::create_uninitialized(commands.size() * 3);
+ BufferStream stream(buffer);
+ for (auto& command : commands)
+ stream << (u8)IAC << command.command << command.subcommand;
+ stream.snip();
+ m_socket->write(buffer.pointer(), buffer.size());
+}
+
+void Client::quit()
+{
+ m_ptm_notifier.set_enabled(false);
+ close(m_ptm_fd);
+ m_socket->close();
+ if (on_exit)
+ on_exit();
+}
diff --git a/Servers/TelnetServer/Client.h b/Servers/TelnetServer/Client.h
new file mode 100644
index 0000000000..83956f8879
--- /dev/null
+++ b/Servers/TelnetServer/Client.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+#include <LibCore/CNotifier.h>
+#include <LibCore/CTCPSocket.h>
+
+#include "Command.h"
+#include "Parser.h"
+
+class Client : public RefCounted<Client> {
+public:
+ static NonnullRefPtr<Client> create(int id, CTCPSocket* socket, int ptm_fd)
+ {
+ return adopt(*new Client(id, socket, ptm_fd));
+ }
+
+ Function<void()> on_exit;
+
+protected:
+ Client(int id, CTCPSocket* socket, int ptm_fd);
+
+ void drain_socket();
+ void drain_pty();
+ void handle_data(const StringView&);
+ void handle_command(const Command& command);
+ void handle_error();
+ void send_data(StringView str);
+ void send_command(Command command);
+ void send_commands(Vector<Command> commands);
+ void quit();
+
+private:
+ // client id
+ int m_id { 0 };
+ // client resources
+ CTCPSocket* m_socket { nullptr };
+ Parser m_parser;
+ // pty resources
+ int m_ptm_fd { -1 };
+ CNotifier m_ptm_notifier;
+};
diff --git a/Servers/TelnetServer/Command.h b/Servers/TelnetServer/Command.h
new file mode 100644
index 0000000000..bfe210e724
--- /dev/null
+++ b/Servers/TelnetServer/Command.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+
+#define CMD_WILL 0xfb
+#define CMD_WONT 0xfc
+#define CMD_DO 0xfd
+#define CMD_DONT 0xfe
+#define SUB_ECHO 0x01
+#define SUB_SUPPRESS_GO_AHEAD 0x03
+
+struct Command {
+ u8 command;
+ u8 subcommand;
+
+ String to_string() const
+ {
+ StringBuilder builder;
+
+ switch (command) {
+ case CMD_WILL:
+ builder.append("WILL");
+ break;
+ case CMD_WONT:
+ builder.append("WONT");
+ break;
+ case CMD_DO:
+ builder.append("DO");
+ break;
+ case CMD_DONT:
+ builder.append("DONT");
+ break;
+ default:
+ builder.append(String::format("UNKNOWN<%02x>", command));
+ break;
+ }
+
+ builder.append(" ");
+
+ switch (subcommand) {
+ case SUB_ECHO:
+ builder.append("ECHO");
+ break;
+ case SUB_SUPPRESS_GO_AHEAD:
+ builder.append("SUPPRESS_GO_AHEAD");
+ break;
+ default:
+ builder.append(String::format("UNKNOWN<%02x>"));
+ break;
+ }
+
+ return builder.to_string();
+ };
+};
diff --git a/Servers/TelnetServer/Makefile b/Servers/TelnetServer/Makefile
new file mode 100644
index 0000000000..ab0373e705
--- /dev/null
+++ b/Servers/TelnetServer/Makefile
@@ -0,0 +1,25 @@
+include ../../Makefile.common
+
+ECHOSERVER_OBJS = \
+ Client.o \
+ Parser.o \
+ main.o
+
+APP = TelnetServer
+OBJS = $(ECHOSERVER_OBJS)
+
+DEFINES += -DUSERLAND
+
+all: $(APP)
+
+$(APP): $(OBJS)
+ $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lc -lcore
+
+.cpp.o:
+ @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+-include $(OBJS:%.o=%.d)
+
+clean:
+ @echo "CLEAN"; rm -f $(APP) $(OBJS) *.d
+
diff --git a/Servers/TelnetServer/Parser.cpp b/Servers/TelnetServer/Parser.cpp
new file mode 100644
index 0000000000..6c05ce7e2e
--- /dev/null
+++ b/Servers/TelnetServer/Parser.cpp
@@ -0,0 +1,63 @@
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+
+#include "Parser.h"
+
+void Parser::write(const StringView& data)
+{
+ for (int i = 0; i < data.length(); i++) {
+ u8 ch = data[i];
+
+ switch (m_state) {
+ case State::Free:
+ switch (ch) {
+ case IAC:
+ m_state = State::ReadCommand;
+ break;
+ case '\r':
+ if (on_data)
+ on_data("\n");
+ break;
+ default:
+ if (on_data)
+ on_data(StringView(&ch, 1));
+ break;
+ }
+ break;
+ case State::ReadCommand:
+ switch (ch) {
+ case IAC: {
+ m_state = State::Free;
+ if (on_data)
+ on_data("\xff");
+ break;
+ }
+ case CMD_WILL:
+ case CMD_WONT:
+ case CMD_DO:
+ case CMD_DONT:
+ m_command = ch;
+ m_state = State::ReadSubcommand;
+ break;
+ default:
+ m_state = State::Error;
+ if (on_error)
+ on_error();
+ break;
+ }
+ break;
+ case State::ReadSubcommand: {
+ auto command = m_command;
+ m_command = 0;
+ m_state = State::Free;
+ if (on_command)
+ on_command({ command, ch });
+ break;
+ }
+ case State::Error:
+ // ignore everything
+ break;
+ }
+ }
+}
diff --git a/Servers/TelnetServer/Parser.h b/Servers/TelnetServer/Parser.h
new file mode 100644
index 0000000000..24700a0149
--- /dev/null
+++ b/Servers/TelnetServer/Parser.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+
+#include "Command.h"
+
+#define IAC 0xff
+
+class Parser {
+public:
+ Function<void(const Command&)> on_command;
+ Function<void(const StringView&)> on_data;
+ Function<void()> on_error;
+
+ void write(const StringView&);
+
+protected:
+ enum State {
+ Free,
+ ReadCommand,
+ ReadSubcommand,
+ Error,
+ };
+
+ void write(const String& str);
+
+private:
+ State m_state { State::Free };
+ u8 m_command { 0 };
+};
diff --git a/Servers/TelnetServer/main.cpp b/Servers/TelnetServer/main.cpp
new file mode 100644
index 0000000000..d8cfd364c8
--- /dev/null
+++ b/Servers/TelnetServer/main.cpp
@@ -0,0 +1,137 @@
+#include <AK/BufferStream.h>
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/IPv4Address.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <LibCore/CEventLoop.h>
+#include <LibCore/CTCPServer.h>
+#include <LibCore/CTCPSocket.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+
+#include "Client.h"
+
+static void run_command(int ptm_fd, String command)
+{
+ pid_t pid = fork();
+ if (pid == 0) {
+ const char* tty_name = ptsname(ptm_fd);
+ if (!tty_name) {
+ perror("ptsname");
+ exit(1);
+ }
+ close(ptm_fd);
+ int pts_fd = open(tty_name, O_RDWR);
+ if (pts_fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ // NOTE: It's okay if this fails.
+ (void)ioctl(0, TIOCNOTTY);
+
+ close(0);
+ close(1);
+ close(2);
+
+ int rc = dup2(pts_fd, 0);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = dup2(pts_fd, 1);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = dup2(pts_fd, 2);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = close(pts_fd);
+ if (rc < 0) {
+ perror("close");
+ exit(1);
+ }
+ rc = ioctl(0, TIOCSCTTY);
+ if (rc < 0) {
+ perror("ioctl(TIOCSCTTY)");
+ exit(1);
+ }
+ const char* args[4] = { "/bin/Shell", nullptr, nullptr, nullptr };
+ if (!command.is_empty()) {
+ args[1] = "-c";
+ args[2] = command.characters();
+ }
+ const char* envs[] = { "TERM=xterm", "PATH=/bin:/usr/bin:/usr/local/bin", nullptr };
+ rc = execve("/bin/Shell", const_cast<char**>(args), const_cast<char**>(envs));
+ if (rc < 0) {
+ perror("execve");
+ exit(1);
+ }
+ ASSERT_NOT_REACHED();
+ }
+}
+
+int main(int argc, char** argv)
+{
+ CEventLoop event_loop;
+ CTCPServer server;
+
+ int opt;
+ u16 port = 23;
+ while ((opt = getopt(argc, argv, "p:")) != -1) {
+ switch (opt) {
+ case 'p':
+ port = atoi(optarg);
+ break;
+ default:
+ fprintf(stderr, "Usage: %s [-p port]", argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!server.listen({}, port)) {
+ perror("listen");
+ exit(1);
+ }
+
+ HashMap<int, NonnullRefPtr<Client>> clients;
+ int next_id = 0;
+
+ server.on_ready_to_accept = [&next_id, &clients, &server] {
+ int id = next_id++;
+
+ auto* client_socket = server.accept();
+ if (!client_socket) {
+ perror("accept");
+ return;
+ }
+
+ int ptm_fd = open("/dev/ptmx", O_RDWR);
+ if (ptm_fd < 0) {
+ perror("open(ptmx)");
+ client_socket->close();
+ return;
+ }
+
+ run_command(ptm_fd, "");
+
+ auto client = Client::create(id, client_socket, ptm_fd);
+ client->on_exit = [&clients, id] { clients.remove(id); };
+ clients.set(id, client);
+ };
+
+ int rc = event_loop.exec();
+ if (rc != 0) {
+ fprintf(stderr, "event loop exited badly; rc=%d", rc);
+ exit(1);
+ }
+
+ return 0;
+}