From 2dbfb58efe5e53b56bfda3ed21a3e41562205437 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Mon, 24 Jul 2017 23:43:07 +0200 Subject: Add tools -> Generate CA Added file field to GuiForm --- src/gui_form.hh | 10 ++++ src/gui_gtk.cc | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/gui_qt.cc | 109 +++++++++++++++++++++++++++++++++++++---- src/logger.cc | 12 +++++ src/logger.hh | 1 + src/monitor-gui.cc | 121 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 376 insertions(+), 18 deletions(-) (limited to 'src') 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 +#include #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 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 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 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(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(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(value.get())->value_); break; + case FILE: { + auto v = static_cast(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_; + GtkFileChooser* chooser_; + + FileValue(std::string const& id, std::string const& label, + std::string const& description, std::string const& value, + bool must_exist, std::vector 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(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 #include #include +#include #include #include #include @@ -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 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(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_; + + FileValue(std::string const& id, std::string const& label, + std::string const& description, + std::string const& value, + bool must_exist, std::vector 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::max(), edit)); } + case FILE: + edit->setText(QString::fromStdString( + static_cast(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(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("" + value->description_ + "")); 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(value.get())->value_); + case FILE: + static_cast(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 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 +#include #include #include -#include #include +#include +#include +#include #include #include @@ -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 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 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 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( + new GenerateFormDelegate(looper_.get(), main_->config())); + auto lst = std::unique_ptr( + new GenerateFormListener()); + auto dlg = std::unique_ptr( + 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 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); } -- cgit v1.2.3-70-g09d2