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