ConnectDialog.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #include "ConnectDialog.h"
  2. #include <QProcess>
  3. #include <QString>
  4. #include <QDir>
  5. #include <QFileInfo>
  6. #include <QTcpSocket>
  7. #include <QtAlgorithms>
  8. #include <cassert>
  9. #ifdef _WIN32
  10. #include "SspiNegotiateClient.h"
  11. #include "QAbstractSocketStreamWrapper.h"
  12. #include <QHostAddress>
  13. #include <fstream>
  14. #include <winsock2.h>
  15. using namespace openmsx;
  16. #else
  17. #include <pwd.h>
  18. #include <sys/socket.h>
  19. #include <sys/un.h>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #endif
  23. // Helper functions to setup a connection
  24. static QString getUserName()
  25. {
  26. #ifdef _WIN32
  27. return "default";
  28. #else
  29. struct passwd* pw = getpwuid(getuid());
  30. return pw->pw_name ? pw->pw_name : "";
  31. #endif
  32. }
  33. static bool checkSocketDir(const QDir& dir)
  34. {
  35. if (!dir.exists()) {
  36. return false;
  37. }
  38. #ifndef _WIN32
  39. // only do permission and owner checks on *nix
  40. QFileInfo info(dir.absolutePath());
  41. if (info.ownerId() != getuid()) {
  42. return false;
  43. }
  44. int all = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
  45. QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup |
  46. QFile::ReadOther | QFile::WriteOther | QFile::ExeOther;
  47. QFile::Permissions needed = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
  48. if ((info.permissions() & all) != needed) {
  49. return false;
  50. }
  51. #endif
  52. return true;
  53. }
  54. static bool checkSocket(const QFileInfo& info)
  55. {
  56. if (!info.fileName().startsWith("socket.")) {
  57. // wrong name
  58. return false;
  59. }
  60. #ifndef _WIN32
  61. // only do permission and owner checks on *nix
  62. int all = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
  63. QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup |
  64. QFile::ReadOther | QFile::WriteOther | QFile::ExeOther;
  65. QFile::Permissions needed = QFile::ReadOwner | QFile::WriteOwner;
  66. if ((info.permissions() & all) != needed) {
  67. return false;
  68. }
  69. if (info.ownerId() != getuid()) {
  70. return false;
  71. }
  72. #endif
  73. return true;
  74. }
  75. static void deleteSocket(const QFileInfo& info)
  76. {
  77. QFile::remove(info.absoluteFilePath()); // ignore errors
  78. QDir dir;
  79. dir.rmdir(info.absolutePath()); // ignore errors
  80. }
  81. static OpenMSXConnection* createConnection(const QDir& dir, const QString& socketName)
  82. {
  83. QFileInfo info(dir, socketName);
  84. if (!checkSocket(info)) {
  85. // invalid socket
  86. return NULL;
  87. }
  88. QAbstractSocket* socket = NULL;
  89. #ifdef _WIN32
  90. int port = -1;
  91. std::ifstream in(info.absoluteFilePath().toLatin1().data());
  92. in >> port;
  93. if (port != -1) {
  94. QHostAddress localhost(QHostAddress::LocalHost);
  95. socket = new QTcpSocket();
  96. socket->connectToHost(localhost, port);
  97. QAbstractSocketStreamWrapper stream(socket);
  98. SspiNegotiateClient client(stream);
  99. if (!socket->waitForConnected(1000) ||
  100. !client.Authenticate()) {
  101. delete socket;
  102. socket = NULL;
  103. }
  104. }
  105. #else
  106. int sd = ::socket(AF_UNIX, SOCK_STREAM, 0);
  107. if (sd != -1) {
  108. sockaddr_un addr;
  109. addr.sun_family = AF_UNIX;
  110. strcpy(addr.sun_path, info.absoluteFilePath().toLatin1().data());
  111. if (connect(sd, (sockaddr*)&addr, sizeof(addr)) != -1) {
  112. socket = new QTcpSocket();
  113. if (!socket->setSocketDescriptor(sd)) {
  114. // failed to wrap socket in QTcpSocket
  115. delete socket;
  116. socket = NULL;
  117. close(sd);
  118. }
  119. } else {
  120. // failed to connect to UNIX socket
  121. close(sd);
  122. }
  123. }
  124. #endif
  125. if (socket) {
  126. return new OpenMSXConnection(socket);
  127. } else {
  128. // cannot connect, must be a stale socket, try to clean it up
  129. deleteSocket(socketName);
  130. return NULL;
  131. }
  132. }
  133. static void collectServers(QList<OpenMSXConnection*>& servers)
  134. {
  135. #ifdef _WIN32
  136. DWORD len = GetTempPathW(0, nullptr);
  137. assert(len > 0); // nothing we can do to recover this
  138. //VLA(wchar_t, bufW, (len+1));
  139. //wchar_t bufW[len+1];
  140. auto bufW = static_cast<wchar_t*>(_alloca(sizeof(wchar_t) * (len+1)));
  141. len = GetTempPathW(len, bufW);
  142. assert(len > 0); // nothing we can do to recover this
  143. QDir dir(QString::fromWCharArray(bufW, len));
  144. #else
  145. QDir dir((getenv("TMPDIR")) ? getenv("TMPDIR") : QDir::tempPath());
  146. #endif
  147. dir.cd("openmsx-" + getUserName());
  148. if (!checkSocketDir(dir)) {
  149. // no correct socket directory
  150. return;
  151. }
  152. QDir::Filters filters =
  153. #ifdef _WIN32
  154. QDir::Files; // regular files for win32
  155. #else
  156. QDir::System; // sockets for *nix
  157. #endif
  158. foreach (QString name, dir.entryList(filters)) {
  159. if (OpenMSXConnection* connection = createConnection(dir, name)) {
  160. servers.push_back(connection);
  161. }
  162. }
  163. }
  164. // ConnectionInfoRequest class
  165. ConnectionInfoRequest::ConnectionInfoRequest(
  166. ConnectDialog& dialog_, OpenMSXConnection& connection_)
  167. : dialog(dialog_), connection(connection_)
  168. {
  169. state = GET_MACHINE;
  170. connection.sendCommand(this);
  171. }
  172. QString ConnectionInfoRequest::getCommand() const
  173. {
  174. switch (state) {
  175. case GET_MACHINE:
  176. return "machine_info config_name";
  177. case GET_TITLE:
  178. return "guess_title";
  179. default:
  180. assert(false);
  181. return "";
  182. }
  183. }
  184. void ConnectionInfoRequest::replyOk(const QString& message)
  185. {
  186. switch (state) {
  187. case GET_MACHINE:
  188. title = message;
  189. state = GET_TITLE;
  190. connection.sendCommand(this);
  191. break;
  192. case GET_TITLE: {
  193. if (!message.isEmpty()) {
  194. title += " (" + message + ")";
  195. }
  196. dialog.connectionOk(connection, title);
  197. state = DONE;
  198. connect(&connection, SIGNAL(disconnected()),
  199. this, SLOT(terminate()));
  200. break;
  201. }
  202. default:
  203. assert(false);
  204. }
  205. }
  206. void ConnectionInfoRequest::replyNok(const QString& /*message*/)
  207. {
  208. cancel();
  209. }
  210. void ConnectionInfoRequest::cancel()
  211. {
  212. dialog.connectionBad(connection);
  213. }
  214. void ConnectionInfoRequest::terminate()
  215. {
  216. cancel();
  217. }
  218. // class ConnectDialog
  219. OpenMSXConnection* ConnectDialog::getConnection(QWidget* parent)
  220. {
  221. ConnectDialog dialog(parent);
  222. // delay for at most 500ms while checking the connections
  223. dialog.delay = 1;
  224. dialog.startTimer(500);
  225. while (!dialog.pendingConnections.empty() && dialog.delay) {
  226. qApp->processEvents(QEventLoop::AllEvents, 200);
  227. }
  228. // if there is only one valid connection, use it immediately,
  229. // otherwise execute the dialog.
  230. if (dialog.pendingConnections.empty() && dialog.confirmedConnections.size() == 1 ) {
  231. dialog.on_connectButton_clicked();
  232. } else {
  233. dialog.exec();
  234. }
  235. return dialog.result;
  236. }
  237. ConnectDialog::ConnectDialog(QWidget* parent)
  238. : QDialog(parent)
  239. , result(NULL)
  240. {
  241. ui.setupUi(this);
  242. on_rescanButton_clicked();
  243. }
  244. ConnectDialog::~ConnectDialog()
  245. {
  246. clear();
  247. }
  248. void ConnectDialog::timerEvent(QTimerEvent* event)
  249. {
  250. killTimer(event->timerId());
  251. delay = 0;
  252. }
  253. void ConnectDialog::clear()
  254. {
  255. ui.listConnections->clear();
  256. // First kill the infos. That will remove the disconnect signals.
  257. qDeleteAll(connectionInfos);
  258. connectionInfos.clear();
  259. qDeleteAll(pendingConnections);
  260. pendingConnections.clear();
  261. qDeleteAll(confirmedConnections);
  262. confirmedConnections.clear();
  263. }
  264. void ConnectDialog::on_connectButton_clicked()
  265. {
  266. int row = ui.listConnections->currentRow();
  267. if (row != -1) {
  268. assert((0 <= row) && (row < confirmedConnections.size()));
  269. result = confirmedConnections[row];
  270. confirmedConnections.removeAt(row);
  271. }
  272. accept();
  273. }
  274. void ConnectDialog::on_rescanButton_clicked()
  275. {
  276. clear();
  277. collectServers(pendingConnections);
  278. foreach (OpenMSXConnection* connection, pendingConnections) {
  279. connectionInfos.append(new ConnectionInfoRequest(*this, *connection));
  280. }
  281. }
  282. void ConnectDialog::connectionOk(OpenMSXConnection& connection,
  283. const QString& title)
  284. {
  285. OpenMSXConnections::iterator it = qFind(pendingConnections.begin(),
  286. pendingConnections.end(),
  287. &connection);
  288. if (it == pendingConnections.end()) {
  289. // connection is already beiing destoyed
  290. return;
  291. }
  292. pendingConnections.erase(it);
  293. confirmedConnections.push_back(&connection);
  294. new QListWidgetItem(title, ui.listConnections);
  295. if (ui.listConnections->count() == 1) {
  296. // automatically select first row
  297. ui.listConnections->setCurrentRow(0);
  298. }
  299. }
  300. void ConnectDialog::connectionBad(OpenMSXConnection& connection)
  301. {
  302. OpenMSXConnections::iterator it = qFind(pendingConnections.begin(),
  303. pendingConnections.end(),
  304. &connection);
  305. if (it == pendingConnections.end()) {
  306. // was this connection established but terminated?
  307. int id = confirmedConnections.indexOf(&connection);
  308. if (id >= 0) {
  309. OpenMSXConnection* conn = confirmedConnections.takeAt(id);
  310. QListWidgetItem* item = ui.listConnections->takeItem(id);
  311. delete item;
  312. conn->deleteLater();
  313. }
  314. return;
  315. }
  316. pendingConnections.erase(it);
  317. connection.deleteLater();
  318. }