summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gui_form.hh10
-rw-r--r--src/gui_gtk.cc141
-rw-r--r--src/gui_qt.cc109
-rw-r--r--src/logger.cc12
-rw-r--r--src/logger.hh1
-rw-r--r--src/monitor-gui.cc121
6 files changed, 376 insertions, 18 deletions
diff --git a/src/gui_form.hh b/src/gui_form.hh
index 4ba5f15..190cf4e 100644
--- a/src/gui_form.hh
+++ b/src/gui_form.hh
@@ -4,6 +4,7 @@
#define GUI_FORM_HH
#include <string>
+#include <vector>
#include "gui_window.hh"
@@ -27,6 +28,14 @@ public:
virtual void add_number(std::string const& id, std::string const& label,
uint64_t value,
std::string const& description) = 0;
+ struct Filter {
+ std::string name;
+ std::vector<std::string> masks;
+ };
+ virtual void add_file(std::string const& id, std::string const& label,
+ std::string const& value,
+ std::string const& description, bool must_exist,
+ std::vector<Filter> const& filter) = 0;
virtual void add_listener(Listener* listener) = 0;
virtual void remove_listener(Listener* listener) = 0;
@@ -37,6 +46,7 @@ public:
virtual std::string get_string(std::string const& id) const = 0;
virtual uint64_t get_number(std::string const& id) const = 0;
+ virtual std::string get_file(std::string const& id) const = 0;
protected:
GuiForm() {}
diff --git a/src/gui_gtk.cc b/src/gui_gtk.cc
index 14c1f63..55ce88a 100644
--- a/src/gui_gtk.cc
+++ b/src/gui_gtk.cc
@@ -919,6 +919,15 @@ public:
values_.emplace_back(new NumberValue(id, label, description, value));
}
+ void add_file(std::string const& id, std::string const& label,
+ std::string const& value,
+ std::string const& description,
+ bool must_exist,
+ std::vector<Filter> const& filter) override {
+ values_.emplace_back(new FileValue(id, label, description, value,
+ must_exist, filter));
+ }
+
std::string get_string(std::string const& id) const override {
for (auto const& value : values_) {
if (value->id_ == id && value->type_ == STRING) {
@@ -948,6 +957,26 @@ public:
return 0;
}
+ std::string get_file(std::string const& id) const override {
+ for (auto const& value : values_) {
+ if (value->id_ == id && value->type_ == FILE) {
+ auto v = static_cast<FileValue*>(value.get());
+ if (v->must_exist_ && v->chooser_) {
+ std::string ret;
+ auto file = gtk_file_chooser_get_filename(v->chooser_);
+ if (file) {
+ ret.assign(file);
+ g_free(file);
+ }
+ return ret;
+ }
+ return v->value_;
+ }
+ }
+ assert(false);
+ return "";
+ }
+
void set_error(std::string const& error) override {
if (!error_) {
assert(false);
@@ -984,12 +1013,13 @@ public:
auto label = gtk_label_new(text_.c_str());
gtk_label_set_width_chars(GTK_LABEL(label), 35);
gtk_label_set_line_wrap(GTK_LABEL(label), true);
- gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 2, 1);
+ gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 3, 1);
}
for (auto& value : values_) {
auto label = gtk_label_new(value->label_.c_str());
value->entry_ = gtk_entry_new();
gtk_entry_set_activates_default(GTK_ENTRY(value->entry_), true);
+ GtkWidget* extra = nullptr;
switch (value->type_) {
case STRING:
gtk_entry_set_text(GTK_ENTRY(value->entry_),
@@ -1004,9 +1034,49 @@ public:
GTK_INPUT_PURPOSE_DIGITS);
break;
}
+ case FILE: {
+ auto v = static_cast<FileValue*>(value.get());
+ if (v->must_exist_) {
+ gtk_widget_destroy(value->entry_);
+ auto button = gtk_file_chooser_button_new(
+ v->label_.c_str(), GTK_FILE_CHOOSER_ACTION_OPEN);
+ v->chooser_ = GTK_FILE_CHOOSER(button);
+ value->entry_ = button;
+ } else {
+ auto chooser = gtk_file_chooser_native_new(
+ v->label_.c_str(),
+ GTK_WINDOW(dialog_),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ nullptr, nullptr);
+ extra = gtk_button_new_with_label("...");
+ g_signal_connect(G_OBJECT(extra), "clicked",
+ G_CALLBACK(show_filechooser), v);
+ v->chooser_ = GTK_FILE_CHOOSER(chooser);
+ gtk_editable_set_editable(GTK_EDITABLE(value->entry_), false);
+ gtk_file_chooser_set_do_overwrite_confirmation(v->chooser_, true);
+ }
+ if (!v->value_.empty()) {
+ gtk_file_chooser_set_filename(v->chooser_, v->value_.c_str());
+ }
+ for (auto const& filter : v->filter_) {
+ auto f = gtk_file_filter_new();
+ gtk_file_filter_set_name(f, filter.name.c_str());
+ for (auto const& mask : filter.masks) {
+ gtk_file_filter_add_pattern(f, mask.c_str());
+ }
+ gtk_file_chooser_add_filter(v->chooser_, f);
+ }
+ break;
+ }
}
gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
- gtk_grid_attach(GTK_GRID(grid), value->entry_, 1, row++, 1, 1);
+ if (extra) {
+ gtk_grid_attach(GTK_GRID(grid), value->entry_, 1, row, 1, 1);
+ gtk_grid_attach(GTK_GRID(grid), extra, 2, row, 1, 1);
+ } else {
+ gtk_grid_attach(GTK_GRID(grid), value->entry_, 1, row, 2, 1);
+ }
+ row++;
if (!value->description_.empty()) {
auto desc = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(desc),
@@ -1014,15 +1084,15 @@ public:
gtk_widget_set_halign(GTK_WIDGET(desc), GTK_ALIGN_END);
gtk_label_set_line_wrap(GTK_LABEL(desc), true);
gtk_label_set_max_width_chars(GTK_LABEL(desc), 35);
- gtk_grid_attach(GTK_GRID(grid), desc, 0, row++, 2, 1);
+ gtk_grid_attach(GTK_GRID(grid), desc, 0, row++, 3, 1);
}
}
error_ = gtk_label_new("");
gtk_label_set_xalign(GTK_LABEL(error_), 0.0);
- gtk_grid_attach(GTK_GRID(grid), error_, 0, row++, 2, 1);
+ gtk_grid_attach(GTK_GRID(grid), error_, 0, row++, 3, 1);
gtk_container_add(GTK_CONTAINER(content_area), grid);
gtk_widget_show_all(dialog_);
- row = add_extra_widgets(GTK_GRID(grid), row);
+ row = add_extra_widgets(GTK_GRID(grid), row, 3);
gtk_widget_hide(error_);
gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT);
gint result;
@@ -1041,6 +1111,22 @@ public:
get_number(value->entry_,
&static_cast<NumberValue*>(value.get())->value_);
break;
+ case FILE: {
+ auto v = static_cast<FileValue*>(value.get());
+ if (v->must_exist_) {
+ auto file = gtk_file_chooser_get_filename(v->chooser_);
+ if (file) {
+ v->value_ = file;
+ g_free(file);
+ } else {
+ v->value_.clear();
+ }
+ } else {
+ g_object_unref(v->chooser_);
+ }
+ v->chooser_ = nullptr;
+ break;
+ }
}
value->entry_ = nullptr;
}
@@ -1055,6 +1141,7 @@ protected:
enum Type {
STRING,
NUMBER,
+ FILE,
};
struct Value {
@@ -1089,6 +1176,20 @@ protected:
}
};
+ struct FileValue : public Value {
+ std::string value_;
+ bool must_exist_;
+ std::vector<Filter> filter_;
+ GtkFileChooser* chooser_;
+
+ FileValue(std::string const& id, std::string const& label,
+ std::string const& description, std::string const& value,
+ bool must_exist, std::vector<Filter> const& filter)
+ : Value(FILE, id, label, description), value_(value),
+ must_exist_(must_exist), filter_(filter) {
+ }
+ };
+
virtual bool notify_about_to_close() {
auto it = observers_.notify();
while (it.has_next()) {
@@ -1110,7 +1211,31 @@ protected:
return false;
}
- virtual gint add_extra_widgets(GtkGrid* UNUSED(grid), gint row) {
+ static void safe_entry_from_filename(GtkWidget* widget,
+ std::string const& filename) {
+ auto str = g_filename_to_utf8(filename.data(), filename.size(),
+ nullptr, nullptr, nullptr);
+ if (str) {
+ gtk_entry_set_text(GTK_ENTRY(widget), str);
+ g_free(str);
+ } else {
+ gtk_entry_set_text(GTK_ENTRY(widget), "");
+ }
+ }
+
+ static void show_filechooser(GtkButton* UNUSED(button), gpointer user_data) {
+ auto value = reinterpret_cast<FileValue*>(user_data);
+ auto ret = gtk_native_dialog_run(GTK_NATIVE_DIALOG(value->chooser_));
+ if (ret == GTK_RESPONSE_ACCEPT) {
+ auto file = gtk_file_chooser_get_filename(value->chooser_);
+ value->value_ = file;
+ g_free(file);
+ safe_entry_from_filename(value->entry_, value->value_);
+ }
+ }
+
+ virtual gint add_extra_widgets(GtkGrid* UNUSED(grid), gint row,
+ gint UNUSED(colspan)) {
return row;
}
@@ -1157,10 +1282,10 @@ public:
}
private:
- gint add_extra_widgets(GtkGrid* layout, gint row) override {
+ gint add_extra_widgets(GtkGrid* layout, gint row, gint colspan) override {
assert(!spinner_);
spinner_ = gtk_spinner_new();
- gtk_grid_attach(layout, spinner_, 0, row++, 2, 1);
+ gtk_grid_attach(layout, spinner_, 0, row++, colspan, 1);
return row;
}
diff --git a/src/gui_qt.cc b/src/gui_qt.cc
index 596ddda..59dc5e1 100644
--- a/src/gui_qt.cc
+++ b/src/gui_qt.cc
@@ -7,6 +7,7 @@
#include <QClipboard>
#include <QCloseEvent>
#include <QDialogButtonBox>
+#include <QFileDialog>
#include <QGridLayout>
#include <QHeaderView>
#include <QIntValidator>
@@ -850,6 +851,15 @@ public:
values_.emplace_back(new NumberValue(id, label, description, value));
}
+ void add_file(std::string const& id, std::string const& label,
+ std::string const& value,
+ std::string const& description,
+ bool must_exist,
+ std::vector<Filter> const& filter) override {
+ values_.emplace_back(new FileValue(id, label, description, value,
+ must_exist, filter));
+ }
+
std::string get_string(std::string const& id) const override {
for (auto const& value : values_) {
if (value->id_ == id && value->type_ == STRING) {
@@ -879,6 +889,19 @@ public:
return 0;
}
+ std::string get_file(std::string const& id) const override {
+ for (auto const& value : values_) {
+ if (value->id_ == id && value->type_ == FILE) {
+ if (value->edit_) {
+ return value->edit_->text().toStdString();
+ }
+ return static_cast<FileValue*>(value.get())->value_;
+ }
+ }
+ assert(false);
+ return "";
+ }
+
void set_error(std::string const& error) override {
if (!error_) {
assert(false);
@@ -900,6 +923,7 @@ protected:
enum Type {
STRING,
NUMBER,
+ FILE,
};
struct Value {
@@ -936,6 +960,20 @@ protected:
}
};
+ struct FileValue : public Value {
+ std::string value_;
+ bool must_exist_;
+ std::vector<Filter> filter_;
+
+ FileValue(std::string const& id, std::string const& label,
+ std::string const& description,
+ std::string const& value,
+ bool must_exist, std::vector<Filter> const& filter)
+ : Value(FILE, id, label, description), value_(value),
+ must_exist_(must_exist), filter_(filter) {
+ }
+ };
+
static bool get_number(QLineEdit* edit, uint64_t* number) {
assert(edit && number);
auto text = edit->text().toStdString();
@@ -956,7 +994,7 @@ protected:
int row = 0;
if (!text_.empty()) {
auto text = new QLabel(QString::fromStdString(text_));
- layout->addWidget(text, row++, 0, 1, 2);
+ layout->addWidget(text, row++, 0, 1, 3);
}
for (auto& value : values_) {
auto label = new QLabel(QString::fromStdString(value->label_));
@@ -975,20 +1013,35 @@ protected:
edit->setValidator(new QIntValidator(
0, std::numeric_limits<int>::max(), edit));
}
+ case FILE:
+ edit->setText(QString::fromStdString(
+ static_cast<FileValue*>(value.get())->value_));
+ break;
}
layout->addWidget(label, row, 0);
- layout->addWidget(edit, row, 1);
+ if (value->type_ == FILE) {
+ auto button = new QPushButton("...");
+ edit->setReadOnly(true);
+ dialog_->connect(button, &QPushButton::clicked,
+ [this,&value](bool UNUSED(checked)) {
+ showFileDialog(static_cast<FileValue*>(value.get()));
+ });
+ layout->addWidget(edit, row, 1);
+ layout->addWidget(button, row, 2);
+ } else {
+ layout->addWidget(edit, row, 1, 1, 2);
+ }
++row;
if (!value->description_.empty()) {
auto desc = new QLabel(
QString::fromStdString("<i>" + value->description_ + "</i>"));
desc->setAlignment(Qt::AlignTrailing);
desc->setWordWrap(true);
- layout->addWidget(desc, row++, 0, 1, 2);
+ layout->addWidget(desc, row++, 0, 1, 3);
}
}
error_ = new QLabel();
- layout->addWidget(error_, row++, 0, 1, 2);
+ layout->addWidget(error_, row++, 0, 1, 3);
error_->hide();
buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
@@ -996,8 +1049,8 @@ protected:
dialog_, &QDialog::accept);
dialog_->connect(buttons_, &QDialogButtonBox::rejected,
dialog_, &QDialog::reject);
- layout->addWidget(buttons_, row++, 0, 1, 2);
- add_extra_widgets(layout, row);
+ layout->addWidget(buttons_, row++, 0, 1, 3);
+ add_extra_widgets(layout, row, 3);
dialog_->setLayout(layout);
auto ret = dialog_->exec();
for (auto& value : values_) {
@@ -1009,6 +1062,10 @@ protected:
case NUMBER:
get_number(value->edit_,
&static_cast<NumberValue*>(value.get())->value_);
+ case FILE:
+ static_cast<FileValue*>(value.get())->value_ =
+ value->edit_->text().toStdString();
+ break;
}
value->edit_ = nullptr;
}
@@ -1028,13 +1085,47 @@ protected:
return true;
}
- virtual int add_extra_widgets(QGridLayout*, int row) {
+ virtual int add_extra_widgets(QGridLayout*, int row, int UNUSED(columns)) {
return row;
}
virtual void reset_extra_widgets() {
}
+ void showFileDialog(FileValue* value) {
+ QString filter;
+ for (auto const& f : value->filter_) {
+ if (!filter.isEmpty()) filter.append(";;");
+ filter.append(QString::fromStdString(f.name));
+ filter.append(" (");
+ bool first = true;
+ for (auto const& mask : f.masks) {
+ if (first) {
+ first = false;
+ } else {
+ filter.append(' ');
+ }
+ filter.append(QString::fromStdString(mask));
+ }
+ filter.append(')');
+ }
+ if (value->must_exist_) {
+ auto file = QFileDialog::getOpenFileName(
+ dialog_, QString::fromStdString(value->label_),
+ value->edit_->text(), filter);
+ if (!file.isNull()) {
+ value->edit_->setText(file);
+ }
+ } else {
+ auto file = QFileDialog::getSaveFileName(
+ dialog_, QString::fromStdString(value->label_),
+ value->edit_->text(), filter);
+ if (!file.isNull()) {
+ value->edit_->setText(file);
+ }
+ }
+ }
+
std::string title_;
std::string text_;
Observers<GuiForm::Listener*> observers_;
@@ -1070,7 +1161,7 @@ public:
}
private:
- int add_extra_widgets(QGridLayout* layout, int row) override {
+ int add_extra_widgets(QGridLayout* layout, int row, int columns) override {
assert(buttons_);
if (buttons_) {
auto ok = buttons_->button(QDialogButtonBox::Ok);
@@ -1080,7 +1171,7 @@ private:
progress_ = new QProgressBar();
progress_->setMinimum(0);
progress_->setMaximum(0);
- layout->addWidget(progress_, row++, 0, 1, 2);
+ layout->addWidget(progress_, row++, 0, 1, columns);
progress_->hide();
return row;
}
diff --git a/src/logger.cc b/src/logger.cc
index 343bb4d..df1af0c 100644
--- a/src/logger.cc
+++ b/src/logger.cc
@@ -127,6 +127,12 @@ private:
FILE* fh_;
};
+class NullLogger : public Logger {
+public:
+ void out(Level UNUSED(lvl), char const* UNUSED(format), ...) override {
+ }
+};
+
} // namespace
// static
@@ -147,3 +153,9 @@ Logger* Logger::create_file(std::string const& path) {
}
return nullptr;
}
+
+// static
+Logger* Logger::null() {
+ static NullLogger logger;
+ return &logger;
+}
diff --git a/src/logger.hh b/src/logger.hh
index 3700635..576175a 100644
--- a/src/logger.hh
+++ b/src/logger.hh
@@ -19,6 +19,7 @@ public:
static Logger* create_stderr();
static Logger* create_syslog(std::string const& name);
static Logger* create_file(std::string const& path);
+ static Logger* null();
virtual void out(Level lvl, char const* format, ...)
#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT
diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc
index 806f8da..8044a02 100644
--- a/src/monitor-gui.cc
+++ b/src/monitor-gui.cc
@@ -3,10 +3,13 @@
#include "common.hh"
#include <fcntl.h>
+#include <fstream>
#include <memory>
#include <string.h>
-#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/types.h>
+#include <thread>
+#include <unistd.h>
#include <unordered_map>
#include <vector>
@@ -27,6 +30,7 @@
#include "observers.hh"
#include "proxy.hh"
#include "resolver.hh"
+#include "ssl.hh"
namespace {
@@ -39,6 +43,7 @@ std::string const ACTION_COPY_RAW = "copy_raw";
std::string const ACTION_COPY_TEXT = "copy_text";
std::string const ACTION_CLEAR = "clear";
std::string const ACTION_PROXY_LOG = "proxy_log";
+std::string const ACTION_GENERATE_CA = "genca";
bool valid_hostname(std::string const& host) {
return !host.empty();
@@ -436,6 +441,95 @@ private:
Resolver* resolver_;
};
+ class GenerateFormListener : public GuiFormApply::Listener {
+ public:
+ bool about_to_close(GuiForm* form) override {
+ auto const& output = form->get_file("output");
+ if (output.empty()) {
+ form->set_error("No output file selected.");
+ return false;
+ }
+ return true;
+ }
+ };
+
+ class GenerateFormDelegate : public GuiFormApply::Delegate {
+ public:
+ GenerateFormDelegate(Looper* looper, Config* config)
+ : looper_(looper), config_(config) {
+ }
+
+ ~GenerateFormDelegate() {
+ if (read_) looper_->remove(read_.get());
+ }
+
+ void apply(GuiFormApply* form) override {
+ auto const& issuer = form->get_string("issuer");
+ auto const& output = form->get_file("output");
+ if (output.empty()) {
+ form->set_error("No ouput file selected.");
+ form->applied(false);
+ return;
+ }
+ config_->set("issuer", issuer);
+ config_->set("genca-output", output);
+ int fd[2];
+ if (pipe(fd)) {
+ form->set_error("Unable to create pipe.");
+ form->applied(false);
+ return;
+ }
+ read_.reset(fd[0]);
+ logger_.reset(new StringLogger());
+ looper_->add(read_.get(), Looper::EVENT_READ, [=](int, uint8_t) -> void {
+ char c;
+ if (read(read_.get(), &c, 1) != 1) {
+ c = 0;
+ }
+ looper_->remove(read_.get());
+ read_.reset();
+ if (c) {
+ form->applied(true);
+ } else {
+ form->set_error(logger_->text()->text());
+ form->applied(false);
+ }
+ });
+ // Hack to get SSL lib to initialize on the right thread
+ SSLCert::load(Logger::null(), "");
+
+ std::thread(generate, issuer, output, logger_.get(), fd[1]).detach();
+ }
+
+ private:
+ static void generate(std::string const& issuer, std::string const& output,
+ Logger* logger, int fd) {
+ char c = doGenerate(issuer, output, logger) ? 1 : 0;
+ write(fd, &c, 1);
+ close(fd);
+ }
+
+ static bool doGenerate(std::string const& issuer, std::string const& output,
+ Logger* logger) {
+ std::unique_ptr<SSLEntropy> entropy(SSLEntropy::create(logger));
+ if (!entropy) return false;
+ std::string key;
+ if (!SSLKey::generate(logger, entropy.get(), &key)) return false;
+ std::string cert;
+ std::unique_ptr<SSLKey> pkey(SSLKey::load(logger, key));
+ if (!SSLCert::generate(logger, entropy.get(), nullptr, nullptr, issuer,
+ pkey.get(), &cert)) return false;
+ std::ofstream of(output);
+ of << cert << '\n' << key << std::endl;
+ return of.good();
+ }
+
+ io::auto_fd read_;
+ Looper* looper_;
+ Config* config_;
+ std::unique_ptr<StringLogger> logger_;
+ };
+
public:
MonitorGui()
: packages_(new PackageList()),
@@ -456,6 +550,8 @@ public:
edit->add_item(ACTION_COPY_RAW, "Copy binary");
edit->add_separator();
edit->add_item(ACTION_CLEAR, "Clear");
+ auto tools = menu_->add_menu("Tools");
+ tools->add_item(ACTION_GENERATE_CA, "Generate CA...");
auto help = menu_->add_menu("Help");
help->add_item(ACTION_ABOUT, "About...");
help->add_item(ACTION_PROXY_LOG, "Proxy log...");
@@ -582,6 +678,29 @@ public:
} else {
proxy_logwnd_->focus();
}
+ } else if (id == ACTION_GENERATE_CA) {
+ auto del = std::unique_ptr<GuiFormApply::Delegate>(
+ new GenerateFormDelegate(looper_.get(), main_->config()));
+ auto lst = std::unique_ptr<GuiFormApply::Listener>(
+ new GenerateFormListener());
+ auto dlg = std::unique_ptr<GuiFormApply>(
+ GuiFormApply::create("Generate CA...",
+ "Generate a certificate and key pair "
+ "and save to file.",
+ "Generate",
+ del.get()));
+ dlg->add_string("issuer", "Issuer name",
+ main_->config()->get("issuer", ""),
+ "Issuer name to user instead of default.");
+ std::vector<GuiFormApply::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.",
+ false, filter);
+ dlg->add_listener(lst.get());
+ dlg->show(main_.get());
} else {
assert(false);
}