OXYGENHUB
◆ main openmsx / src / events / CliServer.cc
6.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "CliServer.hh"
2
3
#include "CliConnection.hh"
4
#include "FileOperations.hh"
5
#include "GlobalCliComm.hh"
6
#include "MSXException.hh"
7
8
#include "one_of.hh"
9
#include "random.hh"
10
#include "xrange.hh"
11
12
#include <bit>
13
#include <memory>
14
#include <string>
15
16
#ifdef _WIN32
17
#include <fstream>
18
#include <ctime>
19
#else
20
#include <fcntl.h>
21
#include <pwd.h>
22
#include <unistd.h>
23
#endif
24
25
namespace openmsx {
26
27
[[nodiscard]] static std::string getUserName()
28
{
29
#ifdef _WIN32
30
	return "default";
31
#else
32
	const struct passwd* pw = getpwuid(getuid());
33
	return (pw && pw->pw_name) ? pw->pw_name : std::string{};
34
#endif
35
}
36
37
[[nodiscard]] static bool checkSocketDir(zstring_view dir)
38
{
39
	auto st = FileOperations::getStat(dir);
40
	if (!st) {
41
		// error during stat()
42
		return false;
43
	}
44
	if (!FileOperations::isDirectory(*st)) {
45
		// not a directory
46
		return false;
47
	}
48
#ifndef _WIN32
49
	// only do permission and owner checks on *nix
50
	if ((st->st_mode & 0777) != 0700) {
51
		// wrong permissions
52
		return false;
53
	}
54
	if (st->st_uid != getuid()) {
55
		// wrong uid
56
		return false;
57
	}
58
#endif
59
	return true;
60
}
61
62
[[nodiscard]] static bool checkSocket(zstring_view socket)
63
{
64
	if (auto name = FileOperations::getFilename(socket);
65
	    !name.starts_with("socket.")) {
66
		return false; // wrong name
67
	}
68
69
	auto st = FileOperations::getStat(socket);
70
	if (!st) {
71
		// error during stat()
72
		return false;
73
	}
74
#ifdef _WIN32
75
	if (!FileOperations::isRegularFile(*st)) {
76
		// not a regular file
77
		return false;
78
	}
79
#else
80
	if (!S_ISSOCK(st->st_mode)) {
81
		// not a socket
82
		return false;
83
	}
84
#endif
85
#ifndef _WIN32
86
	// only do permission and owner checks on *nix
87
	if ((st->st_mode & 0777) != 0600) {
88
		// check will be different on win32 (!= 777) thus actually useless
89
		// wrong permissions
90
		return false;
91
	}
92
	if (st->st_uid != getuid()) {
93
		// does this work on win32? is this check meaningful?
94
		// wrong uid
95
		return false;
96
	}
97
#endif
98
	return true;
99
}
100
101
#ifdef _WIN32
102
[[nodiscard]] static int openPort(SOCKET listenSock)
103
{
104
	const int BASE = 9938;
105
	const int RANGE = 64;
106
107
	int first = random_int(0, RANGE - 1); // [0, RANGE)
108
109
	for (auto n : xrange(RANGE)) {
110
		int port = BASE + ((first + n) % RANGE);
111
		sockaddr_in server_address;
112
		memset(&server_address, 0, sizeof(server_address));
113
		server_address.sin_family = AF_INET;
114
		server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
115
		server_address.sin_port = htons(port);
116
		if (bind(listenSock, std::bit_cast<sockaddr*>(&server_address),
117
		         sizeof(server_address)) != -1) {
118
			return port;
119
		}
120
	}
121
	throw MSXException("Couldn't open socket.");
122
}
123
#endif
124
125
SOCKET CliServer::createSocket()
126
{
127
	auto dir = tmpStrCat(FileOperations::getTempDir(), "/openmsx-", getUserName());
128
	FileOperations::mkdir(dir, 0700);
129
	if (!checkSocketDir(dir)) {
130
		throw MSXException("Couldn't create socket directory.");
131
	}
132
	socketName = strCat(dir, "/socket.", int(getpid()));
133
134
#ifdef _WIN32
135
	SOCKET sd = socket(AF_INET, SOCK_STREAM, 0);
136
	if (sd == OPENMSX_INVALID_SOCKET) {
137
		throw MSXException(sock_error());
138
	}
139
	int portNumber = openPort(sd);
140
141
	// write port number to file
142
	FileOperations::unlink(socketName); // ignore error
143
	std::ofstream out;
144
	FileOperations::openOfStream(out, socketName);
145
	out << portNumber << '\n';
146
	if (!out.good()) {
147
		sock_close(sd);
148
		throw MSXException("Couldn't write socket port file.");
149
	}
150
151
#else
152
	SOCKET sd = socket(AF_UNIX, SOCK_STREAM, 0);
153
	if (sd == OPENMSX_INVALID_SOCKET) {
154
		throw MSXException(sock_error());
155
	}
156
157
	FileOperations::unlink(socketName); // ignore error
158
159
	sockaddr_un addr;
160
	strncpy(addr.sun_path, socketName.c_str(), sizeof(addr.sun_path) - 1);
161
	addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
162
	addr.sun_family = AF_UNIX;
163
164
	if (bind(sd, std::bit_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
165
		sock_close(sd);
166
		throw MSXException("Couldn't bind socket.");
167
	}
168
	if (chmod(socketName.c_str(), 0600) == -1) {
169
		sock_close(sd);
170
		throw MSXException("Couldn't set socket permissions.");
171
	}
172
173
#endif
174
	if (!checkSocket(socketName)) {
175
		sock_close(sd);
176
		throw MSXException("Opened socket fails sanity check.");
177
	}
178
	if (listen(sd, SOMAXCONN) == SOCKET_ERROR) {
179
		sock_close(sd);
180
		throw MSXException("Couldn't listen to socket: ", sock_error());
181
	}
182
	return sd;
183
}
184
185
void CliServer::exitAcceptLoop()
186
{
187
	sock_close(listenSock);
188
	poller.abort();
189
}
190
191
static void deleteSocket(const std::string& socket)
192
{
193
	FileOperations::unlink(socket); // ignore errors
194
	auto dir = socket.substr(0, socket.find_last_of('/'));
195
	FileOperations::rmdir(dir); // ignore errors
196
}
197
198
199
CliServer::CliServer(CommandController& commandController_,
200
                     EventDistributor& eventDistributor_,
201
                     GlobalCliComm& cliComm_)
202
	: commandController(commandController_)
203
	, eventDistributor(eventDistributor_)
204
	, cliComm(cliComm_)
205
{
206
	try {
207
		listenSock = createSocket();
208
		thread = std::thread([this]() { mainLoop(); });
209
	} catch (MSXException& e) {
210
		cliComm.printWarning(e.getMessage());
211
	}
212
}
213
214
CliServer::~CliServer()
215
{
216
	if (listenSock != OPENMSX_INVALID_SOCKET) {
217
		exitAcceptLoop();
218
		thread.join();
219
	}
220
221
	deleteSocket(socketName);
222
}
223
224
void CliServer::mainLoop()
225
{
226
#ifndef _WIN32
227
	// Set socket to non-blocking to make sure accept() doesn't hang when
228
	// a connection attempt is dropped between poll() and accept().
229
	fcntl(listenSock, F_SETFL, O_NONBLOCK);
230
#endif
231
	while (true) {
232
		// wait for incoming connection
233
		// Note: On Windows, closing the socket is sufficient to exit the
234
		//       accept() call.
235
#ifndef _WIN32
236
		if (poller.poll(listenSock)) {
237
			break;
238
		}
239
#endif
240
		SOCKET sd = accept(listenSock, nullptr, nullptr);
241
		if (poller.aborted()) {
242
			if (sd != OPENMSX_INVALID_SOCKET) {
243
				sock_close(sd);
244
			}
245
			break;
246
		}
247
		if (sd == OPENMSX_INVALID_SOCKET) {
248
			if (errno == one_of(EAGAIN, EWOULDBLOCK)) {
249
				continue;
250
			} else {
251
				break;
252
			}
253
		}
254
#ifndef _WIN32
255
		// The BSD/OSX sockets implementation inherits O_NONBLOCK, while Linux
256
		// does not. To be on the safe side, we explicitly reset file flags.
257
		fcntl(sd, F_SETFL, 0);
258
#endif
259
		cliComm.addListener(std::make_unique<SocketConnection>(
260
			commandController, eventDistributor, sd));
261
	}
262
}
263
264
} // namespace openmsx