refactor: open ssh terminal in a new window

This commit is contained in:
uncor3
2025-10-06 15:07:35 -07:00
parent c8094b5653
commit 92847227be
4 changed files with 628 additions and 379 deletions
+35 -362
View File
@@ -1,5 +1,6 @@
#include "jailbrokenwidget.h"
#include "appcontext.h"
#include "sshterminalwidget.h"
#ifdef __linux__
#include "core/services/avahi/avahi_service.h"
@@ -16,18 +17,11 @@
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QProcess>
#include <QProcessEnvironment>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <libssh/libssh.h>
#include <qtermwidget6/qtermwidget.h>
#include <unistd.h>
JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
{
@@ -54,7 +48,7 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
&JailbrokenWidget::onWiredDeviceRemoved);
#ifdef __linux__
#ifdef __linux____
m_wirelessProvider = new AvahiService(this);
connect(m_wirelessProvider, &AvahiService::deviceAdded, this,
&JailbrokenWidget::onWirelessDeviceAdded);
@@ -78,21 +72,9 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
rightLayout->setSpacing(10);
setupDeviceSelectionUI(rightLayout);
setupTerminal();
rightLayout->addWidget(m_terminal, 1);
mainLayout->addWidget(rightContainer, 3);
// Initialize SSH
ssh_init();
m_sshSession = nullptr;
m_sshChannel = nullptr;
// Setup timer for checking SSH data
m_sshTimer = new QTimer(this);
connect(m_sshTimer, &QTimer::timeout, this,
&JailbrokenWidget::checkSshData);
// Start scanning for wireless devices
m_wirelessProvider->startBrowsing();
@@ -100,27 +82,6 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
updateDeviceList();
}
void JailbrokenWidget::setupTerminal()
{
m_terminal = new QTermWidget(0, this);
m_terminal->setMinimumHeight(400);
m_terminal->setScrollBarPosition(QTermWidget::ScrollBarRight);
m_terminal->setColorScheme("Linux");
m_terminal->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_terminal, &QWidget::customContextMenuRequested, this,
[this](const QPoint &pos) {
QMenu menu(this);
QList<QAction *> actions = m_terminal->filterActions(pos);
if (!actions.isEmpty()) {
menu.addActions(actions);
menu.exec(m_terminal->mapToGlobal(pos));
}
});
m_terminal->startTerminalTeletype();
m_terminal->hide();
m_terminal->setStyleSheet("padding : 10px;");
}
void JailbrokenWidget::setupDeviceSelectionUI(QVBoxLayout *layout)
{
// Create scroll area for device selection
@@ -157,10 +118,10 @@ void JailbrokenWidget::setupDeviceSelectionUI(QVBoxLayout *layout)
m_infoLabel = new QLabel("Select a device to connect");
layout->addWidget(m_infoLabel);
m_connectButton = new QPushButton("Connect SSH Terminal");
m_connectButton = new QPushButton("Open SSH Terminal");
m_connectButton->setEnabled(false);
connect(m_connectButton, &QPushButton::clicked, this,
&JailbrokenWidget::onConnectSSH);
&JailbrokenWidget::onOpenSSHTerminal);
layout->addWidget(m_connectButton);
}
@@ -296,7 +257,8 @@ void JailbrokenWidget::onDeviceSelected(QAbstractButton *button)
if (m_selectedWiredDevice->deviceInfo.jailbroken) {
m_infoLabel->setText("Jailbroken device selected");
} else {
m_infoLabel->setText("Device selected (jailbreak status unknown)");
m_infoLabel->setText(
"Device selected (detected as non-jailbroken)");
}
} else if (deviceType == "wireless") {
m_selectedDeviceType = DeviceType::Wireless;
@@ -311,7 +273,7 @@ void JailbrokenWidget::onDeviceSelected(QAbstractButton *button)
}
m_connectButton->setEnabled(true);
m_connectButton->setText("Connect SSH Terminal");
m_connectButton->setText("Open SSH Terminal");
}
void JailbrokenWidget::resetSelection()
@@ -330,333 +292,44 @@ void JailbrokenWidget::resetSelection()
}
}
void JailbrokenWidget::connectLibsshToTerminal()
void JailbrokenWidget::onOpenSSHTerminal()
{
if (!m_terminal)
return;
// Connect terminal input to SSH channel
connect(m_terminal, &QTermWidget::sendData, this,
[this](const char *data, int size) {
if (m_sshChannel && ssh_channel_is_open(m_sshChannel)) {
ssh_channel_write(m_sshChannel, data, size);
}
});
}
void JailbrokenWidget::deviceConnected(iDescriptorDevice *device)
{
if (device->deviceInfo.jailbroken) {
m_infoLabel->setText("Jailbroken device connected");
} else {
m_infoLabel->setText(
"Connected device is not detected as jailbroken. Continue anyway?");
}
m_device = device;
m_connectButton->setEnabled(true);
m_connectButton->setText("Connect SSH Terminal");
}
void JailbrokenWidget::onConnectSSH()
{
if (m_sshConnected) {
disconnectSSH();
return;
}
if (m_selectedDeviceType == DeviceType::None) {
m_infoLabel->setText("Please select a device first");
return;
}
// Prepare connection info
ConnectionInfo connectionInfo;
if (m_selectedDeviceType == DeviceType::Wired) {
initWiredDevice();
} else {
initWirelessDevice();
}
}
void JailbrokenWidget::initWiredDevice()
{
if (m_isInitialized)
return;
m_isInitialized = true;
if (!m_selectedWiredDevice) {
m_infoLabel->setText("No wired device selected");
return;
}
m_connectButton->setEnabled(false);
m_infoLabel->setText("Setting up SSH tunnel...");
// Start iproxy first for wired devices
iproxyProcess = new QProcess(this);
iproxyProcess->setProcessChannelMode(QProcess::MergedChannels);
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
// Add common directories where iproxy might be installed
env.insert("PATH", env.value("PATH") + ":/usr/local/bin:/opt/homebrew/bin");
iproxyProcess->setProcessEnvironment(env);
connect(iproxyProcess, &QProcess::errorOccurred, this,
[this](QProcess::ProcessError error) {
m_infoLabel->setText("Error: " + iproxyProcess->errorString());
m_connectButton->setEnabled(true);
qDebug() << "iproxy error:" << error;
});
// Monitor iproxy output for readiness
connect(iproxyProcess, &QProcess::readyRead, this, [this]() {
QByteArray output = iproxyProcess->readAll();
if (output.contains("waiting for connection")) {
// iproxy is ready, disconnect the signal to avoid multiple calls
disconnect(iproxyProcess, &QProcess::readyRead, this, nullptr);
startSSH("127.0.0.1", 3333);
if (!m_selectedWiredDevice) {
m_infoLabel->setText("No wired device selected");
return;
}
});
QStringList args;
args << "-u" << m_selectedWiredDevice->udid.c_str() << "3333" << "22";
connectionInfo.type = ConnectionType::Wired;
connectionInfo.deviceName = QString::fromStdString(
m_selectedWiredDevice->deviceInfo.deviceName);
connectionInfo.deviceUdid =
QString::fromStdString(m_selectedWiredDevice->udid);
connectionInfo.hostAddress = "127.0.0.1";
connectionInfo.port = 22;
qDebug() << "Starting iproxy with args:" << args;
iproxyProcess->start("iproxy", args);
// Check if iproxy started successfully
if (!iproxyProcess->waitForStarted(5000)) {
m_infoLabel->setText("Failed to start iproxy");
m_connectButton->setEnabled(true);
return;
} else if (m_selectedDeviceType == DeviceType::Wireless) {
connectionInfo.type = ConnectionType::Wireless;
connectionInfo.deviceName = m_selectedNetworkDevice.name;
connectionInfo.deviceUdid = "";
connectionInfo.hostAddress = m_selectedNetworkDevice.address;
connectionInfo.port = m_selectedNetworkDevice.port;
}
// Create and show SSH terminal widget in a new window
SSHTerminalWidget *sshTerminal = new SSHTerminalWidget(connectionInfo);
sshTerminal->setAttribute(Qt::WA_DeleteOnClose);
sshTerminal->show();
sshTerminal->raise();
sshTerminal->activateWindow();
}
void JailbrokenWidget::initWirelessDevice()
{
if (m_isInitialized)
return;
m_isInitialized = true;
m_connectButton->setEnabled(false);
m_infoLabel->setText("Connecting to network device...");
// For wireless devices, connect directly without iproxy
startSSH(m_selectedNetworkDevice.address, m_selectedNetworkDevice.port);
}
void JailbrokenWidget::startSSH(const QString &host, uint16_t port)
{
if (m_sshConnected)
return;
m_infoLabel->setText("Connecting to SSH server...");
qDebug() << "Starting SSH connection to" << host << ":" << port;
// Create SSH session
m_sshSession = ssh_new();
if (!m_sshSession) {
m_infoLabel->setText("Error: Failed to create SSH session");
m_connectButton->setEnabled(true);
return;
}
// Configure SSH session
QByteArray hostBytes = host.toUtf8();
ssh_options_set(m_sshSession, SSH_OPTIONS_HOST, hostBytes.constData());
int sshPort = static_cast<int>(port);
ssh_options_set(m_sshSession, SSH_OPTIONS_PORT, &sshPort);
ssh_options_set(m_sshSession, SSH_OPTIONS_USER, "root");
// Disable strict host key checking
int stricthostcheck = 0;
ssh_options_set(m_sshSession, SSH_OPTIONS_STRICTHOSTKEYCHECK,
&stricthostcheck);
// Set log level for debugging
int log_level = SSH_LOG_PROTOCOL;
ssh_options_set(m_sshSession, SSH_OPTIONS_LOG_VERBOSITY, &log_level);
qDebug() << "SSH session configured, attempting connection...";
// Connect to SSH server
int rc = ssh_connect(m_sshSession);
qDebug() << "SSH connect result:" << rc << "SSH_OK:" << SSH_OK;
if (rc != SSH_OK) {
QString errorMsg = QString("SSH connection failed: %1")
.arg(ssh_get_error(m_sshSession));
m_infoLabel->setText(errorMsg);
qDebug() << errorMsg;
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
qDebug() << "SSH connected successfully, attempting authentication...";
// Authenticate with password
rc = ssh_userauth_password(m_sshSession, nullptr, "alpine");
if (rc != SSH_AUTH_SUCCESS) {
m_infoLabel->setText(QString("SSH authentication failed: %1")
.arg(ssh_get_error(m_sshSession)));
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
// Create SSH channel
m_sshChannel = ssh_channel_new(m_sshSession);
if (!m_sshChannel) {
m_infoLabel->setText("Error: Failed to create SSH channel");
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
// Open SSH channel
rc = ssh_channel_open_session(m_sshChannel);
if (rc != SSH_OK) {
m_infoLabel->setText(QString("Failed to open SSH channel: %1")
.arg(ssh_get_error(m_sshSession)));
ssh_channel_free(m_sshChannel);
m_sshChannel = nullptr;
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
// Request a PTY
rc = ssh_channel_request_pty(m_sshChannel);
if (rc != SSH_OK) {
m_infoLabel->setText("Failed to request PTY");
ssh_channel_close(m_sshChannel);
ssh_channel_free(m_sshChannel);
m_sshChannel = nullptr;
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
// Start shell
rc = ssh_channel_request_shell(m_sshChannel);
if (rc != SSH_OK) {
m_infoLabel->setText("Failed to start shell");
ssh_channel_close(m_sshChannel);
ssh_channel_free(m_sshChannel);
m_sshChannel = nullptr;
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
m_connectButton->setEnabled(true);
return;
}
// Show terminal and connect to libssh
m_terminal->show();
connectLibsshToTerminal();
// Start timer to check for SSH data
m_sshTimer->start(50); // Check every 50ms
m_sshConnected = true;
m_connectButton->setEnabled(true);
m_connectButton->setText("Disconnect SSH");
m_infoLabel->setText("SSH terminal connected");
// Set focus to terminal
m_terminal->setFocus();
}
void JailbrokenWidget::checkSshData()
{
if (!m_sshChannel || !ssh_channel_is_open(m_sshChannel))
return;
// Check if SSH channel has data to read
if (ssh_channel_poll(m_sshChannel, 0) > 0) {
char buffer[4096];
int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer,
sizeof(buffer), 0);
if (nbytes > 0) {
// Write data to terminal's PTY
write(m_terminal->getPtySlaveFd(), buffer, nbytes);
}
}
// Check for stderr data
if (ssh_channel_poll(m_sshChannel, 1) > 0) {
char buffer[4096];
int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer,
sizeof(buffer), 1);
if (nbytes > 0) {
// Write stderr data to terminal's PTY
write(m_terminal->getPtySlaveFd(), buffer, nbytes);
}
}
// Check if channel is closed
if (ssh_channel_is_eof(m_sshChannel)) {
disconnectSSH();
}
}
void JailbrokenWidget::disconnectSSH()
{
if (m_sshTimer) {
m_sshTimer->stop();
}
if (m_sshChannel) {
ssh_channel_close(m_sshChannel);
ssh_channel_free(m_sshChannel);
m_sshChannel = nullptr;
}
if (m_sshSession) {
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
m_sshSession = nullptr;
}
if (iproxyProcess) {
iproxyProcess->kill();
delete iproxyProcess;
iproxyProcess = nullptr;
}
m_terminal->hide();
m_infoLabel->setText("SSH disconnected");
m_sshConnected = false;
m_isInitialized = false;
m_connectButton->setEnabled(false);
}
// todo: crash at exit
JailbrokenWidget::~JailbrokenWidget()
{
if (m_sshTimer) {
m_sshTimer->stop();
}
if (m_sshChannel) {
ssh_channel_close(m_sshChannel);
ssh_channel_free(m_sshChannel);
}
if (m_sshSession) {
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
}
if (iproxyProcess) {
iproxyProcess->kill();
}
}
JailbrokenWidget::~JailbrokenWidget() {}