First Commit
This commit is contained in:
commit
d804da8881
|
@ -0,0 +1,78 @@
|
||||||
|
### C++ template
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
build
|
||||||
|
cmake-build-debug/
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "third-party/libssh2"]
|
||||||
|
path = third-party/libssh2
|
||||||
|
url = https://github.com/libssh2/libssh2.git
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/FooTerm.iml" filepath="$PROJECT_DIR$/.idea/FooTerm.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,39 @@
|
||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
project(FooTerm)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_C_STANDARD 90)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
IF (${CMAKE_BUILD_TYPE} MATCHES Debug)
|
||||||
|
add_definitions(-DDEBUG=1)
|
||||||
|
ENDIF ()
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
|
||||||
|
pkg_check_modules(GTKMM gtkmm-3.0)
|
||||||
|
pkg_check_modules(VTE REQUIRED vte-2.91)
|
||||||
|
|
||||||
|
link_directories(
|
||||||
|
${GTKMM_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${GTKMM_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
includes
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(FooTerm src/main.cpp src/term_windows.cpp src/vte.cpp src/ssh.cpp src/eventloop.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(FooTerm
|
||||||
|
${GTKMM_LIBRARIES})
|
||||||
|
|
||||||
|
target_link_libraries(FooTerm ${VTE_LIBRARIES})
|
||||||
|
add_definitions(${VTE_CFLAGS} ${VTE_CFLAGS_OTHER})
|
||||||
|
|
||||||
|
|
||||||
|
add_subdirectory(third-party)
|
||||||
|
find_package(fmt)
|
||||||
|
|
||||||
|
target_link_libraries(FooTerm fmt::fmt ssh2)
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 01/03/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FOOTERM_EVENTLOOP_HPP
|
||||||
|
#define FOOTERM_EVENTLOOP_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <libssh2.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct EventLoopEntry {
|
||||||
|
enum {
|
||||||
|
DESCRIPTOR,
|
||||||
|
CHANNEL
|
||||||
|
} entryType;
|
||||||
|
|
||||||
|
LIBSSH2_CHANNEL *channel;
|
||||||
|
int out;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EventLoop {
|
||||||
|
private:
|
||||||
|
std::vector<pollfd> pfds;
|
||||||
|
std::vector<EventLoopEntry> outs;
|
||||||
|
bool isClosed{};
|
||||||
|
public:
|
||||||
|
EventLoop();
|
||||||
|
|
||||||
|
void registerFd(int fdin, EventLoopEntry entry);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
static void start(EventLoop *self) {
|
||||||
|
self->run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FOOTERM_EVENTLOOP_HPP
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 01/03/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FOOTERM_SSH_HPP
|
||||||
|
#define FOOTERM_SSH_HPP
|
||||||
|
|
||||||
|
#include <libssh2.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
private:
|
||||||
|
LIBSSH2_SESSION *session;
|
||||||
|
LIBSSH2_CHANNEL *channel;
|
||||||
|
int sock;
|
||||||
|
|
||||||
|
static int openSocket(const char *addr, int port);
|
||||||
|
|
||||||
|
static bool authWithPublicKey(LIBSSH2_AGENT *agent, const char *username);
|
||||||
|
|
||||||
|
static bool authWithPassword(LIBSSH2_SESSION *session, const char *username, const char *password);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Session();
|
||||||
|
|
||||||
|
~Session();
|
||||||
|
|
||||||
|
void openConnection(const char *addr, int port, const char *username, const char *password);
|
||||||
|
|
||||||
|
void disconnect();
|
||||||
|
|
||||||
|
bool isConnected();
|
||||||
|
|
||||||
|
int getSock() {
|
||||||
|
return this->sock;
|
||||||
|
};
|
||||||
|
|
||||||
|
LIBSSH2_CHANNEL *getChannel() {
|
||||||
|
return this->channel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SessionConnectException : public std::exception {
|
||||||
|
public:
|
||||||
|
explicit SessionConnectException(std::string msg) : m_msg(std::move(msg)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char *what() const noexcept override {
|
||||||
|
return m_msg.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string m_msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FOOTERM_SSH_HPP
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 28/02/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FOOTERM_TERM_WINDOWS_HPP
|
||||||
|
#define FOOTERM_TERM_WINDOWS_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <gtkmm/window.h>
|
||||||
|
#include <gtkmm/notebook.h>
|
||||||
|
#include <vte/vte.h>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
#include "eventloop.hpp"
|
||||||
|
|
||||||
|
class FooTermWindow : public Gtk::Window {
|
||||||
|
private:
|
||||||
|
Gtk::Notebook notebook;
|
||||||
|
EventLoop eventLoop;
|
||||||
|
void on_button_click();
|
||||||
|
|
||||||
|
public:
|
||||||
|
FooTermWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FOOTERM_TERM_WINDOWS_HPP
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 28/02/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FOOTERM_VTE_HPP
|
||||||
|
#define FOOTERM_VTE_HPP
|
||||||
|
|
||||||
|
#include <vte/vte.h>
|
||||||
|
#include <gtkmm/widget.h>
|
||||||
|
|
||||||
|
#include "eventloop.hpp"
|
||||||
|
|
||||||
|
class Vte {
|
||||||
|
public:
|
||||||
|
Vte();
|
||||||
|
|
||||||
|
void spawnShell(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password);
|
||||||
|
|
||||||
|
Gtk::Widget *asGtkWidget();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GtkWidget *terminal;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FOOTERM_VTE_HPP
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 01/03/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "eventloop.hpp"
|
||||||
|
#include <libssh2.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
EventLoop::EventLoop() = default;
|
||||||
|
|
||||||
|
void EventLoop::registerFd(int fdin, EventLoopEntry entry) {
|
||||||
|
pollfd fd = {0};
|
||||||
|
fd.fd = fdin;
|
||||||
|
fd.events = POLLIN;
|
||||||
|
this->pfds.emplace_back(fd);
|
||||||
|
this->outs.emplace_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFFER_SIZE (256)
|
||||||
|
|
||||||
|
void EventLoop::run() {
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
ssize_t bytesRead;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "EndlessLoop"
|
||||||
|
while (!this->isClosed) {
|
||||||
|
res = poll(this->pfds.data(), this->pfds.size(), 1000);
|
||||||
|
|
||||||
|
if (res > 0) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < this->pfds.size()) {
|
||||||
|
pollfd &pfd = this->pfds[i];
|
||||||
|
|
||||||
|
if (pfd.revents & POLLIN) {
|
||||||
|
do {
|
||||||
|
if (this->outs[i].entryType == EventLoopEntry::CHANNEL) {
|
||||||
|
bytesRead = libssh2_channel_read(this->outs[i].channel, buffer, BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
write(this->outs[i].out, buffer, bytesRead);
|
||||||
|
} else if (bytesRead < 0 && bytesRead != LIBSSH2_ERROR_EAGAIN) {
|
||||||
|
std::cout << bytesRead << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libssh2_channel_eof(this->outs[i].channel)) {
|
||||||
|
this->outs.erase(this->outs.begin() + i);
|
||||||
|
this->pfds.erase(this->pfds.begin() + i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bytesRead = read(pfd.fd, buffer, BUFFER_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
libssh2_channel_write(this->outs[i].channel, buffer, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (bytesRead > 0);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "term_windows.hpp"
|
||||||
|
|
||||||
|
#include <gtkmm.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto app = Gtk::Application::create("org.gtkmm.examples.base");
|
||||||
|
FooTermWindow window;
|
||||||
|
return app->run(window);
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 01/03/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "ssh.hpp"
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
Session::Session() {
|
||||||
|
this->session = nullptr;
|
||||||
|
this->channel = nullptr;
|
||||||
|
this->sock = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Session::~Session() {
|
||||||
|
if (this->isConnected()) {
|
||||||
|
this->disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Session::openSocket(const char *addr, int port) {
|
||||||
|
struct addrinfo hints = {0};
|
||||||
|
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
|
||||||
|
hints.ai_flags |= AI_CANONNAME;
|
||||||
|
hints.ai_protocol = 0;
|
||||||
|
|
||||||
|
struct addrinfo *result;
|
||||||
|
struct sockaddr *sin;
|
||||||
|
unsigned int socklen;
|
||||||
|
|
||||||
|
char service[256];
|
||||||
|
sprintf(service, "%d", port);
|
||||||
|
|
||||||
|
int s = getaddrinfo(addr, service, &hints, &result);
|
||||||
|
if (s) {
|
||||||
|
throw SessionConnectException(fmt::format("failed to resolve {}: {}", addr, gai_strerror(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
sin = result->ai_addr;
|
||||||
|
socklen = result->ai_addrlen;
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == -1) {
|
||||||
|
throw SessionConnectException(fmt::format("failed to create socket: {}", strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (connect(sock, sin, socklen) != 0) {
|
||||||
|
throw SessionConnectException(fmt::format("failed to connect: {}", strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::openConnection(const char *addr, int port, const char *username, const char *password) {
|
||||||
|
char *error;
|
||||||
|
int errorLen;
|
||||||
|
|
||||||
|
this->sock = Session::openSocket(addr, port);
|
||||||
|
this->session = libssh2_session_init();
|
||||||
|
|
||||||
|
libssh2_session_set_blocking(this->session, 0);
|
||||||
|
|
||||||
|
int rc;
|
||||||
|
while ((rc = libssh2_session_handshake(this->session, sock)) == LIBSSH2_ERROR_EAGAIN) {}
|
||||||
|
|
||||||
|
if (rc) {
|
||||||
|
libssh2_session_last_error(session, &error, &errorLen, true);
|
||||||
|
|
||||||
|
throw SessionConnectException(fmt::format("failure establishing SSH session: {}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *userauthlist;
|
||||||
|
while ((userauthlist = libssh2_userauth_list(session, username, strlen(username))) == nullptr) {
|
||||||
|
if (libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool usePublicKey = true, usePassword = true;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "Authentication methods: %s\n", userauthlist);
|
||||||
|
#endif
|
||||||
|
if (strstr(userauthlist, "publickey") == nullptr) {
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "\"publickey\" authentication is not supported\n");
|
||||||
|
#endif
|
||||||
|
usePublicKey = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(userauthlist, "password") == nullptr || password == nullptr) {
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "\"password\" authentication is not supported\n");
|
||||||
|
#endif
|
||||||
|
usePassword = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Connect to the ssh-agent */
|
||||||
|
LIBSSH2_AGENT *agent = libssh2_agent_init(session);
|
||||||
|
|
||||||
|
if (!agent) {
|
||||||
|
libssh2_session_last_error(session, &error, &errorLen, true);
|
||||||
|
throw SessionConnectException(fmt::format("failure initializing ssh-agent support: {}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libssh2_agent_connect(agent)) {
|
||||||
|
libssh2_session_last_error(session, &error, &errorLen, true);
|
||||||
|
throw SessionConnectException(fmt::format("failure connecting to ssh-agent: {}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
|
if (usePublicKey)
|
||||||
|
isConnected = Session::authWithPublicKey(agent, username);
|
||||||
|
|
||||||
|
if (!isConnected && usePassword)
|
||||||
|
isConnected = Session::authWithPassword(session, username, password);
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
libssh2_session_last_error(session, &error, &errorLen, true);
|
||||||
|
throw SessionConnectException(fmt::format("Failed to authenticate: {}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
this->channel = libssh2_channel_open_session(session);
|
||||||
|
} while (this->channel == nullptr && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
libssh2_session_last_error(session, &error, &errorLen, true);
|
||||||
|
throw SessionConnectException(fmt::format("unable to open a session: {}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = libssh2_channel_setenv(this->channel, "TERM", "xterm-256color");
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = libssh2_channel_request_pty(this->channel, "xterm-256color");
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = libssh2_channel_shell(this->channel);
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::disconnect() {
|
||||||
|
libssh2_channel_free(this->channel);
|
||||||
|
libssh2_session_free(this->session);
|
||||||
|
|
||||||
|
this->channel = nullptr;
|
||||||
|
this->session = nullptr;
|
||||||
|
this->sock = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::isConnected() {
|
||||||
|
return this->channel != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::authWithPublicKey(LIBSSH2_AGENT *agent, const char *username) {
|
||||||
|
int rc;
|
||||||
|
struct libssh2_agent_publickey *identity, *prev_identity = nullptr;
|
||||||
|
|
||||||
|
while ((rc = libssh2_agent_list_identities(agent)) == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
if (rc) {
|
||||||
|
fprintf(stderr, "Failure requesting identities to ssh-agent\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
do {
|
||||||
|
rc = libssh2_agent_get_identity(agent, &identity, prev_identity);
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
if (rc == 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Failure obtaining identity from ssh-agent support\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = libssh2_agent_userauth(agent, username, identity);
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
if (rc) {
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "\tAuthentication with username %s and "
|
||||||
|
"public key %s failed!\n",
|
||||||
|
username, identity->comment);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "\tAuthentication with username %s and "
|
||||||
|
"public key %s succeeded!\n",
|
||||||
|
username, identity->comment);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev_identity = identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::authWithPassword(LIBSSH2_SESSION *session, const char *username, const char *password) {
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = libssh2_userauth_password(session, username, password);
|
||||||
|
} while (rc == LIBSSH2_ERROR_EAGAIN);
|
||||||
|
|
||||||
|
return rc == 0;
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 28/02/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "vte.hpp"
|
||||||
|
#include "term_windows.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <gtkmm/headerbar.h>
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/dialog.h>
|
||||||
|
#include <gtkmm/entry.h>
|
||||||
|
#include <gtkmm/grid.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
FooTermWindow::FooTermWindow() {
|
||||||
|
this->set_default_size(800, 600);
|
||||||
|
this->add(notebook);
|
||||||
|
|
||||||
|
auto *headerBar = Gtk::make_managed<Gtk::HeaderBar>();
|
||||||
|
headerBar->set_title("FooTerm");
|
||||||
|
headerBar->set_show_close_button(true);
|
||||||
|
|
||||||
|
auto *addButton = Gtk::make_managed<Gtk::Button>("");
|
||||||
|
addButton->set_image_from_icon_name("window-new");
|
||||||
|
addButton->signal_clicked().connect(sigc::mem_fun(this, &FooTermWindow::on_button_click));
|
||||||
|
headerBar->add(*addButton);
|
||||||
|
this->set_titlebar(*headerBar);
|
||||||
|
|
||||||
|
notebook.set_scrollable(true);
|
||||||
|
notebook.set_group_name("foo-terminal-window");
|
||||||
|
|
||||||
|
std::thread backgroundThread(EventLoop::start, &this->eventLoop);
|
||||||
|
backgroundThread.detach();
|
||||||
|
|
||||||
|
this->show_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FooTermWindow::on_button_click() {
|
||||||
|
Gtk::Dialog m("login", *this);
|
||||||
|
|
||||||
|
m.add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL);
|
||||||
|
m.add_button("Ok", Gtk::ResponseType::RESPONSE_OK);
|
||||||
|
|
||||||
|
Gtk::Box *content = m.get_content_area();
|
||||||
|
Gtk::Grid contentGrid;
|
||||||
|
contentGrid.set_column_spacing(8);
|
||||||
|
contentGrid.set_row_spacing(8);
|
||||||
|
contentGrid.set_hexpand(true);
|
||||||
|
|
||||||
|
|
||||||
|
contentGrid.set_margin_start(20);
|
||||||
|
contentGrid.set_margin_end(20);
|
||||||
|
contentGrid.set_margin_bottom(20);
|
||||||
|
contentGrid.set_margin_top(20);
|
||||||
|
|
||||||
|
content->add(contentGrid);
|
||||||
|
|
||||||
|
Gtk::Label hostLabel("Host :");
|
||||||
|
contentGrid.attach(hostLabel, 0, 0);
|
||||||
|
|
||||||
|
Gtk::Entry hostEntry;
|
||||||
|
contentGrid.attach(hostEntry, 1, 0);
|
||||||
|
|
||||||
|
Gtk::Label portLabel("Port :");
|
||||||
|
contentGrid.attach(portLabel, 2, 0);
|
||||||
|
|
||||||
|
Gtk::Entry portEntry;
|
||||||
|
contentGrid.attach(portEntry, 3, 0);
|
||||||
|
|
||||||
|
Gtk::Label usernameLabel("Username :");
|
||||||
|
contentGrid.attach(usernameLabel, 0, 1, 1, 1);
|
||||||
|
|
||||||
|
Gtk::Entry usernameEntry;
|
||||||
|
contentGrid.attach(usernameEntry, 1, 1, 3, 1);
|
||||||
|
|
||||||
|
Gtk::Label passwordLabel("Password :");
|
||||||
|
contentGrid.attach(passwordLabel, 0, 2, 1, 1);
|
||||||
|
|
||||||
|
Gtk::Entry passwordEntry;
|
||||||
|
passwordEntry.set_visibility(false);
|
||||||
|
contentGrid.attach(passwordEntry, 1, 2, 3, 1);
|
||||||
|
|
||||||
|
content->show_all();
|
||||||
|
|
||||||
|
int result = m.run();
|
||||||
|
|
||||||
|
std::string host;
|
||||||
|
int port = 22;
|
||||||
|
|
||||||
|
std::string username;
|
||||||
|
std::string password;
|
||||||
|
|
||||||
|
if (result == Gtk::RESPONSE_OK) {
|
||||||
|
host = hostEntry.get_text();
|
||||||
|
|
||||||
|
if (portEntry.get_text_length() > 0) {
|
||||||
|
port = std::stoi(portEntry.get_text());
|
||||||
|
}
|
||||||
|
|
||||||
|
username = usernameEntry.get_text();
|
||||||
|
password = passwordEntry.get_text();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Vte *vte = new Vte();
|
||||||
|
vte->spawnShell(this->eventLoop, host.c_str(), port, username.c_str(), password.c_str());
|
||||||
|
|
||||||
|
Gtk::Widget *widget = vte->asGtkWidget();
|
||||||
|
this->notebook.append_page(*widget, host, true);
|
||||||
|
widget->show();
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// Created by oupson on 28/02/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "vte.hpp"
|
||||||
|
#include "term_windows.hpp"
|
||||||
|
#include "ssh.hpp"
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <thread> // std::thread
|
||||||
|
#include <pty.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
static void got_child_exited([[maybe_unused]] VteTerminal *vte, gint status, [[maybe_unused]] Vte *window) {
|
||||||
|
// TODO WHEN LOCAL TERMINAL
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
termStateCallback([[maybe_unused]] VteTerminal *terminal, GPid pid, GError *error,
|
||||||
|
[[maybe_unused]] gpointer windowPtr) {
|
||||||
|
if (error == nullptr) {
|
||||||
|
fmt::print("{} started. (PID: {})\n", "foo", pid);
|
||||||
|
} else {
|
||||||
|
fmt::print("An error occurred: {}\n", error->message);
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vte::Vte() {
|
||||||
|
this->terminal = vte_terminal_new();
|
||||||
|
vte_terminal_set_enable_sixel(VTE_TERMINAL(this->terminal), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Vte::spawnShell(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password) {
|
||||||
|
int master, slave;
|
||||||
|
|
||||||
|
struct termios settings{};
|
||||||
|
cfmakeraw(&settings);
|
||||||
|
|
||||||
|
int error = openpty(&master, &slave, nullptr, &settings, nullptr); // TODO
|
||||||
|
|
||||||
|
fcntl(slave, F_SETFL, FNDELAY);
|
||||||
|
|
||||||
|
VtePty *pty = vte_pty_new_foreign_sync(master, nullptr, nullptr);
|
||||||
|
vte_terminal_set_pty(VTE_TERMINAL(this->terminal), pty);
|
||||||
|
|
||||||
|
auto *s = new Session();
|
||||||
|
s->openConnection(host, port, username, password);
|
||||||
|
|
||||||
|
EventLoopEntry entry1{};
|
||||||
|
entry1.out = slave;
|
||||||
|
entry1.entryType = EventLoopEntry::CHANNEL;
|
||||||
|
entry1.channel = s->getChannel();
|
||||||
|
|
||||||
|
eventLoop.registerFd(s->getSock(), entry1);
|
||||||
|
|
||||||
|
|
||||||
|
EventLoopEntry entry2{};
|
||||||
|
entry2.out = slave;
|
||||||
|
entry2.entryType = EventLoopEntry::DESCRIPTOR;
|
||||||
|
entry2.channel = s->getChannel();
|
||||||
|
|
||||||
|
eventLoop.registerFd(slave, entry2);
|
||||||
|
|
||||||
|
g_signal_connect(G_OBJECT(terminal), "child-exited", G_CALLBACK(got_child_exited), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk::Widget *Vte::asGtkWidget() {
|
||||||
|
return Glib::wrap(this->terminal);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
add_subdirectory(libssh2)
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 635caa90787220ac3773c1d5ba11f1236c22eae8
|
Loading…
Reference in New Issue