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