summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/monitor-gui.cc266
1 files changed, 248 insertions, 18 deletions
diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc
index 4f6b21d..602acce 100644
--- a/src/monitor-gui.cc
+++ b/src/monitor-gui.cc
@@ -15,12 +15,14 @@
#include <vector>
#include "config.hh"
+#include "data.hh"
#include "gui_about.hh"
#include "gui_config.hh"
#include "gui_formapply.hh"
#include "gui_hexdump.hh"
#include "gui_listmodel.hh"
#include "gui_menu.hh"
+#include "gui_message.hh"
#include "gui_main.hh"
#include "gui_statusbar.hh"
#include "gui_textwnd.hh"
@@ -35,6 +37,8 @@
namespace {
+std::string const APP_TITLE = "TransparentProxy Monitor";
+
std::string const ACTION_SETUP = "setup";
std::string const ACTION_CONNECT = "connect";
std::string const ACTION_DISCONNECT = "disconnect";
@@ -47,6 +51,10 @@ std::string const ACTION_PROXY_LOG = "proxy_log";
#if HAVE_SSL
std::string const ACTION_GENERATE_CA = "genca";
#endif // HAVE_SSL
+std::string const ACTION_NEW = "new";
+std::string const ACTION_OPEN = "open";
+std::string const ACTION_SAVE = "save";
+std::string const ACTION_SAVE_AS = "save_as";
bool valid_hostname(std::string const& host) {
return !host.empty();
@@ -92,12 +100,17 @@ std::string default_cert_bundle() {
class PackageList : public GuiListModel {
public:
struct Package {
+ ::Package pkg;
+
std::string timestamp;
std::string from;
std::string to;
std::string size;
std::string data;
+
+ Package() {
+ }
};
size_t rows() const override {
@@ -150,6 +163,7 @@ public:
open_.emplace(package.id, index);
packages_.emplace_back();
auto& pkg = packages_.back();
+ pkg.pkg = package;
format_timestamp(&pkg.timestamp, package.timestamp);
format_host_port(&pkg.from, package.source_host, package.source_port);
format_host_port(&pkg.to, package.target_host, package.target_port);
@@ -630,18 +644,41 @@ private:
public:
MonitorGui()
: packages_(new PackageList()),
- main_(GuiMain::create("TransparentProxy Monitor", 800, 500)),
+ main_(GuiMain::create(APP_TITLE, 800, 500)),
menu_(GuiMenu::create()),
statusbar_(GuiStatusBar::create()),
looper_(main_->createLooper()),
has_selection_(false),
- selection_(0) {
+ selection_(0),
+ modified_(false)
+ {
+ pem_filter_.emplace_back();
+ pem_filter_.back().name = "PEM";
+ pem_filter_.back().masks.emplace_back("*.pem");
+
+ crt_filter_.emplace_back();
+ crt_filter_.back().name = "Certificates";
+ crt_filter_.back().masks.emplace_back("*.crt");
+
auto file = menu_->add_menu("File");
- file->add_item(ACTION_SETUP, "Setup...");
- file->add_item(ACTION_CONNECT, "Connect...");
- file->add_item(ACTION_DISCONNECT, "Disconnect");
+ file->add_item(ACTION_NEW, "New");
+ file->add_item(ACTION_OPEN, "Open...");
+ file->add_item(ACTION_SAVE, "Save");
+ file->add_item(ACTION_SAVE_AS, "Save As...");
file->add_separator();
file->add_item(ACTION_EXIT, "Exit");
+
+ menu_->enable_item(ACTION_SAVE, false);
+
+ file_filter_.emplace_back();
+ file_filter_.back().name = "TransparentProxy Packages";
+ file_filter_.back().masks.emplace_back("*.tpp");
+
+ auto proxy = menu_->add_menu("Proxy");
+ proxy->add_item(ACTION_SETUP, "Setup...");
+ proxy->add_item(ACTION_CONNECT, "Connect...");
+ proxy->add_item(ACTION_DISCONNECT, "Disconnect");
+
auto edit = menu_->add_menu("Edit");
edit->add_item(ACTION_COPY_TEXT, "Copy");
edit->add_item(ACTION_COPY_RAW, "Copy binary");
@@ -714,23 +751,17 @@ public:
mitm,
"If enabled SSL connections will be intercepted"
" by the proxy to log unencrypted traffic.");
- std::vector<GuiFile::Filter> filter;
- filter.emplace_back();
- filter.back().name = "PEM";
- filter.back().masks.emplace_back("*.pem");
connect_->add_file("ssl-ca", "Certificate Authority",
main_->config()->get("ssl-ca",
main_->config()->get("genca-output", "")),
"CA and key to sign all fake server certificates with",
- GuiFile::FILE_OPEN, filter);
+ GuiFile::FILE_OPEN, pem_filter_);
connect_->enable("ssl-ca", mitm);
- filter.back().name = "CRT";
- filter.back().masks.emplace_back("*.crt");
connect_->add_file("ssl-certs", "Certificate bundle",
main_->config()->get("ssl-certs",
default_cert_bundle()),
"Certificate bundle to verify remote SSL connections",
- GuiFile::FILE_OPEN, filter);
+ GuiFile::FILE_OPEN, crt_filter_);
connect_->enable("ssl-certs", mitm);
connect_->add_bool("unsecure", "Allow unsecure remote connections",
main_->config()->get("unsecure", false),
@@ -797,6 +828,7 @@ public:
main_->add_to_clipboard(pkg.data, "application/octet-stream");
} else if (id == ACTION_CLEAR) {
packages_->clear();
+ set_modified(true);
} else if (id == ACTION_PROXY_LOG) {
if (!proxy_logwnd_) {
if (!proxy_logger_) proxy_logger_.reset(new StringLogger());
@@ -822,16 +854,20 @@ public:
dlg->add_string("issuer", "Issuer name",
main_->config()->get("issuer", ""),
"Issuer name to user instead of default.");
- std::vector<GuiFile::Filter> filter;
- filter.emplace_back();
- filter.back().name = "PEM";
- filter.back().masks.emplace_back("*.pem");
dlg->add_file("output", "File", "",
"File to save certificate and key pair to.",
- GuiFile::FILE_SAVE, filter);
+ GuiFile::FILE_SAVE, pem_filter_);
dlg->add_listener(lst.get());
dlg->show(main_.get());
#endif // HAVE_SSL
+ } else if (id == ACTION_NEW) {
+ new_file();
+ } else if (id == ACTION_OPEN) {
+ load_file();
+ } else if (id == ACTION_SAVE) {
+ save_file();
+ } else if (id == ACTION_SAVE_AS) {
+ save_as_file();
} else {
assert(false);
}
@@ -840,6 +876,7 @@ public:
// GuiMain::Listener
bool about_to_exit(GuiMain* main) override {
assert(main_.get() == main);
+ if (abort_if_modified()) return false;
return true;
}
@@ -927,6 +964,7 @@ public:
void package(Monitor* monitor, ::Package const& package) override {
assert(monitor == monitor_.get());
packages_->package(package);
+ set_modified(true);
}
void package_data(Monitor* monitor, uint32_t id,
@@ -936,6 +974,7 @@ public:
if (has_selection_ && index == selection_) {
selected_row(nullptr, index);
}
+ set_modified(true);
}
private:
@@ -957,6 +996,192 @@ private:
}
}
+ void set_modified(bool modified) {
+ if (modified_ == modified) return;
+ modified_ = modified;
+ update_title();
+ }
+
+ bool abort_if_modified() {
+ if (!modified_) return false;
+ std::unique_ptr<GuiForm> form(
+ GuiForm::create("Confirm?",
+ "If you continue you will lose unsaved packages"));
+ return !form->show(main_.get());
+ }
+
+ void update_title() {
+ if (file_.empty()) {
+ main_->set_title(APP_TITLE);
+ menu_->enable_item(ACTION_SAVE, false);
+ } else {
+ auto pos = file_.find_last_of('/');
+ pos = pos == std::string::npos ? 0 : pos + 1;
+ auto title = file_.substr(pos);
+ if (modified_) title.push_back('*');
+ title.append(" - ");
+ title.append(APP_TITLE);
+ main_->set_title(title);
+ menu_->enable_item(ACTION_SAVE, modified_);
+ }
+ }
+
+ void new_file() {
+ if (abort_if_modified()) return;
+ file_.clear();
+ modified_ = false;
+ packages_->clear();
+ update_title();
+ }
+
+ void load_file() {
+ if (abort_if_modified()) return;
+ file_ = main_->file_dialog("Open File", "", GuiFile::FILE_OPEN,
+ file_filter_);
+ if (file_.empty()) return;
+
+ load(file_, packages_.get());
+ update_title();
+ }
+
+ void save_file() {
+ if (file_.empty()) {
+ assert(false);
+ return;
+ }
+
+ if (save(packages_.get(), file_)) {
+ modified_ = false;
+ update_title();
+ }
+ }
+
+ void save_as_file() {
+ auto file = main_->file_dialog("Save File", file_, GuiFile::FILE_SAVE,
+ file_filter_);
+ if (file.empty()) return;
+ file_ = file;
+
+ if (save(packages_.get(), file)) {
+ file_ = file;
+ modified_ = false;
+ update_title();
+ }
+ }
+
+ void show_error(std::string const& error) {
+ std::unique_ptr<GuiMessage> message(GuiMessage::create(GuiMessage::ERROR,
+ "Error", error));
+ message->show(main_.get());
+ }
+
+ bool load(std::string const& file, PackageList* packages) {
+ std::ifstream in(file);
+ if (!in.good()) {
+ show_error("Unable to open " + file + " for reading");
+ return false;
+ }
+ uint8_t header[8];
+ in.read(reinterpret_cast<char*>(header), 8);
+ if (!in.good() || read_u32(header) != 1) {
+ show_error("Not a valid package file: " +file);
+ return false;
+ }
+ uint32_t count = read_u32(header + 4);
+ bool good = true;
+ while (count--) {
+ good = false;
+ in.read(reinterpret_cast<char*>(header), 8);
+ if (!in.good()) break;
+ auto size = read_u64(header);
+ if (size == 0) break;
+ uint8_t buf[8192];
+ size_t avail = std::min(size, sizeof(buf));
+ in.read(reinterpret_cast<char*>(buf), avail);
+ if (!in.good()) break;
+ ::Package pkg;
+ auto pkg_size = read_package(&pkg, buf, avail);
+ if (pkg_size == 0) {
+ if (avail == size) break;
+ size_t need = std::min(static_cast<size_t>(1024) * 1024, size);
+ std::unique_ptr<uint8_t[]> mem(new uint8_t[need]);
+ memcpy(mem.get(), buf, avail);
+ in.read(reinterpret_cast<char*>(mem.get()) + avail, need - avail);
+ if (!in.good()) break;
+ pkg_size = read_package(&pkg, mem.get(), need);
+ if (pkg_size == 0) break;
+ packages->package(pkg);
+ if (need > pkg_size) {
+ packages->package_data(pkg.id, reinterpret_cast<char*>(mem.get())
+ + pkg_size, need - pkg_size, need == size);
+ }
+ size -= need;
+ } else {
+ packages->package(pkg);
+ if (avail > pkg_size) {
+ packages->package_data(pkg.id, reinterpret_cast<char*>(buf) + pkg_size,
+ avail - pkg_size, avail == size);
+ }
+ size -= avail;
+ }
+ while (size) {
+ avail = std::min(sizeof(buf), size);
+ in.read(reinterpret_cast<char*>(buf), avail);
+ if (!in.good()) break;
+ packages->package_data(pkg.id, reinterpret_cast<char*>(buf), avail,
+ avail == size);
+ size -= avail;
+ }
+ if (size) break;
+ good = true;
+ }
+ if (good) {
+ in.read(reinterpret_cast<char*>(header), 8);
+ if (!in.good() || read_u64(header) != 0) good = false;
+ }
+ if (!good) {
+ show_error("Error reading from " + file);
+ return false;
+ }
+ return true;
+ }
+
+ bool save(PackageList const* packages, std::string const& file) {
+ std::ofstream out(file);
+ if (!out.good()) {
+ show_error("Unable to open " + file + " for writing");
+ return false;
+ }
+ uint8_t header[8];
+ write_u32(header, 1); // Version
+ write_u32(header + 4, packages->rows()); // Count
+ out.write(reinterpret_cast<char*>(header), 8);
+ for (size_t i = 0; i < packages->rows(); ++i) {
+ auto const& pkg = packages->package(i);
+ uint8_t buf[8192];
+ std::unique_ptr<uint8_t[]> backup;
+ uint8_t* ptr = buf;
+ size_t need = write_package(pkg.pkg, buf, sizeof(buf));
+ if (need > sizeof(buf)) {
+ backup.reset(new uint8_t[need]);
+ ptr = backup.get();
+ write_package(pkg.pkg, ptr, need);
+ }
+ write_u64(header, need + pkg.data.size());
+ out.write(reinterpret_cast<char*>(header), 8);
+ out.write(reinterpret_cast<char*>(ptr), need);
+ backup.reset();
+ out.write(pkg.data.data(), pkg.data.size());
+ }
+ write_u64(header, 0); // EOF
+ out.write(reinterpret_cast<char*>(header), 8);
+ if (!out.good()) {
+ show_error("Error writing " + file);
+ return false;
+ }
+ return true;
+ }
+
std::unique_ptr<PackageList> packages_;
std::unique_ptr<GuiMain> main_;
std::unique_ptr<GuiMenu> menu_;
@@ -970,8 +1195,13 @@ private:
std::unique_ptr<StringLogger> proxy_logger_;
std::unique_ptr<Proxy> proxy_;
std::unique_ptr<GuiTextWindow> proxy_logwnd_;
+ std::vector<GuiFile::Filter> pem_filter_;
+ std::vector<GuiFile::Filter> crt_filter_;
bool has_selection_;
size_t selection_;
+ std::vector<GuiFile::Filter> file_filter_;
+ std::string file_;
+ bool modified_;
};
} // namespace