diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/monitor-gui.cc | 266 |
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 |
