File indexing completed on 2024-05-05 09:12:58
0001 /* 0002 SPDX-FileCopyrightText: 2018 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 Work sponsored by the LiMux project of the city of Munich 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "vncsshtunnelthread.h" 0009 #include "krdc_debug.h" 0010 0011 #include <KLocalizedString> 0012 0013 #include <fcntl.h> 0014 #include <arpa/inet.h> 0015 #include <netinet/in.h> 0016 #include <sys/socket.h> 0017 0018 #include <QDebug> 0019 0020 VncSshTunnelThread::VncSshTunnelThread(const QByteArray &host, int vncPort, int tunnelPort, int sshPort, const QByteArray &sshUserName, bool loopback) : 0021 m_host(host), 0022 m_vncPort(vncPort), 0023 m_tunnelPort(tunnelPort), 0024 m_sshPort(sshPort), 0025 m_sshUserName(sshUserName), 0026 m_loopback(loopback), 0027 m_stop_thread(false) 0028 { 0029 } 0030 0031 VncSshTunnelThread::~VncSshTunnelThread() 0032 { 0033 m_stop_thread = true; 0034 wait(); 0035 } 0036 0037 0038 int VncSshTunnelThread::tunnelPort() const 0039 { 0040 return m_tunnelPort; 0041 } 0042 0043 QString VncSshTunnelThread::password() const 0044 { 0045 return m_password; 0046 } 0047 0048 // This is called by the main thread, but from a slot connected to our signal via BlockingQueuedConnection 0049 // so this is safe even without a mutex, the semaphore in BlockingQueuedConnection takes care of the synchronization. 0050 void VncSshTunnelThread::setPassword(const QString &password, PasswordOrigin origin) 0051 { 0052 m_password = password; 0053 m_passwordOrigin = origin; 0054 } 0055 0056 // This is called by the main thread, but from a slot connected to our signal via BlockingQueuedConnection 0057 // so this is safe even without a mutex, the semaphore in BlockingQueuedConnection takes care of the synchronization. 0058 void VncSshTunnelThread::userCanceledPasswordRequest() 0059 { 0060 m_passwordRequestCanceledByUser = true; 0061 } 0062 0063 void VncSshTunnelThread::run() 0064 { 0065 struct CleanupHelper 0066 { 0067 int server_sock = -1; 0068 int client_sock = -1; 0069 ssh_session session = nullptr; 0070 ssh_channel forwarding_channel = nullptr; 0071 0072 ~CleanupHelper() 0073 { 0074 // the ssh functions just return if the param is null 0075 ssh_channel_free(forwarding_channel); 0076 if (client_sock != -1) { 0077 close(client_sock); 0078 } 0079 if (server_sock != -1) { 0080 close(server_sock); 0081 } 0082 ssh_disconnect(session); 0083 ssh_free(session); 0084 } 0085 }; 0086 0087 CleanupHelper cleanup; 0088 0089 ssh_session session = ssh_new(); 0090 if (session == nullptr) 0091 return; 0092 0093 cleanup.session = session; 0094 0095 ssh_options_set(session, SSH_OPTIONS_HOST, m_host.constData()); 0096 ssh_options_set(session, SSH_OPTIONS_USER, m_sshUserName.constData()); 0097 ssh_options_set(session, SSH_OPTIONS_PORT, &m_sshPort); 0098 0099 int res = ssh_connect(session); 0100 if (res != SSH_OK) 0101 { 0102 Q_EMIT errorMessage(i18n("Error connecting to %1: %2", QString::fromUtf8(m_host), QString::fromLocal8Bit(ssh_get_error(session)))); 0103 return; 0104 } 0105 0106 // First try authenticating via ssh agent 0107 res = ssh_userauth_agent(session, nullptr); 0108 0109 m_passwordRequestCanceledByUser = false; 0110 if (res != SSH_AUTH_SUCCESS) { 0111 // If ssh agent didn't work, try with password 0112 Q_EMIT passwordRequest(NoFlags); // This calls blockingly to the main thread which will call setPassword 0113 res = ssh_userauth_password(session, nullptr, m_password.toUtf8().constData()); 0114 0115 // If password didn't work but came from the wallet, ask the user for the password 0116 if (!m_passwordRequestCanceledByUser && res != SSH_AUTH_SUCCESS && m_passwordOrigin == PasswordFromWallet) { 0117 Q_EMIT passwordRequest(IgnoreWallet); // This calls blockingly to the main thread which will call setPassword 0118 res = ssh_userauth_password(session, nullptr, m_password.toUtf8().constData()); 0119 } 0120 } 0121 0122 if (m_passwordRequestCanceledByUser) { 0123 return; 0124 } 0125 0126 if (res != SSH_AUTH_SUCCESS) { 0127 Q_EMIT errorMessage(i18n("Error authenticating with password: %1", QString::fromLocal8Bit(ssh_get_error(session)))); 0128 return; 0129 } 0130 0131 const int server_sock = socket(AF_INET, SOCK_STREAM, 0); 0132 if (server_sock == -1) { 0133 Q_EMIT errorMessage(i18n("Error creating tunnel socket")); 0134 return; 0135 } 0136 0137 cleanup.server_sock = server_sock; 0138 0139 // so that we can bind more than once in case more than one tunnel is used 0140 int sockopt = 1; 0141 setsockopt(server_sock , SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); 0142 0143 { 0144 // bind the server socket 0145 struct sockaddr_in sin; 0146 sin.sin_family = AF_INET; 0147 sin.sin_port = htons(m_tunnelPort); 0148 sin.sin_addr.s_addr = inet_addr("127.0.0.1"); 0149 0150 if (bind(server_sock, (struct sockaddr*)&sin, sizeof sin) == -1) { 0151 Q_EMIT errorMessage(i18n("Error creating tunnel socket")); 0152 return; 0153 } 0154 0155 if (m_tunnelPort == 0) { 0156 socklen_t sin_len = sizeof sin; 0157 if (getsockname(server_sock, (struct sockaddr *)&sin, &sin_len) == -1) { 0158 Q_EMIT errorMessage(i18n("Error creating tunnel socket")); 0159 return; 0160 } 0161 m_tunnelPort = ntohs(sin.sin_port); 0162 } 0163 } 0164 0165 if (listen(server_sock, 1) == -1) { 0166 Q_EMIT errorMessage(i18n("Error creating tunnel socket")); 0167 return; 0168 } 0169 0170 if (m_stop_thread) { 0171 return; 0172 } 0173 0174 Q_EMIT listenReady(); 0175 // After here we don't need to emit errorMessage anymore on error, qCDebug is enough 0176 // this is because the actual vnc thread will start because of this call and thus 0177 // any socket error here will be detected by the vnc thread and the usual error mechanisms 0178 // there will warn the user interface 0179 0180 int client_sock; 0181 { 0182 struct sockaddr_in client_sin; 0183 socklen_t client_sin_len = sizeof client_sin; 0184 client_sock = accept(server_sock, (struct sockaddr *)&client_sin, &client_sin_len); 0185 if (client_sock == -1) 0186 { 0187 qCDebug(KRDC) << "Error on tunnel socket accept"; 0188 return; 0189 } 0190 0191 cleanup.client_sock = client_sock; 0192 0193 int sock_flags = fcntl(client_sock, F_GETFL, 0); 0194 fcntl(client_sock, F_SETFL, sock_flags | O_NONBLOCK); 0195 } 0196 0197 ssh_channel forwarding_channel = ssh_channel_new(session); 0198 { 0199 const char *forward_remote_host = m_loopback ? "127.0.0.1" : m_host.constData(); 0200 res = ssh_channel_open_forward(forwarding_channel, forward_remote_host, m_vncPort, "127.0.0.1", 0); 0201 if (res != SSH_OK || !ssh_channel_is_open(forwarding_channel)) 0202 { 0203 qCDebug(KRDC) << "SSH channel open error" << ssh_get_error(session); 0204 return; 0205 } 0206 cleanup.forwarding_channel = forwarding_channel; 0207 } 0208 0209 char client_read_buffer[40960]; 0210 char *channel_read_buffer = nullptr; 0211 char *channel_read_buffer_ptr = nullptr; 0212 int channel_read_buffer_to_write; 0213 while (!m_stop_thread && !ssh_channel_is_eof(forwarding_channel)) { 0214 struct timeval timeout; 0215 timeout.tv_sec = 0; 0216 timeout.tv_usec = 200000; 0217 0218 fd_set set; 0219 FD_ZERO(&set); 0220 FD_SET(client_sock, &set); 0221 ssh_channel channels[2] = { forwarding_channel, nullptr }; 0222 ssh_channel channels_out[2] = { nullptr, nullptr }; 0223 0224 res = ssh_select(channels, channels_out, client_sock + 1, &set, &timeout); 0225 if (res == SSH_EINTR) continue; 0226 if (res == -1) break; 0227 0228 bool error = false; 0229 if (FD_ISSET(client_sock, &set)) { 0230 int bytes_read; 0231 while (!error && (bytes_read = read(client_sock, client_read_buffer, sizeof client_read_buffer)) > 0) { 0232 int bytes_written = 0; 0233 int bytes_to_write = bytes_read; 0234 for (char *ptr = client_read_buffer; 0235 bytes_to_write > 0; 0236 bytes_to_write -= bytes_written, ptr += bytes_written) 0237 { 0238 bytes_written = ssh_channel_write(forwarding_channel, ptr, bytes_to_write); 0239 if (bytes_written <= 0) { 0240 error = true; 0241 qCDebug(KRDC) << "error on ssh_channel_write"; 0242 break; 0243 } 0244 } 0245 } 0246 if (bytes_read == 0) { 0247 qCDebug(KRDC) << "error on tunnel read"; 0248 error = true; 0249 } 0250 } 0251 0252 // If on the previous iteration we successfully wrote all we read, we need to read again 0253 if (!error && !channel_read_buffer) { 0254 const int bytes_available = ssh_channel_poll(forwarding_channel, 0); 0255 if (bytes_available == SSH_ERROR || bytes_available == SSH_EOF) { 0256 qCDebug(KRDC) << "error on ssh_channel_poll"; 0257 error = true; 0258 } else if (bytes_available > 0) { 0259 channel_read_buffer = new char[bytes_available]; 0260 channel_read_buffer_ptr = channel_read_buffer; 0261 const int bytes_read = ssh_channel_read_nonblocking(forwarding_channel, channel_read_buffer, bytes_available, 0); 0262 if (bytes_read <= 0) { 0263 qCDebug(KRDC) << "error on ssh_channel_read_nonblocking"; 0264 error = true; 0265 } else { 0266 channel_read_buffer_to_write = bytes_read; 0267 } 0268 } 0269 } 0270 0271 if (!error && channel_read_buffer) { 0272 for (int bytes_written = 0; 0273 channel_read_buffer_to_write > 0; 0274 channel_read_buffer_to_write -= bytes_written, channel_read_buffer_ptr += bytes_written) 0275 { 0276 bytes_written = write(client_sock, channel_read_buffer_ptr, channel_read_buffer_to_write); 0277 if (bytes_written == -1 && errno == EAGAIN) { 0278 // socket is full, just carry on and we will write on the next iteration 0279 // that is why the previous code does 'if (!channel_read_buffer)' 0280 break; 0281 } 0282 if (bytes_written <= 0) { 0283 qCDebug(KRDC) << "error on tunnel write"; 0284 error = true; 0285 break; 0286 } 0287 } 0288 if (channel_read_buffer_to_write <= 0) { 0289 delete[] channel_read_buffer; 0290 channel_read_buffer = nullptr; 0291 } 0292 } 0293 } 0294 0295 delete[] channel_read_buffer; 0296 channel_read_buffer = nullptr; 0297 }