Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Client.h"
8#include <AK/DeprecatedString.h>
9#include <AK/HashMap.h>
10#include <AK/Types.h>
11#include <LibCore/ArgsParser.h>
12#include <LibCore/DeprecatedFile.h>
13#include <LibCore/EventLoop.h>
14#include <LibCore/Socket.h>
15#include <LibCore/TCPServer.h>
16#include <LibMain/Main.h>
17#include <fcntl.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <sys/ioctl.h>
21#include <unistd.h>
22
23static void run_command(int ptm_fd, DeprecatedString command)
24{
25 pid_t pid = fork();
26 if (pid == 0) {
27 char const* tty_name = ptsname(ptm_fd);
28 if (!tty_name) {
29 perror("ptsname");
30 exit(1);
31 }
32 close(ptm_fd);
33 int pts_fd = open(tty_name, O_RDWR);
34 if (pts_fd < 0) {
35 perror("open");
36 exit(1);
37 }
38
39 // NOTE: It's okay if this fails.
40 [[maybe_unused]] auto rc = ioctl(0, TIOCNOTTY);
41
42 close(0);
43 close(1);
44 close(2);
45
46 rc = dup2(pts_fd, 0);
47 if (rc < 0) {
48 perror("dup2");
49 exit(1);
50 }
51 rc = dup2(pts_fd, 1);
52 if (rc < 0) {
53 perror("dup2");
54 exit(1);
55 }
56 rc = dup2(pts_fd, 2);
57 if (rc < 0) {
58 perror("dup2");
59 exit(1);
60 }
61 rc = close(pts_fd);
62 if (rc < 0) {
63 perror("close");
64 exit(1);
65 }
66 rc = ioctl(0, TIOCSCTTY);
67 if (rc < 0) {
68 perror("ioctl(TIOCSCTTY)");
69 exit(1);
70 }
71 char const* args[4] = { "/bin/Shell", nullptr, nullptr, nullptr };
72 if (!command.is_empty()) {
73 args[1] = "-c";
74 args[2] = command.characters();
75 }
76 char const* envs[] = { "TERM=xterm", "PATH=" DEFAULT_PATH, nullptr };
77 rc = execve("/bin/Shell", const_cast<char**>(args), const_cast<char**>(envs));
78 if (rc < 0) {
79 perror("execve");
80 exit(1);
81 }
82 VERIFY_NOT_REACHED();
83 }
84}
85
86ErrorOr<int> serenity_main(Main::Arguments arguments)
87{
88 int port = 23;
89 StringView command = ""sv;
90
91 Core::ArgsParser args_parser;
92 args_parser.add_option(port, "Port to listen on", nullptr, 'p', "port");
93 args_parser.add_option(command, "Program to run on connection", nullptr, 'c', "command");
94 args_parser.parse(arguments);
95
96 if ((u16)port != port) {
97 warnln("Invalid port number: {}", port);
98 exit(1);
99 }
100
101 Core::EventLoop event_loop;
102 auto server = TRY(Core::TCPServer::try_create());
103 TRY(server->listen({}, port));
104
105 HashMap<int, NonnullRefPtr<Client>> clients;
106 int next_id = 0;
107
108 server->on_ready_to_accept = [&next_id, &clients, &server, command] {
109 int id = next_id++;
110
111 auto maybe_client_socket = server->accept();
112 if (maybe_client_socket.is_error()) {
113 warnln("accept: {}", maybe_client_socket.error());
114 return;
115 }
116 auto client_socket = maybe_client_socket.release_value();
117
118 int ptm_fd = posix_openpt(O_RDWR);
119 if (ptm_fd < 0) {
120 perror("posix_openpt");
121 client_socket->close();
122 return;
123 }
124 if (grantpt(ptm_fd) < 0) {
125 perror("grantpt");
126 client_socket->close();
127 return;
128 }
129 if (unlockpt(ptm_fd) < 0) {
130 perror("unlockpt");
131 client_socket->close();
132 return;
133 }
134
135 run_command(ptm_fd, command);
136
137 auto maybe_client = Client::create(id, move(client_socket), ptm_fd);
138 if (maybe_client.is_error()) {
139 warnln("Failed to create the client: {}", maybe_client.error());
140 return;
141 }
142
143 auto client = maybe_client.release_value();
144 client->on_exit = [&clients, id] {
145 Core::deferred_invoke([&clients, id] { clients.remove(id); });
146 };
147 clients.set(id, client);
148 };
149
150 return event_loop.exec();
151}