summaryrefslogtreecommitdiff
path: root/Services/TelnetServer/Client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Services/TelnetServer/Client.cpp')
-rw-r--r--Services/TelnetServer/Client.cpp188
1 files changed, 188 insertions, 0 deletions
diff --git a/Services/TelnetServer/Client.cpp b/Services/TelnetServer/Client.cpp
new file mode 100644
index 0000000000..e983025df6
--- /dev/null
+++ b/Services/TelnetServer/Client.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+ */
+
+#include "Client.h"
+#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/Notifier.h>
+#include <LibCore/TCPSocket.h>
+#include <stdio.h>
+#include <unistd.h>
+
+Client::Client(int id, RefPtr<Core::TCPSocket> socket, int ptm_fd)
+ : m_id(id)
+ , m_socket(move(socket))
+ , m_ptm_fd(ptm_fd)
+ , m_ptm_notifier(Core::Notifier::construct(ptm_fd, Core::Notifier::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()
+{
+ NonnullRefPtr<Client> protect(*this);
+ 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, (size_t)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 (size_t 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 (size_t 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.data(), buffer.size());
+}
+
+void Client::quit()
+{
+ m_ptm_notifier->set_enabled(false);
+ close(m_ptm_fd);
+ m_socket->close();
+ if (on_exit)
+ on_exit();
+}