diff --git a/fltk/main.cxx b/fltk/main.cxx index 2afad1c..a59b7a9 100644 --- a/fltk/main.cxx +++ b/fltk/main.cxx @@ -1,366 +1,365 @@ /* main.cpp - A Fltk based dialog for PIN entry. Copyright (C) 2016 Anatoly madRat L. Berenblit Written by Anatoly madRat L. Berenblit <madrat-@users.noreply.github.com>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define PGMNAME (PACKAGE_NAME"-fltk") #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <getopt.h> #include <assert.h> -#include "memory.h" #include <memory> #include <pinentry.h> #ifdef FALLBACK_CURSES #include <pinentry-curses.h> #endif #include <string> #include <string.h> #include <stdexcept> #include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/fl_ask.H> #include "pinwindow.h" #include "passwindow.h" #include "qualitypasswindow.h" #define CONFIRM_STRING "Confirm" #define REPEAT_ERROR_STRING "Texts do not match" #define OK_STRING "OK" #define CANCEL_STRING "Cancel" char *application = NULL; static std::string escape_accel_utf8(const char *s) { std::string result; if (NULL != s) { result.reserve(strlen(s)); for (const char *p = s; *p; ++p) { if ('&' == *p) result.push_back(*p); result.push_back(*p); } } return result; } // For button labels // Accelerator '_' (used e.g. by GPG2) is converted to '&' (for FLTK) // '&' is escaped as in escape_accel_utf8() static std::string convert_accel_utf8(const char *s) { static bool last_was_underscore = false; std::string result; if (NULL != s) { result.reserve(strlen(s)); for (const char *p = s; *p; ++p) { // & => && if ('&' == *p) result.push_back(*p); // _ => & (handle '__' as escaped underscore) if ('_' == *p) { if (last_was_underscore) { result.push_back(*p); last_was_underscore = false; } else last_was_underscore = true; } else { if (last_was_underscore) result.push_back('&'); result.push_back(*p); last_was_underscore = false; } } } return result; } class cancel_exception { }; static int get_quality(const char *passwd, void *ptr) { if (NULL == passwd || 0 == *passwd) return 0; pinentry_t* pe = reinterpret_cast<pinentry_t*>(ptr); return pinentry_inq_quality(*pe, passwd, strlen(passwd)); } bool is_short(const char *str) { return fl_utf_nb_char(reinterpret_cast<const unsigned char*>(str), strlen(str)) < 16; } bool is_empty(const char *str) { return (NULL == str) || (0 == *str); } static int fltk_cmd_handler(pinentry_t pe) { int ret = -1; try { // TODO: Add parent window to pinentry-fltk window //if (pe->parent_wid){} std::string title = !is_empty(pe->title)?pe->title:PGMNAME; std::string ok = convert_accel_utf8(pe->ok?pe->ok:(pe->default_ok?pe->default_ok:OK_STRING)); std::string cancel = convert_accel_utf8(pe->cancel?pe->cancel:(pe->default_cancel?pe->default_cancel:CANCEL_STRING)); if (!!pe->pin) // password (or confirmation) { std::unique_ptr<PinWindow> window; bool isSimple = (NULL == pe->quality_bar) && // pinenty.h: If this is not NULL ... is_empty(pe->error) && is_empty(pe->description) && is_short(pe->prompt); if (isSimple) { assert(NULL == pe->description); window.reset(PinWindow::create()); window->prompt(pe->prompt); } else { PassWindow *pass = NULL; if (pe->quality_bar) // pinenty.h: If this is not NULL ... { QualityPassWindow *p = QualityPassWindow::create(get_quality, &pe); window.reset(p); pass = p; p->quality(pe->quality_bar); } else { pass = PassWindow::create(); window.reset(pass); } if (NULL == pe->description) { pass->description(pe->prompt); pass->prompt(" "); } else { pass->description(pe->description); pass->prompt(escape_accel_utf8(pe->prompt).c_str()); } pass->description(pe->description); pass->prompt(escape_accel_utf8(pe->prompt).c_str()); if (NULL != pe->error) pass->error(pe->error); } window->ok(ok.c_str()); window->cancel(cancel.c_str()); window->title(title.c_str()); window->showModal((NULL != application)?1:0, &application); if (NULL == window->passwd()) throw cancel_exception(); const std::string password = window->passwd(); window.reset(); if (pe->repeat_passphrase) { const char *dont_match = NULL; do { if (NULL == dont_match && is_short(pe->repeat_passphrase)) { window.reset(PinWindow::create()); window->prompt(escape_accel_utf8(pe->repeat_passphrase).c_str()); } else { PassWindow *pass = PassWindow::create(); window.reset(pass); pass->description(pe->repeat_passphrase); pass->prompt(" "); pass->error(dont_match); } window->ok(ok.c_str()); window->cancel(cancel.c_str()); window->title(title.c_str()); window->showModal(); if (NULL == window->passwd()) throw cancel_exception(); if (password == window->passwd()) { pe->repeat_okay = 1; ret = 1; break; } else { dont_match = (NULL!=pe->repeat_error_string)? pe->repeat_error_string:REPEAT_ERROR_STRING; } } while (true); } else ret = 1; pinentry_setbufferlen(pe, password.size()+1); if (pe->pin) { memcpy(pe->pin, password.c_str(), password.size()+1); pe->result = password.size(); ret = password.size(); } } else { // Confirmation or Message Dialog title, desc Fl_Window dummy(0,0, 1,1); dummy.border(0); dummy.show((NULL != application)?1:0, &application); dummy.hide(); fl_message_title(title.c_str()); int result = -1; const char *message = (NULL != pe->description)?pe->description:CONFIRM_STRING; if (pe->one_button) { fl_ok = ok.c_str(); fl_message("%s", message); result = 1; // OK } else if (pe->notok) { switch (fl_choice("%s", ok.c_str(), cancel.c_str(), pe->notok, message)) { case 0: result = 1; break; case 2: result = 0; break; default: case 1: result = -1;break; } } else { switch (fl_choice("%s", ok.c_str(), cancel.c_str(), NULL, message)) { case 0: result = 1; break; default: case 1: result = -1;break; } } // cancel -> pe->canceled = true, 0 // ok/y -> 1 // no -> 0 if (-1 == result) pe->canceled = true; ret = (1 == result); } Fl::check(); } catch (const cancel_exception&) { ret = -1; } catch (...) { ret = -1; } // do_touch_file(pe); only for NCURSES? return ret; } pinentry_cmd_handler_t pinentry_cmd_handler = fltk_cmd_handler; int main(int argc, char *argv[]) { application = *argv; pinentry_init(PGMNAME); #ifdef FALLBACK_CURSES if (!pinentry_have_display(argc, argv)) pinentry_cmd_handler = curses_cmd_handler; else #endif { //FLTK understood only -D (--display) // and should be converted into -di[splay] const static struct option long_options[] = { {"display", required_argument, 0, 'D' }, {NULL, no_argument, 0, 0 } }; for (int i = 0; i < argc-1; ++i) { switch (getopt_long(argc-i, argv+i, "D:", long_options, NULL)) { case 'D': { char* emul[] = {application, (char*)"-display", optarg}; Fl::args(3, emul); i = argc; break; } default: break; } } } pinentry_parse_opts(argc, argv); return pinentry_loop() ?EXIT_FAILURE:EXIT_SUCCESS; } diff --git a/fltk/pinwindow.cxx b/fltk/pinwindow.cxx index c187dd5..8dd6e65 100644 --- a/fltk/pinwindow.cxx +++ b/fltk/pinwindow.cxx @@ -1,251 +1,251 @@ /* pinwindow.cxx - PinWindow is a simple fltk dialog for entring password with timeout. if needed description (long text), error message, qualitybar and etc should used PassWindow. Copyright (C) 2016 Anatoly madRat L. Berenblit Written by Anatoly madRat L. Berenblit <madrat-@users.noreply.github.com>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. SPDX-License-Identifier: GPL-2.0+ */ #include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Box.H> #include <FL/Fl_Input.H> #include <FL/Fl_Button.H> #include <FL/Fl_Secret_Input.H> #include <FL/Fl_Return_Button.H> #include <FL/Fl_Pixmap.H> -#include "memory.h" +#include "../secmem/secmem.h" #include "encrypt.xpm" #include "icon.xpm" #include "pinwindow.h" const char *PinWindow::TITLE = "Password"; const char *PinWindow::BUTTON_OK = "OK"; const char *PinWindow::BUTTON_CANCEL = "Cancel"; const char *PinWindow::PROMPT = "Passphrase:"; static const char *timeout_format = "%s(%d)"; static Fl_Pixmap encrypt(encrypt_xpm); static Fl_Pixmap icon(icon_xpm); PinWindow::PinWindow() : window_(NULL) ,message_(NULL) ,input_(NULL) ,ok_(NULL) ,cancel_(NULL) ,cancel_name_(BUTTON_CANCEL) ,passwd_(NULL) ,timeout_(0) { } PinWindow::~PinWindow() { wipe(); release(); delete window_; } void PinWindow::release() { if (NULL != passwd_) { memset(passwd_, 0, strlen(passwd_)); secmem_free(passwd_); } passwd_ = NULL; } void PinWindow::title(const char *name) { set_label(window_, name, TITLE); } void PinWindow::ok(const char* name) { set_label(ok_, name, BUTTON_OK); } void PinWindow::cancel(const char* label) { if (NULL != label && 0 != *label) cancel_name_ = label; else cancel_name_ = BUTTON_CANCEL; update_cancel_label(); } void PinWindow::prompt(const char *name) { set_label(message_, name, PROMPT); } void PinWindow::timeout(unsigned int time) { if (timeout_ == time) return; // A xor B ~ A != B if ( (time>0) != (timeout_>0)) { //enable or disable if (time>0) Fl::add_timeout(1.0, timeout_cb, this); else Fl::remove_timeout(timeout_cb, this); } timeout_=time; update_cancel_label(); --timeout_; } void PinWindow::showModal() { if (NULL != window_) { window_->show(); Fl::run(); } Fl::check(); } void PinWindow::showModal(const int argc, char* argv[]) { if (NULL != window_) { window_->show(argc, argv); Fl::run(); } Fl::check(); } int PinWindow::init(const int cx, const int cy) { assert(NULL == window_); window_ = new Fl_Window(cx, cy, TITLE); Fl_RGB_Image app(&icon); window_->icon(&app); icon_ = new Fl_Box(10, 10, 64, 64); icon_->image(encrypt); message_ = new Fl_Box(79, 5, cx-99, 44, PROMPT); message_->align(Fl_Align(FL_ALIGN_LEFT_TOP | FL_ALIGN_WRAP | FL_ALIGN_INSIDE)); // left input_ = new Fl_Secret_Input(79, 59, cx-99, 25); input_->labeltype(FL_NO_LABEL); const int button_y = cy-40; ok_ = new Fl_Return_Button(cx-300, button_y, 120, 25, BUTTON_OK); ok_->callback(ok_cb, this); cancel_ = new Fl_Button(cx-160, button_y, 120, 25); update_cancel_label(); cancel_->callback(cancel_cb, this); window_->hotspot(input_); window_->set_modal(); return 84; }; void PinWindow::update_cancel_label() { if (timeout_ == 0) { cancel_->label(cancel_name_.c_str()); } else { const size_t len = cancel_name_.size()+strlen(timeout_format)+10+1; char *buf = new char[len]; snprintf(buf, len, timeout_format, cancel_name_.c_str(), timeout_); cancel_->copy_label(buf); delete[] buf; // no way to attach label } } void PinWindow::timeout_cb(void* val) { PinWindow *self = reinterpret_cast<PinWindow*>(val); if (self->timeout_ == 0) { cancel_cb(self->cancel_, self); } else { self->update_cancel_label(); --self->timeout_; Fl::repeat_timeout(1.0, timeout_cb, val); } } void PinWindow::cancel_cb(Fl_Widget *button, void *val) { PinWindow *self = reinterpret_cast<PinWindow*>(val); self->wipe(); self->release(); self->window_->hide(); } void PinWindow::ok_cb(Fl_Widget *button, void *val) { PinWindow *self = reinterpret_cast<PinWindow*>(val); self->release(); const char *passwd = self->input_->value(); size_t len = strlen(passwd)+1; self->passwd_ = reinterpret_cast<char*>(secmem_malloc(len)); if (NULL != self->passwd_) memcpy(self->passwd_, passwd, len); self->wipe(); self->window_->hide(); } void PinWindow::wipe() { int len = input_->size(); char* emul = new char[len+1]; for (int i=0; i<len; ++i) { emul[i] = 0x20 + rand()%(128-20); // [20..127] } emul[len] = 0; input_->replace(0, len, emul, len); delete[] emul; input_->value(TITLE); // hide size too } PinWindow* PinWindow::create() { PinWindow* p = new PinWindow; p->init(410, 140); p->window_->end(); p->input_->take_focus(); return p; } diff --git a/gnome3/pinentry-gnome3.c b/gnome3/pinentry-gnome3.c index 0b8d8d1..8a8fbed 100644 --- a/gnome3/pinentry-gnome3.c +++ b/gnome3/pinentry-gnome3.c @@ -1,549 +1,547 @@ /* pinentry-gnome3.c * Copyright (C) 2015 g10 Code GmbH * * pinentry-gnome-3 is a pinentry application for GNOME 3. It tries * to follow the Gnome Human Interface Guide as close as possible. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include <gcr/gcr-base.h> #include <string.h> #include <stdlib.h> #include <assuan.h> -#include "memory.h" - #include "pinentry.h" #ifdef FALLBACK_CURSES #include "pinentry-curses.h" #endif #define PGMNAME "pinentry-gnome3" #ifndef VERSION # define VERSION #endif struct pe_gnome3_run_s { pinentry_t pinentry; GcrPrompt *prompt; GMainLoop *main_loop; int ret; guint timeout_id; int timed_out; }; static void pe_gcr_prompt_password_done (GObject *source_object, GAsyncResult *res, gpointer user_data); static void pe_gcr_prompt_confirm_done (GObject *source_object, GAsyncResult *res, gpointer user_data); static gboolean pe_gcr_timeout_done (gpointer user_data); static gchar * pinentry_utf8_validate (gchar *text) { gchar *result; if (!text) return NULL; if (g_utf8_validate (text, -1, NULL)) return g_strdup (text); /* Failure: Assume that it was encoded in the current locale and convert it to utf-8. */ result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL); if (!result) { gchar *p; result = p = g_strdup (text); while (!g_utf8_validate (p, -1, (const gchar **) &p)) *p = '?'; } return result; } static void _propagate_g_error_to_pinentry (pinentry_t pe, GError *error, gpg_err_code_t code, const char *loc) { char *t; /* We can't return the result of g_strdup_printf directly, because * this needs to be g_free'd, but the users of PE (e.g., * pinentry_reset in pinentry/pinentry.c) use free. */ t = g_strdup_printf ("%d: %s", error->code, error->message); if (t) { /* If strdup fails, then PE->SPECIFIC_ERR_INFO will be NULL, * which is exactly what we want if strdup fails. So, there is * no need to check for failure. */ pe->specific_err_info = strdup (t); g_free (t); } else { pe->specific_err_info = NULL; } pe->specific_err = gpg_error (code); pe->specific_err_loc = loc; } static GcrPrompt * create_prompt (pinentry_t pe, int confirm) { GcrPrompt *prompt; GError *error = NULL; char *msg, *p; char window_id[32]; /* Create the prompt. */ prompt = GCR_PROMPT (gcr_system_prompt_open (pe->timeout ? pe->timeout : -1, NULL, &error)); if (! prompt) { /* this means the timeout elapsed, but no prompt was ever shown. */ if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) { fprintf (stderr, "Timeout: the Gcr system prompter was already in use.\n"); pe->specific_err_info = strdup ("Timeout: the Gcr system prompter was already in use."); /* not using GPG_ERR_TIMEOUT here because the user never saw a prompt: */ pe->specific_err = gpg_error (GPG_ERR_PIN_ENTRY); } else { fprintf (stderr, "couldn't create prompt for gnupg passphrase: %s\n", error->message); _propagate_g_error_to_pinentry (pe, error, GPG_ERR_CONFIGURATION, "gcr_system_prompt_open"); } g_error_free (error); return NULL; } /* Set the messages for the various buttons, etc. */ p = pinentry_get_title (pe); if (p) { msg = pinentry_utf8_validate (p); if (msg) { gcr_prompt_set_title (prompt, msg); g_free (msg); } free (p); } if (pe->description) { msg = pinentry_utf8_validate (pe->description); gcr_prompt_set_description (prompt, msg); g_free (msg); } /* An error occurred during the last prompt. */ if (pe->error) { msg = pinentry_utf8_validate (pe->error); gcr_prompt_set_warning (prompt, msg); g_free (msg); } if (! pe->prompt && confirm) gcr_prompt_set_message (prompt, "Message"); else if (! pe->prompt && ! confirm) gcr_prompt_set_message (prompt, "Enter Passphrase"); else { msg = pinentry_utf8_validate (pe->prompt); gcr_prompt_set_message (prompt, msg); g_free (msg); } if (! confirm) gcr_prompt_set_password_new (prompt, !!pe->repeat_passphrase); if (pe->ok || pe->default_ok) { msg = pinentry_utf8_validate (pe->ok ?: pe->default_ok); gcr_prompt_set_continue_label (prompt, msg); g_free (msg); } /* XXX: Disable this button if pe->one_button is set. */ if (pe->cancel || pe->default_cancel) { msg = pinentry_utf8_validate (pe->cancel ?: pe->default_cancel); gcr_prompt_set_cancel_label (prompt, msg); g_free (msg); } if (confirm && pe->notok) { /* XXX: Add support for the third option. */ } /* gcr expects a string; we have a int. see gcr's ui/frob-system-prompt.c for example conversion using %lu */ snprintf (window_id, sizeof (window_id), "%lu", (long unsigned int)pe->parent_wid); gcr_prompt_set_caller_window (prompt, window_id); #ifdef HAVE_LIBSECRET if (! confirm && pe->allow_external_password_cache && pe->keyinfo) { if (pe->default_pwmngr) { msg = pinentry_utf8_validate (pe->default_pwmngr); gcr_prompt_set_choice_label (prompt, msg); g_free (msg); } else gcr_prompt_set_choice_label (prompt, "Automatically unlock this key, whenever I'm logged in"); } #endif return prompt; } static int gnome3_cmd_handler (pinentry_t pe) { struct pe_gnome3_run_s state; state.main_loop = g_main_loop_new (NULL, FALSE); if (!state.main_loop) { pe->specific_err_info = strdup ("Failed to create GMainLoop"); pe->specific_err = gpg_error (GPG_ERR_PIN_ENTRY); pe->specific_err_loc = "g_main_loop_new"; pe->canceled = 1; return -1; } state.pinentry = pe; state.ret = 0; state.timeout_id = 0; state.timed_out = 0; state.prompt = create_prompt (pe, !(pe->pin)); if (!state.prompt) { pe->canceled = 1; return -1; } if (pe->pin) gcr_prompt_password_async (state.prompt, NULL, pe_gcr_prompt_password_done, &state); else gcr_prompt_confirm_async (state.prompt, NULL, pe_gcr_prompt_confirm_done, &state); if (pe->timeout) state.timeout_id = g_timeout_add_seconds (pe->timeout, pe_gcr_timeout_done, &state); g_main_loop_run (state.main_loop); /* clean up state: */ if (state.timeout_id && !state.timed_out) g_source_destroy (g_main_context_find_source_by_id (NULL, state.timeout_id)); g_clear_object (&state.prompt); g_main_loop_unref (state.main_loop); return state.ret; }; static void pe_gcr_prompt_password_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { struct pe_gnome3_run_s *state = user_data; GcrPrompt *prompt = GCR_PROMPT (source_object); if (state && prompt && state->prompt == prompt) { const char *password; GError *error = NULL; pinentry_t pe = state->pinentry; int ret = -1; /* "The returned password is valid until the next time a method is called to display another prompt." */ password = gcr_prompt_password_finish (prompt, res, &error); if ((! password && ! error) || (error && error->code == G_IO_ERROR_CANCELLED)) { /* operation was cancelled or timed out. */ ret = -1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); if (error) g_error_free (error); } else if (error) { _propagate_g_error_to_pinentry (pe, error, GPG_ERR_PIN_ENTRY, "gcr_system_password_finish"); g_error_free (error); ret = -1; } else { pinentry_setbufferlen (pe, strlen (password) + 1); if (pe->pin) strcpy (pe->pin, password); if (pe->repeat_passphrase) pe->repeat_okay = 1; #ifdef HAVE_LIBSECRET if (pe->allow_external_password_cache && pe->keyinfo) pe->may_cache_password = gcr_prompt_get_choice_chosen (prompt); #endif ret = 1; } state->ret = ret; } if (state) g_main_loop_quit (state->main_loop); } static void pe_gcr_prompt_confirm_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { struct pe_gnome3_run_s *state = user_data; GcrPrompt *prompt = GCR_PROMPT (source_object); if (state && prompt && state->prompt == prompt) { GcrPromptReply reply; GError *error = NULL; pinentry_t pe = state->pinentry; int ret = -1; /* XXX: We don't support a third button! */ reply = gcr_prompt_confirm_finish (prompt, res, &error); if (error) { if (error->code == G_IO_ERROR_CANCELLED) { pe->canceled = 1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); } else _propagate_g_error_to_pinentry (state->pinentry, error, GPG_ERR_PIN_ENTRY, "gcr_system_confirm_finish"); g_error_free (error); ret = 0; } else if (reply == GCR_PROMPT_REPLY_CONTINUE /* XXX: Hack since gcr doesn't yet support one button message boxes treat cancel the same as okay. */ || pe->one_button) { /* Confirmation. */ ret = 1; } else /* GCR_PROMPT_REPLY_CANCEL */ { pe->canceled = 1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); ret = 0; } state->ret = ret; } if (state) g_main_loop_quit (state->main_loop); } static gboolean pe_gcr_timeout_done (gpointer user_data) { struct pe_gnome3_run_s *state = user_data; if (!state) return FALSE; state->timed_out = 1; gcr_prompt_close (state->prompt); return FALSE; } pinentry_cmd_handler_t pinentry_cmd_handler = gnome3_cmd_handler; /* Test whether there is a GNOME screensaver running that happens to * be locked. Note that if there is no GNOME screensaver running at * all the answer is still FALSE. */ static gboolean pe_gnome_screen_locked (void) { GDBusConnection *dbus; GError *error = NULL; GVariant *reply, *reply_bool; gboolean ret; dbus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (!dbus) { fprintf (stderr, "failed to connect to user session D-Bus (%d): %s", error ? error->code : -1, error ? error->message : "<no GError>"); if (error) g_error_free (error); return FALSE; } /* this is intended to be the equivalent of: * dbus-send --print-reply=literal --session \ * --dest=org.gnome.ScreenSaver \ * /org/gnome/ScreenSaver \ * org.gnome.ScreenSaver.GetActive */ reply = g_dbus_connection_call_sync (dbus, "org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "GetActive", NULL, ((const GVariantType *) "(b)"), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); g_object_unref(dbus); if (!reply) { /* G_IO_ERROR_IS_DIRECTORY is the expected response when there is * no gnome screensaver at all, don't be noisy in that case: */ if (!(error && error->code == G_IO_ERROR_IS_DIRECTORY)) fprintf (stderr, "Failed to get d-bus reply for org.gnome.ScreenSaver.GetActive (%d): %s\n", error ? error->code : -1, error ? error->message : "<no GError>"); if (error) g_error_free (error); return FALSE; } reply_bool = g_variant_get_child_value (reply, 0); if (!reply_bool) { fprintf (stderr, "Failed to get d-bus boolean from org.gnome.ScreenSaver.GetActive; assuming screensaver is not locked\n"); ret = FALSE; } else { ret = g_variant_get_boolean (reply_bool); g_variant_unref (reply_bool); } g_variant_unref (reply); return ret; } /* Test whether we can create a system prompt or not. This briefly * does create a system prompt, which blocks other tools from making * the same request concurrently, so we just create it to test if it is * available, and quickly close it. */ static int pe_gcr_system_prompt_available (void) { GcrSystemPrompt *prompt; GError *error = NULL; int ret = 0; prompt = GCR_SYSTEM_PROMPT (gcr_system_prompt_open (0, NULL, &error)); if (prompt) { ret = 1; if (!gcr_system_prompt_close (prompt, NULL, &error)) fprintf (stderr, "failed to close test Gcr System Prompt (%d): %s\n", error ? error->code : -1, error ? error->message : "<no GError>"); g_clear_object (&prompt); } else if (error && error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) { /* This one particular failure is OK; we're clearly capable of * making a system prompt, even though someone else has the * system prompter right now: */ ret = 1; } if (error) g_error_free (error); return ret; } int main (int argc, char *argv[]) { pinentry_init (PGMNAME); #ifdef FALLBACK_CURSES if (!getenv ("DBUS_SESSION_BUS_ADDRESS")) { fprintf (stderr, "No $DBUS_SESSION_BUS_ADDRESS found," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } else if (!pe_gcr_system_prompt_available ()) { fprintf (stderr, "No Gcr System Prompter available," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } else if (pe_gnome_screen_locked ()) { fprintf (stderr, "GNOME screensaver is locked," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } #endif pinentry_parse_opts (argc, argv); if (pinentry_loop ()) return 1; return 0; } diff --git a/pinentry/password-cache.c b/pinentry/password-cache.c index f9523b1..21ebfc5 100644 --- a/pinentry/password-cache.c +++ b/pinentry/password-cache.c @@ -1,172 +1,172 @@ /* password-cache.c - Password cache support. Copyright (C) 2015 g10 Code GmbH This file is part of PINENTRY. PINENTRY is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. PINENTRY is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see <https://www.gnu.org/licenses/>. SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #ifdef HAVE_LIBSECRET # include <libsecret/secret.h> #endif #include "password-cache.h" -#include "memory.h" +#include "../secmem/secmem.h" #ifdef HAVE_LIBSECRET static const SecretSchema * gpg_schema (void) { static const SecretSchema the_schema = { "org.gnupg.Passphrase", SECRET_SCHEMA_NONE, { { "stored-by", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "keygrip", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "NULL", 0 }, } }; return &the_schema; } static char * keygrip_to_label (const char *keygrip) { char const prefix[] = "GnuPG: "; char *label; label = malloc (sizeof (prefix) + strlen (keygrip)); if (label) { memcpy (label, prefix, sizeof (prefix) - 1); strcpy (&label[sizeof (prefix) - 1], keygrip); } return label; } #endif void password_cache_save (const char *keygrip, const char *password) { #ifdef HAVE_LIBSECRET char *label; GError *error = NULL; if (! *keygrip) return; label = keygrip_to_label (keygrip); if (! label) return; if (! secret_password_store_sync (gpg_schema (), SECRET_COLLECTION_DEFAULT, label, password, NULL, &error, "stored-by", "GnuPG Pinentry", "keygrip", keygrip, NULL)) { fprintf (stderr, "Failed to cache password for key %s with secret service: %s\n", keygrip, error->message); g_error_free (error); } free (label); #else (void) keygrip; (void) password; return; #endif } char * password_cache_lookup (const char *keygrip, int *fatal_error) { #ifdef HAVE_LIBSECRET GError *error = NULL; char *password; char *password2; if (! *keygrip) return NULL; password = secret_password_lookup_nonpageable_sync (gpg_schema (), NULL, &error, "keygrip", keygrip, NULL); if (error != NULL) { if (fatal_error) *fatal_error = 1; fprintf (stderr, "Failed to lookup password for key %s with secret service: %s\n", keygrip, error->message); g_error_free (error); return NULL; } if (! password) /* The password for this key is not cached. Just return NULL. */ return NULL; /* The password needs to be returned in secmem allocated memory. */ password2 = secmem_malloc (strlen (password) + 1); if (password2) strcpy(password2, password); else fprintf (stderr, "secmem_malloc failed: can't copy password!\n"); secret_password_free (password); return password2; #else (void) keygrip; (void) fatal_error; return NULL; #endif } /* Try and remove the cached password for key grip. Returns -1 on error, 0 if the key is not found and 1 if the password was removed. */ int password_cache_clear (const char *keygrip) { #ifdef HAVE_LIBSECRET GError *error = NULL; int removed = secret_password_clear_sync (gpg_schema (), NULL, &error, "keygrip", keygrip, NULL); if (error != NULL) { fprintf (stderr, "Failed to clear password for key %s with secret service: %s\n", keygrip, error->message); g_debug("%s", error->message); g_error_free (error); return -1; } if (removed) return 1; return 0; #else (void) keygrip; return -1; #endif } diff --git a/pinentry/pinentry-curses.c b/pinentry/pinentry-curses.c index c40fe34..3ca7a76 100644 --- a/pinentry/pinentry-curses.c +++ b/pinentry/pinentry-curses.c @@ -1,1816 +1,1815 @@ /* pinentry-curses.c - A secure curses dialog for PIN entry, library version * Copyright (C) 2002, 2015 g10 Code GmbH * * This file is part of PINENTRY. * * PINENTRY is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * PINENTRY is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <assert.h> #include <curses.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <locale.h> #include <iconv.h> #if defined(HAVE_LANGINFO_H) # include <langinfo.h> #elif defined(HAVE_W32_SYSTEM) # include <stdio.h> # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # include <windows.h> /* A simple replacement for nl_langinfo that only understands CODESET. */ # define CODESET 1 char * nl_langinfo (int ignore) { static char codepage[20]; UINT cp = GetACP (); (void)ignore; sprintf (codepage, "CP%u", cp); return codepage; } #endif #include <limits.h> #include <string.h> #include <errno.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #ifdef HAVE_UTIME_H #include <utime.h> #endif /*HAVE_UTIME_H*/ -#include <memory.h> - #ifdef HAVE_WCHAR_H #include <wchar.h> #endif /*HAVE_WCHAR_H*/ #include <assuan.h> #include "pinentry.h" #if GPG_ERROR_VERSION_NUMBER < 0x011900 /* 1.25 */ # define GPG_ERR_WINDOW_TOO_SMALL 301 # define GPG_ERR_MISSING_ENVVAR 303 #endif /* FIXME: We should allow configuration of these button labels and in any case use the default_ok, default_cancel values if available. However, I have no clue about curses and localization. */ #define STRING_OK "<OK>" #define STRING_NOTOK "<No>" #define STRING_CANCEL "<Cancel>" #define USE_COLORS (has_colors () && COLOR_PAIRS >= 4) static short pinentry_color[] = { -1, -1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE }; static int init_screen; #ifndef HAVE_DOSISH_SYSTEM static int timed_out; #endif #ifdef HAVE_NCURSESW typedef wchar_t CH; #define STRLEN(x) wcslen (x) #define STRWIDTH(x) wcswidth (x, wcslen (x)) #define ADDCH(x) addnwstr (&x, 1); #define CHWIDTH(x) wcwidth (x) #define NULLCH L'\0' #define NLCH L'\n' #define SPCH L' ' #else typedef char CH; #define STRLEN(x) strlen (x) #define STRWIDTH(x) strlen (x) #define ADDCH(x) addch ((unsigned char) x) #define CHWIDTH(x) 1 #define NULLCH '\0' #define NLCH '\n' #define SPCH ' ' #endif typedef enum { DIALOG_POS_NONE, DIALOG_POS_PIN, DIALOG_POS_REPEAT_PIN, DIALOG_POS_OK, DIALOG_POS_NOTOK, DIALOG_POS_CANCEL } dialog_pos_t; struct dialog { dialog_pos_t pos; char *repeat_pin; int pin_y; int pin_x; int repeat_pin_y; int repeat_pin_x; int quality_x; int quality_y; /* Width of the PIN field. */ int pin_size; int repeat_pin_size; int quality_size; /* Cursor location in PIN field. */ int pin_loc; int repeat_pin_loc; int pin_max; /* Length of PIN. */ int pin_len; int got_input; int no_echo; int repeat_pin_len; int ok_y; int ok_x; char *ok; int cancel_y; int cancel_x; char *cancel; int notok_y; int notok_x; char *notok; int error_y; int error_x; int error_height; int width; CH *error; CH *repeat_error; CH *repeat_ok; pinentry_t pinentry; }; typedef struct dialog *dialog_t; /* Flag to remember whether a warning has been printed. */ static int lc_ctype_unknown_warning; static char * pinentry_utf8_to_local (const char *lc_ctype, const char *text) { iconv_t cd; const char *input = text; size_t input_len = strlen (text) + 1; char *output; size_t output_len; char *output_buf; size_t processed; char *old_ctype; char *target_encoding; const char *pgmname = pinentry_get_pgmname (); /* If no locale setting could be determined, simply copy the string. */ if (!lc_ctype) { if (! lc_ctype_unknown_warning) { fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n", pgmname); lc_ctype_unknown_warning = 1; } return strdup (text); } old_ctype = strdup (setlocale (LC_CTYPE, NULL)); if (!old_ctype) return NULL; setlocale (LC_CTYPE, lc_ctype); target_encoding = nl_langinfo (CODESET); if (!target_encoding) target_encoding = "?"; setlocale (LC_CTYPE, old_ctype); free (old_ctype); /* This is overkill, but simplifies the iconv invocation greatly. */ output_len = input_len * MB_LEN_MAX; output_buf = output = malloc (output_len); if (!output) return NULL; cd = iconv_open (target_encoding, "UTF-8"); if (cd == (iconv_t) -1) { fprintf (stderr, "%s: can't convert from UTF-8 to %s: %s\n", pgmname, target_encoding, strerror (errno)); free (output_buf); return NULL; } processed = iconv (cd, (ICONV_CONST char **)&input, &input_len, &output, &output_len); iconv_close (cd); if (processed == (size_t) -1 || input_len) { fprintf (stderr, "%s: error converting from UTF-8 to %s: %s\n", pgmname, target_encoding, strerror (errno)); free (output_buf); return NULL; } return output_buf; } /* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With SECURE set to true, use secure memory for the returned buffer. Return NULL on error. */ static char * pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure) { char *old_ctype; char *source_encoding; iconv_t cd; const char *input = text; size_t input_len = strlen (text) + 1; char *output; size_t output_len; char *output_buf; size_t processed; const char *pgmname = pinentry_get_pgmname (); /* If no locale setting could be determined, simply copy the string. */ if (!lc_ctype) { if (! lc_ctype_unknown_warning) { fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n", pgmname); lc_ctype_unknown_warning = 1; } output_buf = secure? secmem_malloc (input_len) : malloc (input_len); if (output_buf) strcpy (output_buf, input); return output_buf; } old_ctype = strdup (setlocale (LC_CTYPE, NULL)); if (!old_ctype) return NULL; setlocale (LC_CTYPE, lc_ctype); source_encoding = nl_langinfo (CODESET); setlocale (LC_CTYPE, old_ctype); free (old_ctype); /* This is overkill, but simplifies the iconv invocation greatly. */ output_len = input_len * MB_LEN_MAX; output_buf = output = secure? secmem_malloc (output_len):malloc (output_len); if (!output) return NULL; cd = iconv_open ("UTF-8", source_encoding); if (cd == (iconv_t) -1) { fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n", pgmname, source_encoding? source_encoding : "?", strerror (errno)); if (secure) secmem_free (output_buf); else free (output_buf); return NULL; } processed = iconv (cd, (ICONV_CONST char **)&input, &input_len, &output, &output_len); iconv_close (cd); if (processed == (size_t) -1 || input_len) { fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n", pgmname, source_encoding? source_encoding : "?", strerror (errno)); if (secure) secmem_free (output_buf); else free (output_buf); return NULL; } return output_buf; } /* Return the next line up to MAXWIDTH columns wide in START and LEN. Return value is the width needed for the line. The first invocation should have 0 as *LEN. If the line ends with a \n, it is a normal line that will be continued. If it is a '\0' the end of the text is reached after this line. In all other cases there is a forced line break. A full line is returned and will be continued in the next line. */ static int collect_line (int maxwidth, CH **start_p, int *len_p) { int last_space = 0; int len = *len_p; int width = 0; CH *end; /* Skip to next line. */ *start_p += len; /* Skip leading space. */ while (**start_p == SPCH) (*start_p)++; end = *start_p; len = 0; while (width < maxwidth - 1 && *end != NULLCH && *end != NLCH) { if (*end == SPCH) last_space = len; width += CHWIDTH (*end); len++; end++; } if (*end != NULLCH && *end != NLCH && last_space != 0) { /* We reached the end of the available space, but still have characters to go in this line. We can break the line into two parts at a space. */ len = last_space; (*start_p)[len] = NLCH; } *len_p = len + 1; return width; } #ifdef HAVE_NCURSESW static CH * utf8_to_local (char *lc_ctype, char *string) { mbstate_t ps; size_t len; char *local; const char *p; wchar_t *wcs = NULL; char *old_ctype = NULL; local = pinentry_utf8_to_local (lc_ctype, string); if (!local) return NULL; old_ctype = strdup (setlocale (LC_CTYPE, NULL)); setlocale (LC_CTYPE, lc_ctype? lc_ctype : ""); p = local; memset (&ps, 0, sizeof(mbstate_t)); len = mbsrtowcs (NULL, &p, strlen (string), &ps); if (len == (size_t)-1) { free (local); goto leave; } wcs = calloc (len + 1, sizeof(wchar_t)); if (!wcs) { free (local); goto leave; } p = local; memset (&ps, 0, sizeof(mbstate_t)); mbsrtowcs (wcs, &p, len, &ps); free (local); leave: if (old_ctype) { setlocale (LC_CTYPE, old_ctype); free (old_ctype); } return wcs; } #else static CH * utf8_to_local (const char *lc_ctype, const char *string) { return pinentry_utf8_to_local (lc_ctype, string); } #endif static int test_repeat (dialog_t); static void draw_error (dialog_t dialog, int *xpos, int *ypos, int repeat_matches) { CH *p = NULL, *error = NULL; int i = 0; int x = dialog->width; int error_y = dialog->error_y; pinentry_t pinentry = dialog->pinentry; if (dialog->pinentry->confirm) return; for (i = 0; i < dialog->error_height; i++) { move (error_y+i, dialog->error_x+1); hline(' ', dialog->width-2); } if (repeat_matches == -1) { error = p = dialog->error && dialog->pinentry->error ? dialog->error : NULL; if (!p) { if (dialog->error_y || dialog->error_x) { for (i = 0; i < dialog->error_height; i++) { move (i+ dialog->error_y, dialog->error_x); hline(' ', dialog->width - 2); } move (dialog->error_y, dialog->error_x); vline(0, dialog->error_height+1); } } if (pinentry->repeat_passphrase && dialog->repeat_ok) { p = dialog->repeat_ok; goto draw; } } else if (pinentry->repeat_passphrase && dialog->repeat_ok && repeat_matches == 1) p = dialog->repeat_ok; else if (pinentry->repeat_passphrase && dialog->repeat_error && repeat_matches == 0) p = dialog->repeat_error; else p = dialog->error; draw: while (p && *p) { move (error_y, dialog->error_x); vline(0, dialog->error_height+1); move (error_y, dialog->error_x+1); addch(' '); if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE) { if (dialog->repeat_ok && (repeat_matches == 1 || repeat_matches == -1)) { attroff (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0)); attron (COLOR_PAIR (3) | (pinentry->color_so_bright ? A_BOLD : 0)); } else { attroff (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0)); attron (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0)); } } else standout (); for (i = 0; *p && *p != NLCH; p++) if (i < x - 4) { i++; if (dialog->error || (pinentry->repeat_passphrase && dialog->repeat_error && repeat_matches == 0)) { ADDCH (*p); } else if (dialog->repeat_ok && (repeat_matches == 1 || repeat_matches == -1)) { ADDCH (*p); } else addch (' '); // Error without any OK set needs clearing. } hline(' ', dialog->width-i-4); if (USE_COLORS) { if (repeat_matches == 0 && pinentry->color_so != PINENTRY_COLOR_NONE) attroff (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0)); else if (repeat_matches == 1 && pinentry->color_ok != PINENTRY_COLOR_NONE) attroff (COLOR_PAIR (3) | (pinentry->color_so_bright ? A_BOLD : 0)); attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0)); } else standend (); if (*p == '\n') p++; i = 0; error_y++; } if (error) { move (*ypos, *xpos); addch (ACS_VLINE); } (*ypos)++; for (error_y = 0; error_y < dialog->error_height; error_y++) (*ypos)++; } static int test_repeat (dialog_t diag) { int x = diag->error_x; int y = diag->error_y; int ox, oy; int ret = 0; if (!diag->pinentry->repeat_passphrase) ret = 1; if (!diag->pinentry->pin && !diag->repeat_pin) ret = 1; if ((diag->pinentry->pin && !*diag->pinentry->pin && (!diag->repeat_pin || !*diag->repeat_pin))) ret = 1; if (diag->repeat_pin && !*diag->repeat_pin && (!diag->pinentry->pin || !*diag->pinentry->pin)) ret = 1; if (diag->pinentry->pin && diag->repeat_pin && !strcmp (diag->pinentry->pin, diag->repeat_pin)) ret = 1; getyx (stdscr, oy, ox); draw_error (diag, &x, &y, ret); wmove (stdscr, oy, ox); return ret; } static int dialog_create (pinentry_t pinentry, dialog_t dialog) { int err = 0; int size_y; int size_x; int y; int x; int ypos; int xpos; int description_x = 0; int error_x = 0; CH *description = NULL; CH *error = NULL; CH *ok = NULL; CH *prompt = NULL; CH *repeat_passphrase = NULL; CH *repeat_error_string = NULL; CH *repeat_ok_string = NULL; dialog->pinentry = pinentry; dialog->error_height = 0; #define COPY_OUT(what) \ do \ if (pinentry->what) \ { \ what = utf8_to_local (pinentry->lc_ctype, pinentry->what); \ if (!what) \ { \ err = 1; \ pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM); \ pinentry->specific_err_loc = "dialog_create_copy"; \ goto out; \ } \ } \ while (0) COPY_OUT (description); COPY_OUT (prompt); COPY_OUT (repeat_passphrase); COPY_OUT (error); if (!error || !*error) { free (error); error = NULL; } dialog->error = error; if (repeat_passphrase) { COPY_OUT (repeat_error_string); if (!repeat_error_string || !*repeat_error_string) { free (repeat_error_string); repeat_error_string = NULL; } dialog->repeat_error = repeat_error_string; if (repeat_error_string) { if (!error || (error && STRLEN (error) < STRLEN (repeat_error_string))) error = repeat_error_string; } COPY_OUT (repeat_ok_string); if (!repeat_ok_string || !*repeat_ok_string) { free (repeat_ok_string); repeat_ok_string = NULL; } else ok = dialog->repeat_ok = repeat_ok_string; } /* There is no pinentry->default_notok. Map it to pinentry->notok. */ #define default_notok notok #define MAKE_BUTTON(which,default) \ do \ { \ char *new = NULL; \ if (pinentry->default_##which || pinentry->which) \ { \ int len; \ char *msg; \ int i, j; \ \ msg = pinentry->which; \ if (! msg) \ msg = pinentry->default_##which; \ len = strlen (msg); \ \ new = malloc (len + 3); \ if (!new) \ { \ err = 1; \ pinentry->specific_err = gpg_error_from_syserror (); \ pinentry->specific_err_loc = "dialog_create_mk_button"; \ goto out; \ } \ \ new[0] = '<'; \ for (i = 0, j = 1; i < len; i ++, j ++) \ { \ if (msg[i] == '_') \ { \ i ++; \ if (msg[i] == 0) \ /* _ at end of string. */ \ break; \ } \ new[j] = msg[i]; \ } \ \ new[j] = '>'; \ new[j + 1] = 0; \ } \ dialog->which = pinentry_utf8_to_local (pinentry->lc_ctype, \ new ? new : default); \ free (new); \ if (!dialog->which) \ { \ err = 1; \ pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM); \ pinentry->specific_err_loc = "dialog_create_utf8conv"; \ goto out; \ } \ } \ while (0) MAKE_BUTTON (ok, STRING_OK); if (!pinentry->one_button) MAKE_BUTTON (cancel, STRING_CANCEL); else dialog->cancel = NULL; if (!pinentry->one_button && pinentry->notok) MAKE_BUTTON (notok, STRING_NOTOK); else dialog->notok = NULL; getmaxyx (stdscr, size_y, size_x); /* Check if all required lines fit on the screen. */ y = 1; /* Top frame. */ if (description) { CH *start = description; int len = 0; do { int width = collect_line (size_x - 4, &start, &len); if (width > description_x) description_x = width; y++; } while (start[len - 1]); y++; } if (pinentry->pin) { if (error) { CH *start = error; int len = 0; do { int width = collect_line (size_x - 4, &start, &len); if (width > error_x) error_x = width; dialog->error_height++; y++; } while (start[len - 1]); y++; } if (ok) { CH *start = ok; int len = 0; int i = 0; do { int width = collect_line (size_x - 4, &start, &len); if (width > error_x) error_x = width; i++; } while (start[len - 1]); if (i > dialog->error_height) { y += i - dialog->error_height; dialog->error_height = i; y++; } } y += 2; /* Pin entry field. */ if (repeat_passphrase) { y += 2; y += 2; // quality meter } } y += 2; /* OK/Cancel and bottom frame. */ if (y > size_y) { err = 1; pinentry->specific_err = gpg_error (size_y < 0? GPG_ERR_MISSING_ENVVAR /* */ : GPG_ERR_WINDOW_TOO_SMALL); pinentry->specific_err_loc = "dialog_create"; goto out; } /* Check if all required columns fit on the screen. */ x = 0; if (description) { int new_x = description_x; if (new_x > size_x - 4) new_x = size_x - 4; if (new_x > x) x = new_x; } if (pinentry->pin) { #define MIN_PINENTRY_LENGTH 40 int new_x; if (error || ok) { new_x = error_x; if (new_x > size_x - 4) new_x = size_x - 4; if (new_x > x) x = new_x; } new_x = MIN_PINENTRY_LENGTH; if (prompt) { int n = repeat_passphrase ? STRWIDTH (repeat_passphrase) : 0; new_x += STRWIDTH (prompt) + n + 1; /* One space after prompt. */ } else if (repeat_passphrase) new_x += STRWIDTH (repeat_passphrase) + 1; if (new_x > size_x - 4) new_x = size_x - 4; if (new_x > x) x = new_x; } /* We position the buttons after the first, second and third fourth of the width. Account for rounding. */ if (x < 3 * strlen (dialog->ok)) x = 3 * strlen (dialog->ok); if (dialog->cancel) if (x < 3 * strlen (dialog->cancel)) x = 3 * strlen (dialog->cancel); if (dialog->notok) if (x < 3 * strlen (dialog->notok)) x = 3 * strlen (dialog->notok); /* Add the frame. */ x += 4; if (x > size_x) { err = 1; pinentry->specific_err = gpg_error (size_x < 0? GPG_ERR_MISSING_ENVVAR /* */ : GPG_ERR_WINDOW_TOO_SMALL); pinentry->specific_err_loc = "dialog_create"; goto out; } dialog->pos = DIALOG_POS_NONE; dialog->pin_max = pinentry->pin_len; dialog->pin_loc = dialog->repeat_pin_loc = 0; dialog->pin_len = dialog->repeat_pin_len = 0; ypos = (size_y - y) / 2; xpos = (size_x - x) / 2; move (ypos, xpos); addch (ACS_ULCORNER); hline (0, x - 2); move (ypos, xpos + x - 1); addch (ACS_URCORNER); move (ypos + 1, xpos + x - 1); vline (0, y - 2); move (ypos + y - 1, xpos); addch (ACS_LLCORNER); hline (0, x - 2); move (ypos + y - 1, xpos + x - 1); addch (ACS_LRCORNER); ypos++; dialog->width = x; if (description) { CH *start = description; int len = 0; do { int i; move (ypos, xpos); addch (ACS_VLINE); addch (' '); collect_line (size_x - 4, &start, &len); for (i = 0; i < len - 1; i++) { ADDCH (start[i]); } if (start[len - 1] != NULLCH && start[len - 1] != NLCH) ADDCH (start[len - 1]); ypos++; } while (start[len - 1]); move (ypos, xpos); addch (ACS_VLINE); ypos++; } if (pinentry->pin) { int i; if (error || ok) { dialog->error_x = xpos; dialog->error_y = ypos; draw_error (dialog, &xpos, &ypos, -1); } move (ypos, xpos); addch (ACS_VLINE); addch (' '); dialog->pin_y = ypos; dialog->pin_x = xpos + 2; dialog->pin_size = x - 4; if (prompt) { CH *p = prompt; i = STRWIDTH (prompt); if (i > x - 4 - MIN_PINENTRY_LENGTH) i = x - 4 - MIN_PINENTRY_LENGTH; dialog->pin_x += i + 1; dialog->pin_size -= i + 1; i = STRLEN (prompt); while (i-- > 0) { ADDCH (*(p++)); } addch (' '); } for (i = 0; i < dialog->pin_size; i++) addch ('_'); ypos++; move (ypos, xpos); addch (ACS_VLINE); ypos++; if (repeat_passphrase) { CH *p = repeat_passphrase; move (ypos, xpos); addch (ACS_VLINE); addch (' '); dialog->repeat_pin_y = ypos; dialog->repeat_pin_x = xpos + 2; dialog->repeat_pin_size = x - 4; i = STRLEN (p); if (i > x - 4 - MIN_PINENTRY_LENGTH) i = x - 4 - MIN_PINENTRY_LENGTH; dialog->repeat_pin_x += i + 1; dialog->repeat_pin_size -= i + 1; while (i-- > 0) { ADDCH (*(p++)); } addch (' '); for (i = 0; i < dialog->repeat_pin_size; i++) addch ('_'); ypos++; move (ypos, xpos); vline(0, 3); dialog->quality_y = ypos + 1; dialog->quality_x = xpos + 3; dialog->quality_size = x - 6; move (ypos, xpos + 2); addch (ACS_ULCORNER); hline (0, x - 6); move (ypos, xpos + 2 + x - 5); addch (ACS_URCORNER); move (ypos + 1, xpos + 2); vline (0, 1); move (ypos + 1, xpos + 2 + x -5); vline (0, 1); move (ypos + 2, xpos + 2); addch (ACS_LLCORNER); hline (0, x - 6); move (ypos + 2, xpos + 2 + x - 5); addch (ACS_LRCORNER); move (ypos + 2, xpos + 2 + x - 5); move (dialog->quality_y, dialog->quality_x); ypos += 3; } } move (ypos, xpos); addch (ACS_VLINE); if (dialog->cancel || dialog->notok) { dialog->ok_y = ypos; /* Calculating the left edge of the left button, rounding down. */ dialog->ok_x = xpos + 2 + ((x - 4) / 3 - strlen (dialog->ok)) / 2; move (dialog->ok_y, dialog->ok_x); addstr (dialog->ok); if (! pinentry->pin && dialog->notok) { dialog->notok_y = ypos; /* Calculating the left edge of the middle button, rounding up. */ dialog->notok_x = xpos + x / 2 - strlen (dialog->notok) / 2; move (dialog->notok_y, dialog->notok_x); addstr (dialog->notok); } if (dialog->cancel) { dialog->cancel_y = ypos; /* Calculating the left edge of the right button, rounding up. */ dialog->cancel_x = xpos + x - 2 - ((x - 4) / 3 + strlen (dialog->cancel)) / 2; move (dialog->cancel_y, dialog->cancel_x); addstr (dialog->cancel); } } else { dialog->ok_y = ypos; /* Calculating the left edge of the OK button, rounding down. */ dialog->ok_x = xpos + x / 2 - strlen (dialog->ok) / 2; move (dialog->ok_y, dialog->ok_x); addstr (dialog->ok); } dialog->got_input = 0; dialog->no_echo = 0; out: if (description) free (description); if (prompt) free (prompt); if (repeat_passphrase) free (repeat_passphrase); return err; } static void set_cursor_state (int on) { static int normal_state = -1; static int on_last; if (normal_state < 0 && !on) { normal_state = curs_set (0); on_last = on; } else if (on != on_last) { curs_set (on ? normal_state : 0); on_last = on; } } static int dialog_switch_pos (dialog_t diag, dialog_pos_t new_pos) { if (new_pos != diag->pos) { switch (diag->pos) { case DIALOG_POS_OK: move (diag->ok_y, diag->ok_x); addstr (diag->ok); break; case DIALOG_POS_NOTOK: if (diag->notok) { move (diag->notok_y, diag->notok_x); addstr (diag->notok); } break; case DIALOG_POS_CANCEL: if (diag->cancel) { move (diag->cancel_y, diag->cancel_x); addstr (diag->cancel); } break; default: break; } diag->pos = new_pos; switch (diag->pos) { case DIALOG_POS_PIN: move (diag->pin_y, diag->pin_x + diag->pin_loc); set_cursor_state (1); break; case DIALOG_POS_REPEAT_PIN: move (diag->repeat_pin_y, diag->repeat_pin_x + diag->repeat_pin_loc); if (diag->no_echo) { move (diag->repeat_pin_y, diag->repeat_pin_x); addstr ("[no echo]"); } set_cursor_state (1); break; case DIALOG_POS_OK: set_cursor_state (0); move (diag->ok_y, diag->ok_x); standout (); addstr (diag->ok); standend (); move (diag->ok_y, diag->ok_x); break; case DIALOG_POS_NOTOK: if (diag->notok) { set_cursor_state (0); move (diag->notok_y, diag->notok_x); standout (); addstr (diag->notok); standend (); move (diag->notok_y, diag->notok_x); } break; case DIALOG_POS_CANCEL: if (diag->cancel) { set_cursor_state (0); move (diag->cancel_y, diag->cancel_x); standout (); addstr (diag->cancel); standend (); move (diag->cancel_y, diag->cancel_x); } break; case DIALOG_POS_NONE: set_cursor_state (0); break; } refresh (); } return 0; } /* XXX Assume that field width is at least > 5. */ static void dialog_input (dialog_t diag, int alt, int chr) { int old_loc = diag->pos == DIALOG_POS_PIN ? diag->pin_loc : diag->repeat_pin_loc; int *pin_len; int *pin_loc; int *pin_size; int *pin_x, *pin_y; int *pin_max = &diag->pin_max; char *pin; assert (diag->pinentry->pin); assert (diag->pos == DIALOG_POS_PIN || diag->pos == DIALOG_POS_REPEAT_PIN); if (diag->pos == DIALOG_POS_PIN) { pin_len = &diag->pin_len; pin_loc = &diag->pin_loc; pin_size = &diag->pin_size; pin_x = &diag->pin_x; pin_y = &diag->pin_y; pin = diag->pinentry->pin; } else { pin_len = &diag->repeat_pin_len; pin_loc = &diag->repeat_pin_loc; pin_size = &diag->repeat_pin_size; pin_x = &diag->repeat_pin_x; pin_y = &diag->repeat_pin_y; pin = diag->repeat_pin; } if (alt && chr == KEY_BACKSPACE) /* Remap alt-backspace to control-W. */ chr = 'w' - 'a' + 1; switch (chr) { case KEY_BACKSPACE: /* control-h. */ case 'h' - 'a' + 1: /* ASCII DEL. What Mac OS X apparently emits when the "delete" (backspace) key is pressed. */ case 127: if (*pin_len > 0) { (*pin_len)--; (*pin_loc)--; if (*pin_loc == 0 && *pin_len > 0) { *pin_loc = *pin_size - 5; if (*pin_loc > *pin_len) *pin_loc = *pin_len; } } else if (!diag->got_input) { diag->no_echo = 1; move (*pin_y, *pin_x); addstr ("[no echo]"); } break; case 'l' - 'a' + 1: /* control-l */ /* Refresh the screen. */ endwin (); refresh (); break; case 'u' - 'a' + 1: /* control-u */ /* Erase the whole line. */ if (*pin_len > 0) { *pin_len = 0; *pin_loc = 0; } break; case 'w' - 'a' + 1: /* control-w. */ while (*pin_len > 0 && pin[*pin_len - 1] == ' ') { (*pin_len)--; (*pin_loc)--; if (*pin_loc < 0) { *pin_loc += *pin_size; if (*pin_loc > *pin_len) *pin_loc = *pin_len; } } while (*pin_len > 0 && pin[*pin_len - 1] != ' ') { (*pin_len)--; (*pin_loc)--; if (*pin_loc < 0) { *pin_loc += *pin_size; if (*pin_loc > *pin_len) *pin_loc = *pin_len; } } break; default: if (chr > 0 && chr < 256 && *pin_len < *pin_max) { /* Make sure there is enough room for this character and a following NUL byte. */ if (! pinentry_setbufferlen (diag->pinentry, *pin_len + 2)) { /* Bail. Here we use a simple approach. It would be better to have a pinentry_bug function. */ assert (!"setbufferlen failed"); abort (); } if (diag->pos == DIALOG_POS_REPEAT_PIN) { if (!*pin_len || *pin_len > diag->repeat_pin_len) { char *newp = secmem_realloc (diag->repeat_pin, *pin_len + 2); if (newp) pin = diag->repeat_pin = newp; else { secmem_free (diag->pinentry->pin); diag->pinentry->pin = 0; diag->pinentry->pin_len = 0; secmem_free (diag->repeat_pin); diag->repeat_pin = NULL; *pin_len = 0; /* Bail. Here we use a simple approach. It would be better to have a pinentry_bug function. */ assert (!"setbufferlen failed"); abort (); } } } else pin = diag->pinentry->pin; pin[*pin_len] = (char) chr; (*pin_len)++; (*pin_loc)++; if (*pin_loc == *pin_size && *pin_len < *pin_max) { *pin_loc = 5; if (*pin_loc < *pin_size - (*pin_max + 1 - *pin_len)) *pin_loc = *pin_size - (*pin_max + 1 - *pin_len); } } break; } diag->got_input = 1; if (!diag->no_echo) { if (old_loc < *pin_loc) { move (*pin_y, *pin_x + old_loc); while (old_loc++ < *pin_loc) addch ('*'); } else if (old_loc > *pin_loc) { move (*pin_y, *pin_x + *pin_loc); while (old_loc-- > *pin_loc) addch ('_'); } move (*pin_y, *pin_x + *pin_loc); } if (pin) pin[*pin_len] = 0; if (diag->pinentry->repeat_passphrase && diag->pos == DIALOG_POS_PIN) { int n = pinentry_inq_quality(diag->pinentry, pin, *pin_len); if (n >= 0) { char buf[16], *p = buf; int r; move(diag->quality_y, diag->quality_x); hline(' ', diag->quality_size); r = n*diag->quality_size/100; attroff (COLOR_PAIR (1) | (diag->pinentry->color_fg_bright ? A_BOLD : 0)); attron (COLOR_PAIR (4) | (diag->pinentry->color_qualitybar_bright ? A_BOLD : 0)); hline(ACS_BLOCK, r); attroff (COLOR_PAIR (4) | (diag->pinentry->color_fg_bright ? A_BOLD : 0)); attron (COLOR_PAIR (1) | (diag->pinentry->color_qualitybar_bright ? A_BOLD : 0)); if (n >= 0) { snprintf (buf, sizeof(buf), "%i%%", n); move(diag->quality_y, diag->quality_x+((diag->quality_size/2)-(strlen(buf)/2))); for (; p && *p; p++) addch(*p); } } } } static int dialog_run (pinentry_t pinentry, const char *tty_name, const char *tty_type) { int confirm_mode = !pinentry->pin; struct dialog diag; FILE *ttyfi = NULL; FILE *ttyfo = NULL; SCREEN *screen = 0; int done = 0; char *pin_utf8; int alt = 0; #ifndef HAVE_DOSISH_SYSTEM int no_input = 1; #endif +#ifdef HAVE_NCURSESW + char *old_ctype = NULL; +#endif memset (&diag, 0, sizeof (struct dialog)); #ifdef HAVE_NCURSESW - char *old_ctype = NULL; - if (pinentry->lc_ctype) { old_ctype = strdup (setlocale (LC_CTYPE, NULL)); setlocale (LC_CTYPE, pinentry->lc_ctype); } else setlocale (LC_CTYPE, ""); #endif /* Open the desired terminal if necessary. */ if (tty_name) { ttyfi = fopen (tty_name, "r"); if (!ttyfi) { pinentry->specific_err = gpg_error_from_syserror (); pinentry->specific_err_loc = "open_tty_for_read"; #ifdef HAVE_NCURSESW free (old_ctype); #endif return confirm_mode? 0 : -1; } ttyfo = fopen (tty_name, "w"); if (!ttyfo) { int err = errno; fclose (ttyfi); errno = err; pinentry->specific_err = gpg_error_from_syserror (); pinentry->specific_err_loc = "open_tty_for_write"; #ifdef HAVE_NCURSESW free (old_ctype); #endif return confirm_mode? 0 : -1; } screen = newterm (tty_type, ttyfo, ttyfi); if (!screen) { pinentry->specific_err = gpg_error (GPG_ERR_WINDOW_TOO_SMALL); pinentry->specific_err_loc = "curses_init"; fclose (ttyfo); fclose (ttyfi); #ifdef HAVE_NCURSESW free (old_ctype); #endif return confirm_mode? 0 : -1; } set_term (screen); } else { if (!init_screen) { if (!(isatty(fileno(stdin)) && isatty(fileno(stdout)))) { errno = ENOTTY; pinentry->specific_err = gpg_error_from_syserror (); pinentry->specific_err_loc = "isatty"; #ifdef HAVE_NCURSESW free (old_ctype); #endif return confirm_mode? 0 : -1; } init_screen = 1; initscr (); } else clear (); } keypad (stdscr, TRUE); /* Enable keyboard mapping. */ nonl (); /* Tell curses not to do NL->CR/NL on output. */ cbreak (); /* Take input chars one at a time, no wait for \n. */ noecho (); /* Don't echo input - in color. */ if (pinentry->ttyalert) { if (! strcmp(pinentry->ttyalert, "beep")) beep (); else if (! strcmp(pinentry->ttyalert, "flash")) flash (); } if (has_colors ()) { start_color (); /* Ncurses has use_default_colors, an extentions to the curses library, which allows use of -1 to select default color. */ #ifdef NCURSES_VERSION use_default_colors (); #else /* With no extention, we need to specify color explicitly. */ if (pinentry->color_fg == PINENTRY_COLOR_DEFAULT) pinentry->color_fg = PINENTRY_COLOR_WHITE; if (pinentry->color_bg == PINENTRY_COLOR_DEFAULT) pinentry->color_bg = PINENTRY_COLOR_BLACK; #endif if (pinentry->color_so == PINENTRY_COLOR_DEFAULT) { pinentry->color_so = PINENTRY_COLOR_RED; pinentry->color_so_bright = 1; } if (pinentry->color_ok == PINENTRY_COLOR_DEFAULT) { pinentry->color_ok = PINENTRY_COLOR_GREEN; pinentry->color_ok_bright = 1; } if (pinentry->color_qualitybar == PINENTRY_COLOR_DEFAULT) { pinentry->color_qualitybar = PINENTRY_COLOR_CYAN; pinentry->color_qualitybar_bright = 0; } if (COLOR_PAIRS >= 4) { init_pair (1, pinentry_color[pinentry->color_fg], pinentry_color[pinentry->color_bg]); init_pair (2, pinentry_color[pinentry->color_so], pinentry_color[pinentry->color_bg]); init_pair (3, pinentry_color[pinentry->color_ok], pinentry_color[pinentry->color_bg]); init_pair (4, pinentry_color[pinentry->color_qualitybar], pinentry_color[pinentry->color_bg]); bkgd (COLOR_PAIR (1)); attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0)); } } refresh (); /* Create the dialog. */ if (dialog_create (pinentry, &diag)) { /* Note: pinentry->specific_err has already been set. */ endwin (); if (screen) delscreen (screen); #ifdef HAVE_NCURSESW if (old_ctype) { setlocale (LC_CTYPE, old_ctype); free (old_ctype); } #endif if (ttyfi) fclose (ttyfi); if (ttyfo) fclose (ttyfo); return -2; } dialog_switch_pos (&diag, confirm_mode? DIALOG_POS_OK : DIALOG_POS_PIN); #ifndef HAVE_DOSISH_SYSTEM wtimeout (stdscr, 70); #endif do { int c; c = wgetch (stdscr); /* Refresh, accept single keystroke of input. */ #ifndef HAVE_DOSISH_SYSTEM if (timed_out && no_input) { done = -2; pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); break; } #endif switch (c) { case ERR: #ifndef HAVE_DOSISH_SYSTEM continue; #else done = -2; break; #endif case 27: /* Alt was pressed. */ alt = 1; /* Get the next key press. */ continue; case KEY_LEFT: case KEY_UP: switch (diag.pos) { case DIALOG_POS_OK: if (!confirm_mode) { if (diag.pinentry->repeat_passphrase) dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN); else if (diag.pinentry->pin) dialog_switch_pos (&diag, DIALOG_POS_PIN); } break; case DIALOG_POS_REPEAT_PIN: if (diag.pinentry->pin) dialog_switch_pos (&diag, DIALOG_POS_PIN); break; case DIALOG_POS_NOTOK: dialog_switch_pos (&diag, DIALOG_POS_OK); break; case DIALOG_POS_CANCEL: if (diag.notok) dialog_switch_pos (&diag, DIALOG_POS_NOTOK); else { if (!test_repeat (&diag)) dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN); else dialog_switch_pos (&diag, DIALOG_POS_OK); } break; default: break; } break; case KEY_RIGHT: case KEY_DOWN: switch (diag.pos) { case DIALOG_POS_PIN: if (diag.pinentry->repeat_passphrase) dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN); else dialog_switch_pos (&diag, DIALOG_POS_OK); break; case DIALOG_POS_REPEAT_PIN: if (test_repeat (&diag)) dialog_switch_pos (&diag, DIALOG_POS_OK); else dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; case DIALOG_POS_OK: if (diag.notok) dialog_switch_pos (&diag, DIALOG_POS_NOTOK); else dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; case DIALOG_POS_NOTOK: dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; default: break; } break; case '\t': switch (diag.pos) { case DIALOG_POS_PIN: if (diag.pinentry->repeat_passphrase) dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN); else dialog_switch_pos (&diag, DIALOG_POS_OK); break; case DIALOG_POS_REPEAT_PIN: if (test_repeat (&diag)) dialog_switch_pos (&diag, DIALOG_POS_OK); else dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; case DIALOG_POS_OK: if (diag.notok) dialog_switch_pos (&diag, DIALOG_POS_NOTOK); else dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; case DIALOG_POS_NOTOK: dialog_switch_pos (&diag, DIALOG_POS_CANCEL); break; case DIALOG_POS_CANCEL: if (confirm_mode) dialog_switch_pos (&diag, DIALOG_POS_OK); else dialog_switch_pos (&diag, DIALOG_POS_PIN); break; default: break; } break; case '\005': done = -2; break; case '\r': switch (diag.pos) { case DIALOG_POS_PIN: case DIALOG_POS_REPEAT_PIN: case DIALOG_POS_OK: if (!test_repeat (&diag)) break; done = 1; break; case DIALOG_POS_NOTOK: done = -1; break; case DIALOG_POS_CANCEL: done = -2; break; case DIALOG_POS_NONE: break; } break; default: if (diag.pos == DIALOG_POS_PIN || diag.pos == DIALOG_POS_REPEAT_PIN) { dialog_input (&diag, alt, c); if (diag.pinentry->repeat_passphrase) diag.pinentry->repeat_okay = test_repeat (&diag); } } #ifndef HAVE_DOSISH_SYSTEM no_input = 0; #endif if (c != -1) alt = 0; } while (!done); if (!confirm_mode) { /* NUL terminate the passphrase. dialog_run makes sure there is enough space for the terminating NUL byte. */ diag.pinentry->pin[diag.pin_len] = 0; } set_cursor_state (1); endwin (); if (screen) delscreen (screen); #ifdef HAVE_NCURSESW if (old_ctype) { setlocale (LC_CTYPE, old_ctype); free (old_ctype); } #endif if (ttyfi) fclose (ttyfi); if (ttyfo) fclose (ttyfo); /* XXX Factor out into dialog_release or something. */ free (diag.ok); if (diag.cancel) free (diag.cancel); if (diag.notok) free (diag.notok); if (!confirm_mode) { pinentry->locale_err = 1; pin_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, pinentry->pin, 1); if (pin_utf8) { pinentry_setbufferlen (pinentry, strlen (pin_utf8) + 1); if (pinentry->pin) strcpy (pinentry->pin, pin_utf8); secmem_free (pin_utf8); pinentry->locale_err = 0; } } if (diag.repeat_pin) secmem_free (diag.repeat_pin); if (diag.error) free (diag.error); if (done == -2) pinentry->canceled = 1; /* In confirm mode return cancel instead of error. */ if (confirm_mode) return done < 0 ? 0 : 1; return done < 0 ? -1 : diag.pin_len; } /* If a touch has been registered, touch that file. */ static void do_touch_file (pinentry_t pinentry) { #ifdef HAVE_UTIME_H struct stat st; time_t tim; if (!pinentry->touch_file || !*pinentry->touch_file) return; if (stat (pinentry->touch_file, &st)) return; /* Oops. */ /* Make sure that we actually update the mtime. */ while ( (tim = time (NULL)) == st.st_mtime ) sleep (1); /* Update but ignore errors as we can't do anything in that case. Printing error messages may even clubber the display further. */ utime (pinentry->touch_file, NULL); #endif /*HAVE_UTIME_H*/ } #ifndef HAVE_DOSISH_SYSTEM static void catchsig (int sig) { if (sig == SIGALRM) timed_out = 1; } #endif int curses_cmd_handler (pinentry_t pinentry) { int rc; #ifndef HAVE_DOSISH_SYSTEM timed_out = 0; if (pinentry->timeout) { struct sigaction sa; memset (&sa, 0, sizeof(sa)); sa.sa_handler = catchsig; sigaction (SIGALRM, &sa, NULL); alarm (pinentry->timeout); } #endif rc = dialog_run (pinentry, pinentry->ttyname, pinentry->ttytype_l); do_touch_file (pinentry); return rc; } diff --git a/pinentry/pinentry-emacs.c b/pinentry/pinentry-emacs.c index 9685b67..7cac4a5 100644 --- a/pinentry/pinentry-emacs.c +++ b/pinentry/pinentry-emacs.c @@ -1,721 +1,721 @@ /* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version * Copyright (C) 2015 Daiki Ueno * * This file is part of PINENTRY. * * PINENTRY is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * PINENTRY is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifdef HAVE_STDINT_H #include <stdint.h> #endif #ifdef HAVE_INTTYPES_H #include <inttypes.h> #endif #include <assert.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #ifdef HAVE_UTIME_H #include <utime.h> #endif /*HAVE_UTIME_H*/ #include <assuan.h> #include "pinentry-emacs.h" -#include "memory.h" +#include "../secmem/secmem.h" #include "secmem-util.h" /* The communication mechanism is similar to emacsclient, but there are a few differences: - To avoid unnecessary character escaping and encoding conversion, we use a subset of the Pinentry Assuan protocol, instead of the emacsclient protocol. - We only use a Unix domain socket, while emacsclient has an ability to use a TCP socket. The socket file is located at ${TMPDIR-/tmp}/emacs$(id -u)/pinentry (i.e., under the same directory as the socket file used by emacsclient, so the same permission and file owner settings apply). - The server implementation can be found in pinentry.el, which is available in Emacs 25+ or from ELPA. */ #define LINELENGTH ASSUAN_LINELENGTH #define SEND_BUFFER_SIZE 4096 #define INITIAL_TIMEOUT 60 static int initial_timeout = INITIAL_TIMEOUT; #undef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #undef MAX #define MAX(x, y) ((x) < (y) ? (y) : (x)) #ifndef SUN_LEN # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) #endif /* FIXME: We could use the I/O functions in Assuan directly, once Pinentry links to libassuan. */ static int emacs_socket = -1; static char send_buffer[SEND_BUFFER_SIZE + 1]; static int send_buffer_length; /* Fill pointer for the send buffer. */ static pinentry_cmd_handler_t fallback_cmd_handler; #ifndef HAVE_DOSISH_SYSTEM static int timed_out; #endif static int set_socket (const char *socket_name) { struct sockaddr_un unaddr; struct stat statbuf; const char *tmpdir; char *tmpdir_storage = NULL; char *socket_name_storage = NULL; uid_t uid; unaddr.sun_family = AF_UNIX; /* We assume 32-bit UIDs, which can be represented with 10 decimal digits. */ uid = getuid (); if (uid != (uint32_t) uid) { fprintf (stderr, "UID is too large\n"); return 0; } tmpdir = getenv ("TMPDIR"); if (!tmpdir) { #ifdef _CS_DARWIN_USER_TEMP_DIR size_t n = confstr (_CS_DARWIN_USER_TEMP_DIR, NULL, (size_t) 0); if (n > 0) { tmpdir = tmpdir_storage = malloc (n); if (!tmpdir) { fprintf (stderr, "out of core\n"); return 0; } confstr (_CS_DARWIN_USER_TEMP_DIR, tmpdir_storage, n); } else #endif tmpdir = "/tmp"; } socket_name_storage = malloc (strlen (tmpdir) + strlen ("/emacs") + 10 + strlen ("/") + strlen (socket_name) + 1); if (!socket_name_storage) { fprintf (stderr, "out of core\n"); free (tmpdir_storage); return 0; } sprintf (socket_name_storage, "%s/emacs%u/%s", tmpdir, (uint32_t) uid, socket_name); free (tmpdir_storage); if (strlen (socket_name_storage) >= sizeof (unaddr.sun_path)) { fprintf (stderr, "socket name is too long\n"); free (socket_name_storage); return 0; } strcpy (unaddr.sun_path, socket_name_storage); free (socket_name_storage); /* See if the socket exists, and if it's owned by us. */ if (stat (unaddr.sun_path, &statbuf) == -1) { perror ("stat"); return 0; } if (statbuf.st_uid != geteuid ()) { fprintf (stderr, "socket is not owned by the same user\n"); return 0; } emacs_socket = socket (AF_UNIX, SOCK_STREAM, 0); if (emacs_socket < 0) { perror ("socket"); return 0; } if (connect (emacs_socket, (struct sockaddr *) &unaddr, SUN_LEN (&unaddr)) < 0) { perror ("connect"); close (emacs_socket); emacs_socket = -1; return 0; } return 1; } /* Percent-escape control characters in DATA. Return a newly allocated string. */ static char * escape (const char *data) { char *buffer, *out_p; size_t length, buffer_length; size_t offset; size_t count = 0; length = strlen (data); for (offset = 0; offset < length; offset++) { switch (data[offset]) { case '%': case '\n': case '\r': count++; break; default: break; } } buffer_length = length + count * 2; buffer = malloc (buffer_length + 1); if (!buffer) return NULL; out_p = buffer; for (offset = 0; offset < length; offset++) { int c = data[offset]; switch (c) { case '%': case '\n': case '\r': sprintf (out_p, "%%%02X", c); out_p += 3; break; default: *out_p++ = c; break; } } *out_p = '\0'; return buffer; } /* The inverse of escape. Unlike escape, it removes quoting in string DATA by modifying the string in place, to avoid copying of secret data sent from Emacs. */ static char * unescape (char *data) { char *p = data, *q = data; while (*p) { if (*p == '%' && p[1] && p[2]) { p++; *q++ = xtoi_2 (p); p += 2; } else *q++ = *p++; } *q = 0; return data; } /* Let's send the data to Emacs when either - the data ends in "\n", or - the buffer is full (but this shouldn't happen) Otherwise, we just accumulate it. */ static int send_to_emacs (int s, const char *buffer) { size_t length; length = strlen (buffer); while (*buffer) { size_t part = MIN (length, SEND_BUFFER_SIZE - send_buffer_length); memcpy (&send_buffer[send_buffer_length], buffer, part); buffer += part; send_buffer_length += part; if (send_buffer_length == SEND_BUFFER_SIZE || (send_buffer_length > 0 && send_buffer[send_buffer_length-1] == '\n')) { int sent = send (s, send_buffer, send_buffer_length, 0); if (sent < 0) { fprintf (stderr, "failed to send %d bytes to socket: %s\n", send_buffer_length, strerror (errno)); send_buffer_length = 0; return 0; } if (sent != send_buffer_length) memmove (send_buffer, &send_buffer[sent], send_buffer_length - sent); send_buffer_length -= sent; } length -= part; } return 1; } /* Read a server response. If the response contains data, it will be stored in BUFFER with a terminating NUL byte. BUFFER must be at least as large as CAPACITY. */ static gpg_error_t read_from_emacs (int s, int timeout, char *buffer, size_t capacity) { struct timeval tv; fd_set rfds; int retval; /* Offset in BUFFER. */ size_t offset = 0; int got_response = 0; char read_buffer[LINELENGTH + 1]; /* Offset in READ_BUFFER. */ size_t read_offset = 0; gpg_error_t result = 0; tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO (&rfds); FD_SET (s, &rfds); retval = select (s + 1, &rfds, NULL, NULL, &tv); if (retval == -1) { perror ("select"); return gpg_error (GPG_ERR_ASS_GENERAL); } else if (retval == 0) { timed_out = 1; return gpg_error (GPG_ERR_TIMEOUT); } /* Loop until we get either OK or ERR. */ while (!got_response) { int rl = 0; char *p, *end_p; do { errno = 0; rl = recv (s, read_buffer + read_offset, LINELENGTH - read_offset, 0); } /* If we receive a signal (e.g. SIGWINCH, which we pass through to Emacs), on some OSes we get EINTR and must retry. */ while (rl < 0 && errno == EINTR); if (rl < 0) { perror ("recv"); return gpg_error (GPG_ERR_ASS_GENERAL);; } if (rl == 0) break; read_offset += rl; read_buffer[read_offset] = '\0'; end_p = strchr (read_buffer, '\n'); /* If the buffer is filled without NL, throw away the content and start over the buffering. FIXME: We could return ASSUAN_Line_Too_Long or ASSUAN_Line_Not_Terminated here. */ if (!end_p && read_offset == sizeof (read_buffer) - 1) { read_offset = 0; continue; } /* Loop over all NL-terminated messages. */ for (p = read_buffer; end_p; p = end_p + 1, end_p = strchr (p, '\n')) { *end_p = '\0'; if (!strncmp ("D ", p, 2)) { char *data; size_t data_length; size_t needed_capacity; data = p + 2; data_length = end_p - data; if (data_length > 0) { needed_capacity = offset + data_length + 1; /* Check overflow. This is unrealistic but can happen since OFFSET is cumulative. */ if (needed_capacity < offset) return gpg_error (GPG_ERR_ASS_GENERAL);; if (needed_capacity > capacity) return gpg_error (GPG_ERR_ASS_GENERAL);; memcpy (&buffer[offset], data, data_length); offset += data_length; buffer[offset] = 0; } } else if (!strcmp ("OK", p) || !strncmp ("OK ", p, 3)) { got_response = 1; break; } else if (!strncmp ("ERR ", p, 4)) { unsigned long code = strtoul (p + 4, NULL, 10); if (code == ULONG_MAX && errno == ERANGE) return gpg_error (GPG_ERR_ASS_GENERAL); else result = code; got_response = 1; break; } else if (*p == '#') ; else fprintf (stderr, "invalid response: %s\n", p); } if (!got_response) { size_t length = &read_buffer[read_offset] - p; memmove (read_buffer, p, length); read_offset = length; } } return result; } int set_label (pinentry_t pe, const char *name, const char *value) { char buffer[16], *escaped; gpg_error_t error; int retval; if (!send_to_emacs (emacs_socket, name) || !send_to_emacs (emacs_socket, " ")) return 0; escaped = escape (value); if (!escaped) return 0; retval = send_to_emacs (emacs_socket, escaped) && send_to_emacs (emacs_socket, "\n"); free (escaped); if (!retval) return 0; error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer)); return error == 0; } static void set_labels (pinentry_t pe) { char *p; p = pinentry_get_title (pe); if (p) { set_label (pe, "SETTITLE", p); free (p); } if (pe->description) set_label (pe, "SETDESC", pe->description); if (pe->error) set_label (pe, "SETERROR", pe->error); if (pe->prompt) set_label (pe, "SETPROMPT", pe->prompt); else if (pe->default_prompt) set_label (pe, "SETPROMPT", pe->default_prompt); if (pe->repeat_passphrase) set_label (pe, "SETREPEAT", pe->repeat_passphrase); if (pe->repeat_error_string) set_label (pe, "SETREPEATERROR", pe->repeat_error_string); /* XXX: pe->quality_bar and pe->quality_bar_tt are not supported. */ /* Buttons. */ if (pe->ok) set_label (pe, "SETOK", pe->ok); else if (pe->default_ok) set_label (pe, "SETOK", pe->default_ok); if (pe->cancel) set_label (pe, "SETCANCEL", pe->cancel); else if (pe->default_cancel) set_label (pe, "SETCANCEL", pe->default_cancel); if (pe->notok) set_label (pe, "SETNOTOK", pe->notok); } static int do_password (pinentry_t pe) { char *buffer, *password; size_t length = LINELENGTH; gpg_error_t error; set_labels (pe); if (!send_to_emacs (emacs_socket, "GETPIN\n")) return -1; buffer = secmem_malloc (length); if (!buffer) { pe->specific_err = gpg_error (GPG_ERR_ENOMEM); return -1; } error = read_from_emacs (emacs_socket, pe->timeout, buffer, length); if (error != 0) { if (gpg_err_code (error) == GPG_ERR_CANCELED) pe->canceled = 1; secmem_free (buffer); pe->specific_err = error; return -1; } password = unescape (buffer); pinentry_setbufferlen (pe, strlen (password) + 1); if (pe->pin) strcpy (pe->pin, password); secmem_free (buffer); if (pe->repeat_passphrase) pe->repeat_okay = 1; /* XXX: we don't support external password cache (yet). */ return 1; } static int do_confirm (pinentry_t pe) { char buffer[16]; gpg_error_t error; set_labels (pe); if (!send_to_emacs (emacs_socket, "CONFIRM\n")) return 0; error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer)); if (error != 0) { if (gpg_err_code (error) == GPG_ERR_CANCELED) pe->canceled = 1; pe->specific_err = error; return 0; } return 1; } /* If a touch has been registered, touch that file. */ static void do_touch_file (pinentry_t pinentry) { #ifdef HAVE_UTIME_H struct stat st; time_t tim; if (!pinentry->touch_file || !*pinentry->touch_file) return; if (stat (pinentry->touch_file, &st)) return; /* Oops. */ /* Make sure that we actually update the mtime. */ while ( (tim = time (NULL)) == st.st_mtime ) sleep (1); /* Update but ignore errors as we can't do anything in that case. Printing error messages may even clubber the display further. */ utime (pinentry->touch_file, NULL); #endif /*HAVE_UTIME_H*/ } #ifndef HAVE_DOSISH_SYSTEM static void catchsig (int sig) { if (sig == SIGALRM) timed_out = 1; } #endif int emacs_cmd_handler (pinentry_t pe) { int rc; #ifndef HAVE_DOSISH_SYSTEM timed_out = 0; if (pe->timeout) { struct sigaction sa; memset (&sa, 0, sizeof(sa)); sa.sa_handler = catchsig; sigaction (SIGALRM, &sa, NULL); alarm (pe->timeout); } #endif if (pe->pin) rc = do_password (pe); else rc = do_confirm (pe); do_touch_file (pe); return rc; } static int initial_emacs_cmd_handler (pinentry_t pe) { /* Let the select() call in pinentry_emacs_init honor the timeout value set through an Assuan option. */ initial_timeout = pe->timeout; if (emacs_socket < 0) pinentry_emacs_init (); /* If we have successfully connected to Emacs, swap pinentry_cmd_handler to emacs_cmd_handler, so further interactions will be forwarded to Emacs. Otherwise, set it back to the original command handler saved as fallback_cmd_handler. */ if (emacs_socket < 0) pinentry_cmd_handler = fallback_cmd_handler; else { pinentry_cmd_handler = emacs_cmd_handler; pinentry_set_flavor_flag ("emacs"); } return (* pinentry_cmd_handler) (pe); } void pinentry_enable_emacs_cmd_handler (void) { const char *envvar; /* Check if pinentry_cmd_handler is already prepared for Emacs. */ if (pinentry_cmd_handler == initial_emacs_cmd_handler || pinentry_cmd_handler == emacs_cmd_handler) return; /* Check if INSIDE_EMACS envvar is set. */ envvar = getenv ("INSIDE_EMACS"); if (!envvar || !*envvar) return; /* Save the original command handler as fallback_cmd_handler, and swap pinentry_cmd_handler to initial_emacs_cmd_handler. */ fallback_cmd_handler = pinentry_cmd_handler; pinentry_cmd_handler = initial_emacs_cmd_handler; } /* Returns true if the Emacs pinentry is enabled. The value is 1 * before the first connection with Emacs has been done and 2 if the * connection to Emacs has been establish. Returns false if the Emacs * pinentry is not enabled. */ int pinentry_emacs_status (void) { if (pinentry_cmd_handler == initial_emacs_cmd_handler) return 1; else if (pinentry_cmd_handler == emacs_cmd_handler) return 2; else return 0; } int pinentry_emacs_init (void) { char buffer[256]; gpg_error_t error; assert (emacs_socket < 0); /* Check if we can connect to the Emacs server socket. */ if (!set_socket ("pinentry")) return 0; /* Check if the server responds. */ error = read_from_emacs (emacs_socket, initial_timeout, buffer, sizeof (buffer)); if (error != 0) { close (emacs_socket); emacs_socket = -1; return 0; } return 1; } diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c index 870792b..866f427 100644 --- a/pinentry/pinentry.c +++ b/pinentry/pinentry.c @@ -1,2104 +1,2103 @@ /* pinentry.c - The PIN entry support library * Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 g10 Code GmbH * * This file is part of PINENTRY. * * PINENTRY is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * PINENTRY is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifndef HAVE_W32CE_SYSTEM # include <errno.h> #endif #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <assert.h> #ifndef HAVE_W32_SYSTEM # include <sys/utsname.h> #endif #ifndef HAVE_W32CE_SYSTEM # include <locale.h> #endif #include <limits.h> #ifdef HAVE_W32CE_SYSTEM # include <windows.h> #endif #include <assuan.h> -#include "memory.h" #include "secmem-util.h" #include "argparse.h" #include "pinentry.h" #include "password-cache.h" #ifdef INSIDE_EMACS # include "pinentry-emacs.h" #endif #ifdef FALLBACK_CURSES # include "pinentry-curses.h" #endif #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () #endif /* Keep the name of our program here. */ static char this_pgmname[50]; struct pinentry pinentry; static const char *flavor_flag; /* Because gtk_init removes the --display arg from the command lines * and our command line parser is called after gtk_init (so that it * does not see gtk specific options) we don't have a way to get hold * of the --display option. Our solution is to remember --display in * the call to pinentry_have_display and set it then in our * parser. */ static char *remember_display; static void pinentry_reset (int use_defaults) { /* GPG Agent sets these options once when it starts the pinentry. Don't reset them. */ int grab = pinentry.grab; char *ttyname = pinentry.ttyname; char *ttytype = pinentry.ttytype_l; char *ttyalert = pinentry.ttyalert; char *lc_ctype = pinentry.lc_ctype; char *lc_messages = pinentry.lc_messages; int allow_external_password_cache = pinentry.allow_external_password_cache; char *default_ok = pinentry.default_ok; char *default_cancel = pinentry.default_cancel; char *default_prompt = pinentry.default_prompt; char *default_pwmngr = pinentry.default_pwmngr; char *default_cf_visi = pinentry.default_cf_visi; char *default_tt_visi = pinentry.default_tt_visi; char *default_tt_hide = pinentry.default_tt_hide; char *default_capshint = pinentry.default_capshint; char *touch_file = pinentry.touch_file; unsigned long owner_pid = pinentry.owner_pid; int owner_uid = pinentry.owner_uid; char *owner_host = pinentry.owner_host; int constraints_enforce = pinentry.constraints_enforce; char *constraints_hint_short = pinentry.constraints_hint_short; char *constraints_hint_long = pinentry.constraints_hint_long; char *constraints_error_title = pinentry.constraints_error_title; /* These options are set from the command line. Don't reset them. */ int debug = pinentry.debug; char *display = pinentry.display; int parent_wid = pinentry.parent_wid; pinentry_color_t color_fg = pinentry.color_fg; int color_fg_bright = pinentry.color_fg_bright; pinentry_color_t color_bg = pinentry.color_bg; pinentry_color_t color_so = pinentry.color_so; int color_so_bright = pinentry.color_so_bright; pinentry_color_t color_ok = pinentry.color_ok; int color_ok_bright = pinentry.color_ok_bright; pinentry_color_t color_qualitybar = pinentry.color_qualitybar; int color_qualitybar_bright = pinentry.color_qualitybar_bright; int timeout = pinentry.timeout; char *invisible_char = pinentry.invisible_char; /* Free any allocated memory. */ if (use_defaults) { free (pinentry.ttyname); free (pinentry.ttytype_l); free (pinentry.ttyalert); free (pinentry.lc_ctype); free (pinentry.lc_messages); free (pinentry.default_ok); free (pinentry.default_cancel); free (pinentry.default_prompt); free (pinentry.default_pwmngr); free (pinentry.default_cf_visi); free (pinentry.default_tt_visi); free (pinentry.default_tt_hide); free (pinentry.default_capshint); free (pinentry.touch_file); free (pinentry.owner_host); free (pinentry.display); free (pinentry.constraints_hint_short); free (pinentry.constraints_hint_long); free (pinentry.constraints_error_title); } free (pinentry.title); free (pinentry.description); free (pinentry.error); free (pinentry.prompt); free (pinentry.ok); free (pinentry.notok); free (pinentry.cancel); secmem_free (pinentry.pin); free (pinentry.repeat_passphrase); free (pinentry.repeat_error_string); free (pinentry.quality_bar); free (pinentry.quality_bar_tt); free (pinentry.formatted_passphrase_hint); free (pinentry.keyinfo); free (pinentry.specific_err_info); /* Reset the pinentry structure. */ memset (&pinentry, 0, sizeof (pinentry)); /* Restore options without a default we want to preserve. */ pinentry.invisible_char = invisible_char; /* Restore other options or set defaults. */ if (use_defaults) { /* Pinentry timeout in seconds. */ pinentry.timeout = 60; /* Global grab. */ pinentry.grab = 1; pinentry.color_fg = PINENTRY_COLOR_DEFAULT; pinentry.color_fg_bright = 0; pinentry.color_bg = PINENTRY_COLOR_DEFAULT; pinentry.color_so = PINENTRY_COLOR_DEFAULT; pinentry.color_so_bright = 0; pinentry.color_ok = PINENTRY_COLOR_DEFAULT; pinentry.color_ok_bright = 0; pinentry.color_qualitybar = PINENTRY_COLOR_DEFAULT; pinentry.color_qualitybar_bright = 0; pinentry.owner_uid = -1; } else /* Restore the options. */ { pinentry.grab = grab; pinentry.ttyname = ttyname; pinentry.ttytype_l = ttytype; pinentry.ttyalert = ttyalert; pinentry.lc_ctype = lc_ctype; pinentry.lc_messages = lc_messages; pinentry.allow_external_password_cache = allow_external_password_cache; pinentry.default_ok = default_ok; pinentry.default_cancel = default_cancel; pinentry.default_prompt = default_prompt; pinentry.default_pwmngr = default_pwmngr; pinentry.default_cf_visi = default_cf_visi; pinentry.default_tt_visi = default_tt_visi; pinentry.default_tt_hide = default_tt_hide; pinentry.default_capshint = default_capshint; pinentry.touch_file = touch_file; pinentry.owner_pid = owner_pid; pinentry.owner_uid = owner_uid; pinentry.owner_host = owner_host; pinentry.constraints_enforce = constraints_enforce; pinentry.constraints_hint_short = constraints_hint_short; pinentry.constraints_hint_long = constraints_hint_long; pinentry.constraints_error_title = constraints_error_title; pinentry.debug = debug; pinentry.display = display; pinentry.parent_wid = parent_wid; pinentry.color_fg = color_fg; pinentry.color_fg_bright = color_fg_bright; pinentry.color_bg = color_bg; pinentry.color_so = color_so; pinentry.color_so_bright = color_so_bright; pinentry.color_ok = color_ok; pinentry.color_ok_bright = color_ok_bright; pinentry.color_qualitybar = color_qualitybar; pinentry.color_qualitybar_bright = color_qualitybar_bright; pinentry.timeout = timeout; } } static gpg_error_t pinentry_assuan_reset_handler (assuan_context_t ctx, char *line) { (void)ctx; (void)line; pinentry_reset (0); return 0; } /* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a pointer to the end of the new buffer. Note that BUFFER must be large enough to keep the entire text; allocataing it 3 times of TEXTLEN is sufficient. */ static char * copy_and_escape (char *buffer, const void *text, size_t textlen) { int i; const unsigned char *s = (unsigned char *)text; char *p = buffer; for (i=0; i < textlen; i++) { if (s[i] < ' ' || s[i] == '+') { snprintf (p, 4, "%%%02X", s[i]); p += 3; } else if (s[i] == ' ') *p++ = '+'; else *p++ = s[i]; } return p; } /* Perform percent unescaping in STRING and return the new valid length of the string. A terminating Nul character is inserted at the end of the unescaped string. */ static size_t do_unescape_inplace (char *s) { unsigned char *p, *p0; p = p0 = s; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *p++ = xtoi_2 (s); s += 2; } else *p++ = *s++; } *p = 0; return (p - p0); } /* Return a malloced copy of the commandline for PID. If this is not * possible NULL is returned. */ #ifndef HAVE_W32_SYSTEM static char * get_cmdline (unsigned long pid) { char buffer[200]; FILE *fp; size_t i, n; snprintf (buffer, sizeof buffer, "/proc/%lu/cmdline", pid); fp = fopen (buffer, "rb"); if (!fp) return NULL; n = fread (buffer, 1, sizeof buffer - 1, fp); if (n < sizeof buffer -1 && ferror (fp)) { /* Some error occurred. */ fclose (fp); return NULL; } fclose (fp); if (n == 0) return NULL; /* Arguments are delimited by Nuls. We should do proper quoting but * that can be a bit complicated, thus we simply replace the Nuls by * spaces. */ for (i=0; i < n; i++) if (!buffer[i] && i < n-1) buffer[i] = ' '; buffer[i] = 0; /* Make sure the last byte is the string terminator. */ return strdup (buffer); } #endif /*!HAVE_W32_SYSTEM*/ /* Atomically ask the kernel for information about process PID. * Return a malloc'ed copy of the process name as long as the process * uid matches UID. If it cannot determine that the process has uid * UID, it returns NULL. * * This is not as informative as get_cmdline, but it verifies that the * process does belong to the user in question. */ #ifndef HAVE_W32_SYSTEM static char * get_pid_name_for_uid (unsigned long pid, int uid) { char buffer[400]; FILE *fp; size_t end, n; char *uidstr; snprintf (buffer, sizeof buffer, "/proc/%lu/status", pid); fp = fopen (buffer, "rb"); if (!fp) return NULL; n = fread (buffer, 1, sizeof buffer - 1, fp); if (n < sizeof buffer -1 && ferror (fp)) { /* Some error occurred. */ fclose (fp); return NULL; } fclose (fp); if (n == 0) return NULL; buffer[n] = 0; /* Fixme: Is it specified that "Name" is always the first line? For * robustness I would prefer to have a real parser here. -wk */ if (strncmp (buffer, "Name:\t", 6)) return NULL; end = strcspn (buffer + 6, "\n") + 6; buffer[end] = 0; /* check that uid matches what we expect */ uidstr = strstr (buffer + end + 1, "\nUid:\t"); if (!uidstr) return NULL; if (atoi (uidstr + 6) != uid) return NULL; return strdup (buffer + 6); } #endif /*!HAVE_W32_SYSTEM*/ const char * pinentry_get_pgmname (void) { return this_pgmname; } /* Return a malloced string with the title. The caller mus free the * string. If no title is available or the title string has an error * NULL is returned. */ char * pinentry_get_title (pinentry_t pe) { char *title; if (pe->title) title = strdup (pe->title); #ifndef HAVE_W32_SYSTEM else if (pe->owner_pid) { char buf[200]; struct utsname utsbuf; char *pidname = NULL; char *cmdline = NULL; if (pe->owner_host && !uname (&utsbuf) && !strcmp (utsbuf.nodename, pe->owner_host)) { pidname = get_pid_name_for_uid (pe->owner_pid, pe->owner_uid); if (pidname) cmdline = get_cmdline (pe->owner_pid); } if (pe->owner_host && (cmdline || pidname)) snprintf (buf, sizeof buf, "[%lu]@%s (%s)", pe->owner_pid, pe->owner_host, cmdline ? cmdline : pidname); else if (pe->owner_host) snprintf (buf, sizeof buf, "[%lu]@%s", pe->owner_pid, pe->owner_host); else snprintf (buf, sizeof buf, "[%lu] <unknown host>", pe->owner_pid); free (pidname); free (cmdline); title = strdup (buf); } #endif /*!HAVE_W32_SYSTEM*/ else title = strdup (this_pgmname); return title; } /* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH because not all backends might be able to return a proper C-string.). Returns: A value between -100 and 100 to give an estimate of the passphrase's quality. Negative values are use if the caller won't even accept that passphrase. Note that we expect just one data line which should not be escaped in any represent a numeric signed decimal value. Extra data is currently ignored but should not be send at all. */ int pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length) { assuan_context_t ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE QUALITY "; char *command; char *line; size_t linelen; int gotvalue = 0; int value = 0; int rc; if (!ctx) return 0; /* Can't run the callback. */ if (length > 300) length = 300; /* Limit so that it definitely fits into an Assuan line. */ command = secmem_malloc (strlen (prefix) + 3*length + 1); if (!command) return 0; strcpy (command, prefix); copy_and_escape (command + strlen(command), passphrase, length); rc = assuan_write_line (ctx, command); secmem_free (command); if (rc) { fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); return 0; } for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) { fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); return 0; } } while (*line == '#' || !linelen); if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (!line[3] || line[3] == ' ')) break; /* END command received*/ if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && (!line[3] || line[3] == ' ')) break; /* CAN command received*/ if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (!line[3] || line[3] == ' ')) break; /* ERR command received*/ if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; gotvalue = 1; value = atoi (line+2); } if (value < -100) value = -100; else if (value > 100) value = 100; return value; } /* Run a checkpin inquiry */ char * pinentry_inq_checkpin (pinentry_t pin, const char *passphrase, size_t length) { assuan_context_t ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE CHECKPIN "; char *command; char *line; size_t linelen; int gotvalue = 0; char *value = NULL; int rc; if (!ctx) return 0; /* Can't run the callback. */ if (length > 300) length = 300; /* Limit so that it definitely fits into an Assuan line. */ command = secmem_malloc (strlen (prefix) + 3*length + 1); if (!command) return 0; strcpy (command, prefix); copy_and_escape (command + strlen(command), passphrase, length); rc = assuan_write_line (ctx, command); secmem_free (command); if (rc) { fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); return 0; } for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) { fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); return 0; } } while (*line == '#' || !linelen); if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (!line[3] || line[3] == ' ')) break; /* END command received*/ if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && (!line[3] || line[3] == ' ')) break; /* CAN command received*/ if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (!line[3] || line[3] == ' ')) break; /* ERR command received*/ if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; gotvalue = 1; value = strdup (line + 2); } return value; } /* Run a genpin inquiry */ char * pinentry_inq_genpin (pinentry_t pin) { assuan_context_t ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE GENPIN"; char *line; size_t linelen; int gotvalue = 0; char *value = NULL; int rc; if (!ctx) return 0; /* Can't run the callback. */ rc = assuan_write_line (ctx, prefix); if (rc) { fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); return 0; } for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) { fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); free (value); return 0; } } while (*line == '#' || !linelen); if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (!line[3] || line[3] == ' ')) break; /* END command received*/ if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && (!line[3] || line[3] == ' ')) break; /* CAN command received*/ if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (!line[3] || line[3] == ' ')) break; /* ERR command received*/ if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; gotvalue = 1; value = strdup (line + 2); } return value; } /* Try to make room for at least LEN bytes in the pinentry. Returns new buffer on success and 0 on failure or when the old buffer is sufficient. */ char * pinentry_setbufferlen (pinentry_t pin, int len) { char *newp; if (pin->pin_len) assert (pin->pin); else assert (!pin->pin); if (len < 2048) len = 2048; if (len <= pin->pin_len) return pin->pin; newp = secmem_realloc (pin->pin, len); if (newp) { pin->pin = newp; pin->pin_len = len; } else { secmem_free (pin->pin); pin->pin = 0; pin->pin_len = 0; } return newp; } static void pinentry_setbuffer_clear (pinentry_t pin) { if (! pin->pin) { assert (pin->pin_len == 0); return; } assert (pin->pin_len > 0); secmem_free (pin->pin); pin->pin = NULL; pin->pin_len = 0; } static void pinentry_setbuffer_init (pinentry_t pin) { pinentry_setbuffer_clear (pin); pinentry_setbufferlen (pin, 0); } /* passphrase better be alloced with secmem_alloc. */ void pinentry_setbuffer_use (pinentry_t pin, char *passphrase, int len) { if (! passphrase) { assert (len == 0); pinentry_setbuffer_clear (pin); return; } if (passphrase && len == 0) len = strlen (passphrase) + 1; if (pin->pin) secmem_free (pin->pin); pin->pin = passphrase; pin->pin_len = len; } static struct assuan_malloc_hooks assuan_malloc_hooks = { secmem_malloc, secmem_realloc, secmem_free }; /* Initialize the secure memory subsystem, drop privileges and return. Must be called early. */ void pinentry_init (const char *pgmname) { /* Store away our name. */ if (strlen (pgmname) > sizeof this_pgmname - 2) abort (); strcpy (this_pgmname, pgmname); gpgrt_check_version (NULL); /* Initialize secure memory. 1 is too small, so the default size will be used. */ secmem_init (1); secmem_set_flags (SECMEM_WARN); drop_privs (); if (atexit (secmem_term)) { /* FIXME: Could not register at-exit function, bail out. */ } assuan_set_malloc_hooks (&assuan_malloc_hooks); } /* Simple test to check whether DISPLAY is set or the option --display was given. Used to decide whether the GUI or curses should be initialized. */ int pinentry_have_display (int argc, char **argv) { int found = 0; for (; argc; argc--, argv++) { if (!strcmp (*argv, "--display")) { if (argv[1] && !remember_display) { remember_display = strdup (argv[1]); if (!remember_display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } } found = 1; break; } else if (!strncmp (*argv, "--display=", 10)) { if (!remember_display) { remember_display = strdup (*argv+10); if (!remember_display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } } found = 1; break; } } #ifndef HAVE_W32CE_SYSTEM { const char *s; s = getenv ("DISPLAY"); if (s && *s) found = 1; } #endif return found; } /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = this_pgmname; break; case 12: p = "pinentry"; break; case 13: p = PACKAGE_VERSION; break; case 14: p = "Copyright (C) 2016 g10 Code GmbH"; break; case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; case 1: case 40: { static char *str; if (!str) { size_t n = 50 + strlen (this_pgmname); str = malloc (n); if (str) { snprintf (str, n, "Usage: %s [options] (-h for help)", this_pgmname); } } p = str; } break; case 41: p = "Ask securely for a secret and print it to stdout."; break; case 42: p = "1"; /* Flag print 40 as part of 41. */ break; default: p = NULL; break; } return p; } char * parse_color (char *arg, pinentry_color_t *color_p, int *bright_p) { static struct { const char *name; pinentry_color_t color; } colors[] = { { "none", PINENTRY_COLOR_NONE }, { "default", PINENTRY_COLOR_DEFAULT }, { "black", PINENTRY_COLOR_BLACK }, { "red", PINENTRY_COLOR_RED }, { "green", PINENTRY_COLOR_GREEN }, { "yellow", PINENTRY_COLOR_YELLOW }, { "blue", PINENTRY_COLOR_BLUE }, { "magenta", PINENTRY_COLOR_MAGENTA }, { "cyan", PINENTRY_COLOR_CYAN }, { "white", PINENTRY_COLOR_WHITE } }; int i; char *new_arg; pinentry_color_t color = PINENTRY_COLOR_DEFAULT; if (!arg) return NULL; new_arg = strchr (arg, ','); if (new_arg) new_arg++; if (bright_p) { const char *bname[] = { "bright-", "bright", "bold-", "bold" }; *bright_p = 0; for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++) if (!strncasecmp (arg, bname[i], strlen (bname[i]))) { *bright_p = 1; arg += strlen (bname[i]); } } for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++) if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name))) color = colors[i].color; *color_p = color; return new_arg; } /* Parse the command line options. May exit the program if only help or version output is requested. */ void pinentry_parse_opts (int argc, char *argv[]) { static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n('d', "debug", "Turn on debugging output"), ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"), ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"), ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"), ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"), ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"), ARGPARSE_s_i('o', "timeout", "|SECS|Timeout waiting for input after this many seconds"), ARGPARSE_s_n('g', "no-global-grab", "Grab keyboard only while window is focused"), ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"), ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"), ARGPARSE_s_s('a', "ttyalert", "|STRING|Set the alert mode (none, beep or flash)"), ARGPARSE_end() }; ARGPARSE_ARGS pargs = { &argc, &argv, 0 }; set_strusage (my_strusage); pinentry_reset (1); while (arg_parse (&pargs, opts)) { switch (pargs.r_opt) { case 'd': pinentry.debug = 1; break; case 'g': pinentry.grab = 0; break; case 'D': /* Note, this is currently not used because the GUI engine has already been initialized when parsing these options. */ pinentry.display = strdup (pargs.r.ret_str); if (!pinentry.display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'T': pinentry.ttyname = strdup (pargs.r.ret_str); if (!pinentry.ttyname) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'N': pinentry.ttytype_l = strdup (pargs.r.ret_str); if (!pinentry.ttytype_l) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'C': pinentry.lc_ctype = strdup (pargs.r.ret_str); if (!pinentry.lc_ctype) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'M': pinentry.lc_messages = strdup (pargs.r.ret_str); if (!pinentry.lc_messages) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'W': pinentry.parent_wid = pargs.r.ret_ulong; break; case 'c': { char *tmpstr = pargs.r.ret_str; tmpstr = parse_color (tmpstr, &pinentry.color_fg, &pinentry.color_fg_bright); tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL); tmpstr = parse_color (tmpstr, &pinentry.color_so, &pinentry.color_so_bright); tmpstr = parse_color (tmpstr, &pinentry.color_ok, &pinentry.color_ok_bright); tmpstr = parse_color (tmpstr, &pinentry.color_qualitybar, &pinentry.color_qualitybar_bright); } break; case 'o': pinentry.timeout = pargs.r.ret_int; break; case 'a': pinentry.ttyalert = strdup (pargs.r.ret_str); if (!pinentry.ttyalert) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; default: pargs.err = ARGPARSE_PRINT_WARNING; break; } } if (!pinentry.display && remember_display) { pinentry.display = remember_display; remember_display = NULL; } } /* Set the optional flag used with getinfo. */ void pinentry_set_flavor_flag (const char *string) { flavor_flag = string; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { (void)ctx; if (!strcmp (key, "no-grab") && !*value) pinentry.grab = 0; else if (!strcmp (key, "grab") && !*value) pinentry.grab = 1; else if (!strcmp (key, "debug-wait")) { #ifndef HAVE_W32_SYSTEM fprintf (stderr, "%s: waiting for debugger - my pid is %u ...\n", this_pgmname, (unsigned int) getpid()); sleep (*value?atoi (value):5); fprintf (stderr, "%s: ... okay\n", this_pgmname); #endif } else if (!strcmp (key, "display")) { if (pinentry.display) free (pinentry.display); pinentry.display = strdup (value); if (!pinentry.display) return gpg_error_from_syserror (); } else if (!strcmp (key, "ttyname")) { if (pinentry.ttyname) free (pinentry.ttyname); pinentry.ttyname = strdup (value); if (!pinentry.ttyname) return gpg_error_from_syserror (); } else if (!strcmp (key, "ttytype")) { if (pinentry.ttytype_l) free (pinentry.ttytype_l); pinentry.ttytype_l = strdup (value); if (!pinentry.ttytype_l) return gpg_error_from_syserror (); } else if (!strcmp (key, "ttyalert")) { if (pinentry.ttyalert) free (pinentry.ttyalert); pinentry.ttyalert = strdup (value); if (!pinentry.ttyalert) return gpg_error_from_syserror (); } else if (!strcmp (key, "lc-ctype")) { if (pinentry.lc_ctype) free (pinentry.lc_ctype); pinentry.lc_ctype = strdup (value); if (!pinentry.lc_ctype) return gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { if (pinentry.lc_messages) free (pinentry.lc_messages); pinentry.lc_messages = strdup (value); if (!pinentry.lc_messages) return gpg_error_from_syserror (); } else if (!strcmp (key, "owner")) { long along; char *endp; free (pinentry.owner_host); pinentry.owner_host = NULL; pinentry.owner_uid = -1; pinentry.owner_pid = 0; errno = 0; along = strtol (value, &endp, 10); if (along && !errno) { pinentry.owner_pid = (unsigned long)along; if (*endp) { errno = 0; if (*endp == '/') { /* we have a uid */ endp++; along = strtol (endp, &endp, 10); if (along >= 0 && !errno) pinentry.owner_uid = (int)along; } if (endp) { while (*endp == ' ') endp++; if (*endp) { pinentry.owner_host = strdup (endp); for (endp=pinentry.owner_host; *endp && *endp != ' '; endp++) ; *endp = 0; } } } } } else if (!strcmp (key, "parent-wid")) { pinentry.parent_wid = atoi (value); /* FIXME: Use strtol and add some error handling. */ } else if (!strcmp (key, "touch-file")) { if (pinentry.touch_file) free (pinentry.touch_file); pinentry.touch_file = strdup (value); if (!pinentry.touch_file) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-ok")) { pinentry.default_ok = strdup (value); if (!pinentry.default_ok) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-cancel")) { pinentry.default_cancel = strdup (value); if (!pinentry.default_cancel) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-prompt")) { pinentry.default_prompt = strdup (value); if (!pinentry.default_prompt) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-pwmngr")) { pinentry.default_pwmngr = strdup (value); if (!pinentry.default_pwmngr) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-cf-visi")) { pinentry.default_cf_visi = strdup (value); if (!pinentry.default_cf_visi) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-tt-visi")) { pinentry.default_tt_visi = strdup (value); if (!pinentry.default_tt_visi) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-tt-hide")) { pinentry.default_tt_hide = strdup (value); if (!pinentry.default_tt_hide) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-capshint")) { pinentry.default_capshint = strdup (value); if (!pinentry.default_capshint) return gpg_error_from_syserror (); } else if (!strcmp (key, "allow-external-password-cache") && !*value) { pinentry.allow_external_password_cache = 1; pinentry.tried_password_cache = 0; } else if (!strcmp (key, "allow-emacs-prompt") && !*value) { #ifdef INSIDE_EMACS pinentry_enable_emacs_cmd_handler (); #endif } else if (!strcmp (key, "invisible-char")) { if (pinentry.invisible_char) free (pinentry.invisible_char); pinentry.invisible_char = strdup (value); if (!pinentry.invisible_char) return gpg_error_from_syserror (); } else if (!strcmp (key, "formatted-passphrase") && !*value) { pinentry.formatted_passphrase = 1; } else if (!strcmp (key, "formatted-passphrase-hint")) { if (pinentry.formatted_passphrase_hint) free (pinentry.formatted_passphrase_hint); pinentry.formatted_passphrase_hint = strdup (value); if (!pinentry.formatted_passphrase_hint) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.formatted_passphrase_hint); } else if (!strcmp (key, "constraints-enforce") && !*value) pinentry.constraints_enforce = 1; else if (!strcmp (key, "constraints-hint-short")) { if (pinentry.constraints_hint_short) free (pinentry.constraints_hint_short); pinentry.constraints_hint_short = strdup (value); if (!pinentry.constraints_hint_short) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_hint_short); } else if (!strcmp (key, "constraints-hint-long")) { if (pinentry.constraints_hint_long) free (pinentry.constraints_hint_long); pinentry.constraints_hint_long = strdup (value); if (!pinentry.constraints_hint_long) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_hint_long); } else if (!strcmp (key, "constraints-error-title")) { if (pinentry.constraints_error_title) free (pinentry.constraints_error_title); pinentry.constraints_error_title = strdup (value); if (!pinentry.constraints_error_title) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_error_title); } else return gpg_error (GPG_ERR_UNKNOWN_OPTION); return 0; } /* Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else *d++ = *s++; } *d = 0; } static void write_status_error (assuan_context_t ctx, pinentry_t pe) { char buf[500]; const char *pgm; pgm = strchr (this_pgmname, '-'); if (pgm && pgm[1]) pgm++; else pgm = this_pgmname; snprintf (buf, sizeof buf, "%s.%s %d %s", pgm, pe->specific_err_loc? pe->specific_err_loc : "?", pe->specific_err, pe->specific_err_info? pe->specific_err_info : ""); assuan_write_status (ctx, "ERROR", buf); } static gpg_error_t cmd_setdesc (assuan_context_t ctx, char *line) { char *newd; (void)ctx; newd = malloc (strlen (line) + 1); if (!newd) return gpg_error_from_syserror (); strcpy_escaped (newd, line); if (pinentry.description) free (pinentry.description); pinentry.description = newd; return 0; } static gpg_error_t cmd_setprompt (assuan_context_t ctx, char *line) { char *newp; (void)ctx; newp = malloc (strlen (line) + 1); if (!newp) return gpg_error_from_syserror (); strcpy_escaped (newp, line); if (pinentry.prompt) free (pinentry.prompt); pinentry.prompt = newp; return 0; } /* The data provided at LINE may be used by pinentry implementations to identify a key for caching strategies of its own. The empty string and --clear mean that the key does not have a stable identifier. */ static gpg_error_t cmd_setkeyinfo (assuan_context_t ctx, char *line) { (void)ctx; if (pinentry.keyinfo) free (pinentry.keyinfo); if (*line && strcmp(line, "--clear") != 0) pinentry.keyinfo = strdup (line); else pinentry.keyinfo = NULL; return 0; } static gpg_error_t cmd_setrepeat (assuan_context_t ctx, char *line) { char *p; (void)ctx; p = malloc (strlen (line) + 1); if (!p) return gpg_error_from_syserror (); strcpy_escaped (p, line); free (pinentry.repeat_passphrase); pinentry.repeat_passphrase = p; return 0; } static gpg_error_t cmd_setrepeatok (assuan_context_t ctx, char *line) { char *p; (void)ctx; p = malloc (strlen (line) + 1); if (!p) return gpg_error_from_syserror (); strcpy_escaped (p, line); free (pinentry.repeat_ok_string); pinentry.repeat_ok_string = p; return 0; } static gpg_error_t cmd_setrepeaterror (assuan_context_t ctx, char *line) { char *p; (void)ctx; p = malloc (strlen (line) + 1); if (!p) return gpg_error_from_syserror (); strcpy_escaped (p, line); free (pinentry.repeat_error_string); pinentry.repeat_error_string = p; return 0; } static gpg_error_t cmd_seterror (assuan_context_t ctx, char *line) { char *newe; (void)ctx; newe = malloc (strlen (line) + 1); if (!newe) return gpg_error_from_syserror (); strcpy_escaped (newe, line); if (pinentry.error) free (pinentry.error); pinentry.error = newe; return 0; } static gpg_error_t cmd_setok (assuan_context_t ctx, char *line) { char *newo; (void)ctx; newo = malloc (strlen (line) + 1); if (!newo) return gpg_error_from_syserror (); strcpy_escaped (newo, line); if (pinentry.ok) free (pinentry.ok); pinentry.ok = newo; return 0; } static gpg_error_t cmd_setnotok (assuan_context_t ctx, char *line) { char *newo; (void)ctx; newo = malloc (strlen (line) + 1); if (!newo) return gpg_error_from_syserror (); strcpy_escaped (newo, line); if (pinentry.notok) free (pinentry.notok); pinentry.notok = newo; return 0; } static gpg_error_t cmd_setcancel (assuan_context_t ctx, char *line) { char *newc; (void)ctx; newc = malloc (strlen (line) + 1); if (!newc) return gpg_error_from_syserror (); strcpy_escaped (newc, line); if (pinentry.cancel) free (pinentry.cancel); pinentry.cancel = newc; return 0; } static gpg_error_t cmd_settimeout (assuan_context_t ctx, char *line) { (void)ctx; if (line && *line) pinentry.timeout = atoi (line); return 0; } static gpg_error_t cmd_settitle (assuan_context_t ctx, char *line) { char *newt; (void)ctx; newt = malloc (strlen (line) + 1); if (!newt) return gpg_error_from_syserror (); strcpy_escaped (newt, line); if (pinentry.title) free (pinentry.title); pinentry.title = newt; return 0; } static gpg_error_t cmd_setqualitybar (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (!*line) line = "Quality:"; newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); if (pinentry.quality_bar) free (pinentry.quality_bar); pinentry.quality_bar = newval; return 0; } /* Set the tooltip to be used for a quality bar. */ static gpg_error_t cmd_setqualitybar_tt (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.quality_bar_tt) free (pinentry.quality_bar_tt); pinentry.quality_bar_tt = newval; return 0; } /* Set the tooltip to be used for a generate action. */ static gpg_error_t cmd_setgenpin_tt (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.genpin_tt) free (pinentry.genpin_tt); pinentry.genpin_tt = newval; return 0; } /* Set the label to be used for a generate action. */ static gpg_error_t cmd_setgenpin_label (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.genpin_label) free (pinentry.genpin_label); pinentry.genpin_label = newval; return 0; } static gpg_error_t cmd_getpin (assuan_context_t ctx, char *line) { int result; int set_prompt = 0; int just_read_password_from_cache = 0; (void)line; pinentry_setbuffer_init (&pinentry); if (!pinentry.pin) return gpg_error (GPG_ERR_ENOMEM); pinentry.confirm = 0; /* Try reading from the password cache. */ if (/* If repeat passphrase is set, then we don't want to read from the cache. */ ! pinentry.repeat_passphrase /* Are we allowed to read from the cache? */ && pinentry.allow_external_password_cache && pinentry.keyinfo /* Only read from the cache if we haven't already tried it. */ && ! pinentry.tried_password_cache /* If the last read resulted in an error, then don't read from the cache. */ && ! pinentry.error) { char *password; int give_up_on_password_store = 0; pinentry.tried_password_cache = 1; password = password_cache_lookup (pinentry.keyinfo, &give_up_on_password_store); if (give_up_on_password_store) pinentry.allow_external_password_cache = 0; if (password) /* There is a cached password. Try it. */ { int len = strlen(password) + 1; if (len > pinentry.pin_len) len = pinentry.pin_len; memcpy (pinentry.pin, password, len); pinentry.pin[len] = '\0'; secmem_free (password); pinentry.pin_from_cache = 1; assuan_write_status (ctx, "PASSWORD_FROM_CACHE", ""); /* Result is the length of the password not including the NUL terminator. */ result = len - 1; just_read_password_from_cache = 1; goto out; } } /* The password was not cached (or we are not allowed to / cannot use the cache). Prompt the user. */ pinentry.pin_from_cache = 0; if (!pinentry.prompt) { pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:"; set_prompt = 1; } pinentry.locale_err = 0; pinentry.specific_err = 0; pinentry.specific_err_loc = NULL; free (pinentry.specific_err_info); pinentry.specific_err_info = NULL; pinentry.close_button = 0; pinentry.repeat_okay = 0; pinentry.one_button = 0; pinentry.ctx_assuan = ctx; result = (*pinentry_cmd_handler) (&pinentry); pinentry.ctx_assuan = NULL; if (pinentry.error) { free (pinentry.error); pinentry.error = NULL; } if (pinentry.repeat_passphrase) { free (pinentry.repeat_passphrase); pinentry.repeat_passphrase = NULL; } if (set_prompt) pinentry.prompt = NULL; pinentry.quality_bar = 0; /* Reset it after the command. */ if (pinentry.close_button) assuan_write_status (ctx, "BUTTON_INFO", "close"); if (result < 0) { pinentry_setbuffer_clear (&pinentry); if (pinentry.specific_err) { write_status_error (ctx, &pinentry); if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return pinentry.specific_err; } return (pinentry.locale_err ? gpg_error (GPG_ERR_LOCALE_PROBLEM) : gpg_error (GPG_ERR_CANCELED)); } out: if (result) { if (pinentry.repeat_okay) assuan_write_status (ctx, "PIN_REPEATED", ""); assuan_begin_confidential (ctx); result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin)); if (!result) result = assuan_send_data (ctx, NULL, 0); assuan_end_confidential (ctx); if (/* GPG Agent says it's okay. */ pinentry.allow_external_password_cache && pinentry.keyinfo /* We didn't just read it from the cache. */ && ! just_read_password_from_cache /* And the user said it's okay. */ && pinentry.may_cache_password) /* Cache the password. */ password_cache_save (pinentry.keyinfo, pinentry.pin); } pinentry_setbuffer_clear (&pinentry); return result; } /* Note that the option --one-button is a hack to allow the use of old pinentries while the caller is ignoring the result. Given that options have never been used or flagged as an error the new option is an easy way to enable the messsage mode while not requiring to update pinentry or to have the caller test for the message command. New applications which are free to require an updated pinentry should use MESSAGE instead. */ static gpg_error_t cmd_confirm (assuan_context_t ctx, char *line) { int result; pinentry.one_button = !!strstr (line, "--one-button"); pinentry.quality_bar = 0; pinentry.close_button = 0; pinentry.locale_err = 0; pinentry.specific_err = 0; pinentry.specific_err_loc = NULL; free (pinentry.specific_err_info); pinentry.specific_err_info = NULL; pinentry.canceled = 0; pinentry.confirm = 1; pinentry_setbuffer_clear (&pinentry); result = (*pinentry_cmd_handler) (&pinentry); if (pinentry.error) { free (pinentry.error); pinentry.error = NULL; } if (pinentry.close_button) assuan_write_status (ctx, "BUTTON_INFO", "close"); if (result > 0) return 0; /* OK */ if (pinentry.specific_err) { write_status_error (ctx, &pinentry); if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return pinentry.specific_err; } if (pinentry.locale_err) return gpg_error (GPG_ERR_LOCALE_PROBLEM); if (pinentry.one_button) return 0; /* OK */ if (pinentry.canceled) return gpg_error (GPG_ERR_CANCELED); return gpg_error (GPG_ERR_NOT_CONFIRMED); } static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { (void)line; return cmd_confirm (ctx, "--one-button"); } /* Return a staically allocated string with information on the mode, * uid, and gid of DEVICE. On error "?" is returned if DEVICE is * NULL, "-" is returned. */ static const char * device_stat_string (const char *device) { #ifdef HAVE_STAT static char buf[40]; struct stat st; if (!device || !*device) return "-"; if (stat (device, &st)) return "?"; /* Error */ snprintf (buf, sizeof buf, "%lo/%lu/%lu", (unsigned long)st.st_mode, (unsigned long)st.st_uid, (unsigned long)st.st_gid); return buf; #else return "-"; #endif } /* GETINFO <what> Multipurpose function to return a variety of information. Supported values for WHAT are: version - Return the version of the program. pid - Return the process id of the server. flavor - Return information about the used pinentry flavor ttyinfo - Return DISPLAY, ttyinfo and an emacs pinentry status */ static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { int rc; const char *s; char buffer[150]; if (!strcmp (line, "version")) { s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (buffer, sizeof buffer, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, buffer, strlen (buffer)); } else if (!strcmp (line, "flavor")) { if (!strncmp (this_pgmname, "pinentry-", 9) && this_pgmname[9]) s = this_pgmname + 9; else s = this_pgmname; snprintf (buffer, sizeof buffer, "%s%s%s", s, flavor_flag? ":":"", flavor_flag? flavor_flag : ""); rc = assuan_send_data (ctx, buffer, strlen (buffer)); /* if (!rc) */ /* rc = assuan_write_status (ctx, "FEATURES", "tabbing foo bar"); */ } else if (!strcmp (line, "ttyinfo")) { char emacs_status[10]; #ifdef INSIDE_EMACS snprintf (emacs_status, sizeof emacs_status, "%d", pinentry_emacs_status ()); #else strcpy (emacs_status, "-"); #endif snprintf (buffer, sizeof buffer, "%s %s %s %s %lu/%lu %s", pinentry.ttyname? pinentry.ttyname : "-", pinentry.ttytype_l? pinentry.ttytype_l : "-", pinentry.display? pinentry.display : "-", device_stat_string (pinentry.ttyname), #ifdef HAVE_DOSISH_SYSTEM 0l, 0l, #else (unsigned long)geteuid (), (unsigned long)getegid (), #endif emacs_status ); rc = assuan_send_data (ctx, buffer, strlen (buffer)); } else rc = gpg_error (GPG_ERR_ASS_PARAMETER); return rc; } /* CLEARPASSPHRASE <cacheid> Clear the cache passphrase associated with the key identified by cacheid. */ static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { (void)ctx; if (! line) return gpg_error (GPG_ERR_ASS_INV_VALUE); /* Remove leading and trailing white space. */ while (*line == ' ') line ++; while (line[strlen (line) - 1] == ' ') line[strlen (line) - 1] = 0; switch (password_cache_clear (line)) { case 1: return 0; case 0: return gpg_error (GPG_ERR_ASS_INV_VALUE); default: return gpg_error (GPG_ERR_ASS_GENERAL); } } /* Tell the assuan library about our commands. */ static gpg_error_t register_commands (assuan_context_t ctx) { static struct { const char *name; gpg_error_t (*handler) (assuan_context_t, char *line); } table[] = { { "SETDESC", cmd_setdesc }, { "SETPROMPT", cmd_setprompt }, { "SETKEYINFO", cmd_setkeyinfo }, { "SETREPEAT", cmd_setrepeat }, { "SETREPEATERROR", cmd_setrepeaterror }, { "SETREPEATOK", cmd_setrepeatok}, { "SETERROR", cmd_seterror }, { "SETOK", cmd_setok }, { "SETNOTOK", cmd_setnotok }, { "SETCANCEL", cmd_setcancel }, { "GETPIN", cmd_getpin }, { "CONFIRM", cmd_confirm }, { "MESSAGE", cmd_message }, { "SETQUALITYBAR", cmd_setqualitybar }, { "SETQUALITYBAR_TT", cmd_setqualitybar_tt }, { "SETGENPIN", cmd_setgenpin_label }, { "SETGENPIN_TT", cmd_setgenpin_tt }, { "GETINFO", cmd_getinfo }, { "SETTITLE", cmd_settitle }, { "SETTIMEOUT", cmd_settimeout }, { "CLEARPASSPHRASE", cmd_clear_passphrase }, { NULL } }; int i, j; gpg_error_t rc; for (i = j = 0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL); if (rc) return rc; } return 0; } int pinentry_loop2 (int infd, int outfd) { gpg_error_t rc; assuan_fd_t filedes[2]; assuan_context_t ctx; /* Extra check to make sure we have dropped privs. */ #ifndef HAVE_DOSISH_SYSTEM if (getuid() != geteuid()) abort (); #endif rc = assuan_new (&ctx); if (rc) { fprintf (stderr, "server context creation failed: %s\n", gpg_strerror (rc)); return -1; } /* For now we use a simple pipe based server so that we can work from scripts. We will later add options to run as a daemon and wait for requests on a Unix domain socket. */ filedes[0] = assuan_fdopen (infd); filedes[1] = assuan_fdopen (outfd); rc = assuan_init_pipe_server (ctx, filedes); if (rc) { fprintf (stderr, "%s: failed to initialize the server: %s\n", this_pgmname, gpg_strerror (rc)); return -1; } rc = register_commands (ctx); if (rc) { fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n", this_pgmname, gpg_strerror (rc)); return -1; } assuan_register_option_handler (ctx, option_handler); #if 0 assuan_set_log_stream (ctx, stderr); #endif assuan_register_reset_notify (ctx, pinentry_assuan_reset_handler); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; else if (rc) { fprintf (stderr, "%s: Assuan accept problem: %s\n", this_pgmname, gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { fprintf (stderr, "%s: Assuan processing failed: %s\n", this_pgmname, gpg_strerror (rc)); continue; } } assuan_release (ctx); return 0; } /* Start the pinentry event loop. The program will start to process Assuan commands until it is finished or an error occurs. If an error occurs, -1 is returned. Otherwise, 0 is returned. */ int pinentry_loop (void) { return pinentry_loop2 (STDIN_FILENO, STDOUT_FILENO); } diff --git a/pinentry/pinentry.h b/pinentry/pinentry.h index 5f58ce1..78b7f6e 100644 --- a/pinentry/pinentry.h +++ b/pinentry/pinentry.h @@ -1,375 +1,377 @@ /* pinentry.h - The interface for the PIN entry support library. * Copyright (C) 2002, 2003, 2010, 2015, 2021 g10 Code GmbH * * This file is part of PINENTRY. * * PINENTRY is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * PINENTRY is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifndef PINENTRY_H #define PINENTRY_H +#include "../secmem/secmem.h" + #ifdef __cplusplus extern "C" { #if 0 } #endif #endif typedef enum { PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT, PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED, PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW, PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA, PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE } pinentry_color_t; struct pinentry { /* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */ char *title; /* The description to display, or NULL. (Assuan: "SETDESC DESC".) */ char *description; /* The error message to display, or NULL. (Assuan: "SETERROR MESSAGE".) */ char *error; /* The prompt to display, or NULL. (Assuan: "SETPROMPT prompt".) */ char *prompt; /* The OK button text to display, or NULL. (Assuan: "SETOK OK".) */ char *ok; /* The Not-OK button text to display, or NULL. This is the text for the alternative option shown by the third button. (Assuan: "SETNOTOK NOTOK".) */ char *notok; /* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL CANCEL".) */ char *cancel; /* The buffer to store the secret into. */ char *pin; /* The length of the buffer. */ int pin_len; /* Whether the pin was read from an external cache (1) or entered by the user (0). */ int pin_from_cache; /* The name of the X display to use if X is available and supported. (Assuan: "OPTION display DISPLAY".) */ char *display; /* The name of the terminal node to open if X not available or supported. (Assuan: "OPTION ttyname TTYNAME".) */ char *ttyname; /* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */ char *ttytype_l; /* Set the alert mode (none, beep or flash). */ char *ttyalert; /* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype LC_CTYPE".) */ char *lc_ctype; /* The LC_MESSAGES value for the terminal. (Assuan: "OPTION lc-messages LC_MESSAGES".) */ char *lc_messages; /* True if debug mode is requested. */ int debug; /* The number of seconds before giving up while waiting for user input. */ int timeout; /* True if caller should grab the keyboard. (Assuan: "OPTION grab" or "OPTION no-grab".) */ int grab; /* The PID of the owner or 0 if not known. The owner is the process * which actually triggered the the pinentry. For example gpg. */ unsigned long owner_pid; /* The numeric uid (user ID) of the owner process or -1 if not * known. */ int owner_uid; /* The malloced hostname of the owner or NULL. */ char *owner_host; /* The window ID of the parent window over which the pinentry window should be displayed. (Assuan: "OPTION parent-wid WID".) */ int parent_wid; /* The name of an optional file which will be touched after a curses entry has been displayed. (Assuan: "OPTION touch-file FILENAME".) */ char *touch_file; /* The frontend should set this to -1 if the user canceled the request, and to the length of the PIN stored in pin otherwise. */ int result; /* The frontend should set this if the NOTOK button was pressed. */ int canceled; /* The frontend should set this to true if an error with the local conversion occurred. */ int locale_err; /* The frontend should set this to a gpg-error so that commands are able to return specific error codes. This is an ugly hack due to the fact that pinentry_cmd_handler_t returns the length of the passphrase or a negative error code. */ int specific_err; /* The frontend may store a string with the error location here. */ const char *specific_err_loc; /* The frontend may store a malloced string here to emit an ERROR * status code with this extra info along with SPECIFIC_ERR. */ char *specific_err_info; /* The frontend should set this to true if the window close button has been used. This flag is used in addition to a regular return value. */ int close_button; /* The caller should set this to true if only one button is required. This is useful for notification dialogs where only a dismiss button is required. */ int one_button; /* Whether this is a CONFIRM pinentry. */ int confirm; /* If true a second prompt for the passphrase is shown and the user is expected to enter the same passphrase again. Pinentry checks that both match. (Assuan: "SETREPEAT".) */ char *repeat_passphrase; /* The string to show if a repeated passphrase does not match. (Assuan: "SETREPEATERROR ERROR".) */ char *repeat_error_string; /* The string to show if a repeated passphrase does match. (Assuan: "SETREPEATOK STRING".) */ char *repeat_ok_string; /* Set to true if the passphrase has been entered a second time and matches the first passphrase. */ int repeat_okay; /* If this is not NULL, a passphrase quality indicator is shown. There will also be an inquiry back to the caller to get an indication of the quality for the passphrase entered so far. The string is used as a label for the quality bar. (Assuan: "SETQUALITYBAR LABEL".) */ char *quality_bar; /* The tooltip to be shown for the qualitybar. Malloced or NULL. (Assuan: "SETQUALITYBAR_TT TOOLTIP".) */ char *quality_bar_tt; /* If this is not NULL, a generate action should be shown. There will be an inquiry back to the caller to get such a PIN. generate action. Malloced or NULL. (Assuan: "SETGENPIN LABEL" .) */ char *genpin_label; /* The tooltip to be shown for the generate action. Malloced or NULL. (Assuan: "SETGENPIN_TT TOOLTIP".) */ char *genpin_tt; /* Specifies whether passphrase formatting should be enabled. (Assuan: "OPTION formatted-passphrase") */ int formatted_passphrase; /* A hint to be shown near the passphrase input field if passphrase formatting is enabled. Malloced or NULL. (Assuan: "OPTION formatted-passphrase-hint=HINT".) */ char *formatted_passphrase_hint; /* For the curses pinentry, the color of error messages. */ pinentry_color_t color_fg; int color_fg_bright; pinentry_color_t color_bg; pinentry_color_t color_so; int color_so_bright; pinentry_color_t color_ok; int color_ok_bright; pinentry_color_t color_qualitybar; int color_qualitybar_bright; /* Malloced and i18ned default strings or NULL. These strings may include an underscore character to indicate an accelerator key. A double underscore represents a plain one. */ /* (Assuan: "OPTION default-ok OK"). */ char *default_ok; /* (Assuan: "OPTION default-cancel CANCEL"). */ char *default_cancel; /* (Assuan: "OPTION default-prompt PROMPT"). */ char *default_prompt; /* (Assuan: "OPTION default-pwmngr SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */ char *default_pwmngr; /* (Assuan: "OPTION default-cf-visi Do you really want to make your passphrase visible?"). */ char *default_cf_visi; /* (Assuan: "OPTION default-tt-visi Make passphrase visible?"). */ char *default_tt_visi; /* (Assuan: "OPTION default-tt-hide Hide passphrase"). */ char *default_tt_hide; /* (Assuan: "OPTION default-capshint Caps Lock is on"). */ char *default_capshint; /* Whether we are allowed to read the password from an external cache. (Assuan: "OPTION allow-external-password-cache") */ int allow_external_password_cache; /* We only try the cache once. */ int tried_password_cache; /* A stable identifier for the key. (Assuan: "SETKEYINFO KEYINFO".) */ char *keyinfo; /* Whether we may cache the password (according to the user). */ int may_cache_password; /* NOTE: If you add any additional fields to this structure, be sure to update the initializer in pinentry/pinentry.c!!! */ /* For the quality indicator and genpin we need to do an inquiry. Thus we need to save the assuan ctx. */ void *ctx_assuan; /* An UTF-8 string with an invisible character used to override the default in some pinentries. Only the first character is used. */ char *invisible_char; /* Whether the passphrase constraints are enforced by gpg-agent. (Assuan: "OPTION constraints-enforce") */ int constraints_enforce; /* A short translated hint for the user with the constraints for new passphrases to be displayed near the passphrase input field. Malloced or NULL. (Assuan: "OPTION constraints-hint-short=At least 8 characters".) */ char *constraints_hint_short; /* A longer translated hint for the user with the constraints for new passphrases to be displayed for example as tooltip. Malloced or NULL. (Assuan: "OPTION constraints-hint-long=The passphrase must ...".) */ char *constraints_hint_long; /* A short translated title for an error dialog informing the user about unsatisfied passphrase constraints. Malloced or NULL. (Assuan: "OPTION constraints-error-title=Passphrase Not Allowed".) */ char *constraints_error_title; }; typedef struct pinentry *pinentry_t; /* The pinentry command handler type processes the pinentry request PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN entry. On confirmation, the function should return TRUE if confirmed, and FALSE otherwise. On PIN entry, the function should return -1 if an error occurred or the user cancelled the operation and 1 otherwise. */ typedef int (*pinentry_cmd_handler_t) (pinentry_t pin); /* Start the pinentry event loop. The program will start to process Assuan commands until it is finished or an error occurs. If an error occurs, -1 is returned and errno indicates the type of an error. Otherwise, 0 is returned. */ int pinentry_loop (void); /* The same as above but allows to specify the i/o descriptors. * infd and outfd will be duplicated in this function so the caller * still has to close them if necessary. */ int pinentry_loop2 (int infd, int outfd); const char *pinentry_get_pgmname (void); char *pinentry_get_title (pinentry_t pe); /* Run a quality inquiry for PASSPHRASE of LENGTH. */ int pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length); /* Run a checkpin inquiry for PASSPHRASE of LENGTH. Returns NULL, if the passphrase satisfies the constraints. Otherwise, returns a malloced error string. */ char *pinentry_inq_checkpin (pinentry_t pin, const char *passphrase, size_t length); /* Run a genpin iquriry. Returns a malloced string or NULL */ char *pinentry_inq_genpin (pinentry_t pin); /* Try to make room for at least LEN bytes for the pin in the pinentry PIN. Returns new buffer on success and 0 on failure. */ char *pinentry_setbufferlen (pinentry_t pin, int len); /* Use the buffer at BUFFER for PIN->PIN. BUFFER must be NULL or allocated using secmem_alloc. LEN is the size of the buffer. If it is unknown, but BUFFER is a NUL terminated string, you pass 0 to just use strlen(buffer)+1. */ void pinentry_setbuffer_use (pinentry_t pin, char *buffer, int len); /* Initialize the secure memory subsystem, drop privileges and return. Must be called early. */ void pinentry_init (const char *pgmname); /* Return true if either DISPLAY is set or ARGV contains the string "--display". */ int pinentry_have_display (int argc, char **argv); /* Parse the command line options. May exit the program if only help or version output is requested. */ void pinentry_parse_opts (int argc, char *argv[]); /* Set the optional flag used with getinfo. */ void pinentry_set_flavor_flag (const char *string); /* The caller must define this variable to process assuan commands. */ extern pinentry_cmd_handler_t pinentry_cmd_handler; #ifdef HAVE_W32_SYSTEM /* Windows declares sleep as obsolete, but provides a definition for _sleep but non for the still existing sleep. */ #define sleep(a) _sleep ((a)) #endif /*HAVE_W32_SYSTEM*/ #if 0 { #endif #ifdef __cplusplus } #endif #endif /* PINENTRY_H */ diff --git a/secmem/Makefile.am b/secmem/Makefile.am index 9e176e7..48f3ef5 100644 --- a/secmem/Makefile.am +++ b/secmem/Makefile.am @@ -1,30 +1,30 @@ # Secure Memory Makefile # Copyright (C) 2002 g10 Code GmbH # # This file is part of PINENTRY. # # PINENTRY is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # PINENTRY is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see <https://www.gnu.org/licenses/>. # SPDX-License-Identifier: GPL-2.0+ ## Process this file with automake to produce Makefile.in noinst_LIBRARIES = libsecmem.a libsecmem_a_SOURCES = \ - memory.h \ + secmem.h \ secmem-util.h \ util.h \ secmem.c \ util.c \ secmem++.h diff --git a/secmem/secmem++.h b/secmem/secmem++.h index b0aa9e9..116da88 100644 --- a/secmem/secmem++.h +++ b/secmem/secmem++.h @@ -1,91 +1,91 @@ /* STL allocator for secmem * Copyright (C) 2008 Marc Mutz <marc@kdab.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifndef __SECMEM_SECMEMPP_H__ #define __SECMEM_SECMEMPP_H__ -#include "secmem/memory.h" +#include "../secmem/secmem.h" #include <cstddef> namespace secmem { template <typename T> class alloc { public: // type definitions: typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef T value_type; // rebind template <typename U> struct rebind { typedef alloc<U> other; }; // address pointer address( reference value ) const { return &value; } const_pointer address( const_reference value ) const { return &value; } // (trivial) ctors and dtors alloc() {} alloc( const alloc & ) {} template <typename U> alloc( const alloc<U> & ) {} // copy ctor is ok ~alloc() {} // de/allocation size_type max_size() const { return secmem_get_max_size(); } pointer allocate( size_type n, void * =0 ) { return static_cast<pointer>( secmem_malloc( n * sizeof(T) ) ); } void deallocate( pointer p, size_type ) { secmem_free( p ); } // de/construct void construct( pointer p, const T & value ) { void * loc = p; new (loc)T(value); } void destruct( pointer p ) { p->~T(); } }; // equality comparison template <typename T1,typename T2> bool operator==( const alloc<T1> &, const alloc<T2> & ) { return true; } template <typename T1, typename T2> bool operator!=( const alloc<T1> &, const alloc<T2> & ) { return false; } } #endif /* __SECMEM_SECMEMPP_H__ */ diff --git a/secmem/secmem.c b/secmem/secmem.c index 5cf5b5f..6f82273 100644 --- a/secmem/secmem.c +++ b/secmem/secmem.c @@ -1,443 +1,443 @@ /* secmem.c - memory allocation from a secure heap * Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #include <config.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #ifndef HAVE_W32CE_SYSTEM #include <errno.h> #endif #include <stdarg.h> #include <unistd.h> #if defined(HAVE_MLOCK) || defined(HAVE_MMAP) # include <sys/mman.h> # include <sys/types.h> # include <fcntl.h> #endif #include <string.h> -#include "memory.h" +#include "secmem.h" #ifdef ORIGINAL_GPG_VERSION #include "types.h" #include "util.h" #else /* ORIGINAL_GPG_VERSION */ #include "util.h" typedef union { int a; short b; char c[1]; long d; #ifdef HAVE_U64_TYPE u64 e; #endif float f; double g; } PROPERLY_ALIGNED_TYPE; #define log_error log_info #define log_bug log_fatal void log_info(char *template, ...) { va_list args; va_start(args, template); vfprintf(stderr, template, args); va_end(args); } void log_fatal(char *template, ...) { va_list args; va_start(args, template); vfprintf(stderr, template, args); va_end(args); exit(EXIT_FAILURE); } #endif /* ORIGINAL_GPG_VERSION */ #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) # define MAP_ANONYMOUS MAP_ANON #endif #define DEFAULT_POOLSIZE 16384 typedef struct memblock_struct MEMBLOCK; struct memblock_struct { unsigned size; union { MEMBLOCK *next; PROPERLY_ALIGNED_TYPE aligned; } u; }; static void *pool; static volatile int pool_okay; /* may be checked in an atexit function */ #if HAVE_MMAP static int pool_is_mmapped; #endif static size_t poolsize; /* allocated length */ static size_t poollen; /* used length */ static MEMBLOCK *unused_blocks; static unsigned max_alloced; static unsigned cur_alloced; static unsigned max_blocks; static unsigned cur_blocks; static int disable_secmem; static int show_warning; static int no_warning; static int suspend_warning; static void print_warn(void) { if( !no_warning ) log_info("Warning: using insecure memory!\n"); } static void lock_pool( void *p, size_t n ) { #if defined(HAVE_MLOCK) uid_t uid; int err; uid = getuid(); #ifdef HAVE_BROKEN_MLOCK if( uid ) { errno = EPERM; err = -1; } else { err = mlock( p, n ); } #else err = mlock( p, n ); #endif if( uid && !geteuid() ) { if( setuid( uid ) || getuid() != geteuid() ) log_fatal("failed to reset uid: %s\n", strerror(errno)); } if( err ) { if( errno != EPERM #ifdef EAGAIN /* OpenBSD returns this */ && errno != EAGAIN #endif ) log_error("can't lock memory: %s\n", strerror(errno)); show_warning = 1; } #else (void)p; (void)n; log_info("Please note that you don't have secure memory on this system\n"); #endif } static void init_pool( size_t n) { #if HAVE_MMAP size_t pgsize; #endif poolsize = n; if( disable_secmem ) log_bug("secure memory is disabled"); #if HAVE_MMAP #ifdef HAVE_GETPAGESIZE pgsize = getpagesize(); #else pgsize = 4096; #endif poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1); # ifdef MAP_ANONYMOUS pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); # else /* map /dev/zero instead */ { int fd; fd = open("/dev/zero", O_RDWR); if( fd == -1 ) { log_error("can't open /dev/zero: %s\n", strerror(errno) ); pool = (void*)-1; } else { pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); close (fd); } } # endif if( pool == (void*)-1 ) log_info("can't mmap pool of %u bytes: %s - using malloc\n", (unsigned)poolsize, strerror(errno)); else { pool_is_mmapped = 1; pool_okay = 1; } #endif if( !pool_okay ) { pool = malloc( poolsize ); if( !pool ) log_fatal("can't allocate memory pool of %u bytes\n", (unsigned)poolsize); else pool_okay = 1; } lock_pool( pool, poolsize ); poollen = 0; } /* concatenate unused blocks */ static void compress_pool(void) { /* fixme: we really should do this */ } void secmem_set_flags( unsigned flags ) { int was_susp = suspend_warning; no_warning = flags & 1; suspend_warning = flags & 2; /* and now issue the warning if it is not longer suspended */ if( was_susp && !suspend_warning && show_warning ) { show_warning = 0; print_warn(); } } unsigned secmem_get_flags(void) { unsigned flags; flags = no_warning ? 1:0; flags |= suspend_warning ? 2:0; return flags; } void secmem_init( size_t n ) { if( !n ) { #if !defined(HAVE_DOSISH_SYSTEM) uid_t uid; disable_secmem=1; uid = getuid(); if( uid != geteuid() ) { if( setuid( uid ) || getuid() != geteuid() ) log_fatal("failed to drop setuid\n" ); } #endif } else { if( n < DEFAULT_POOLSIZE ) n = DEFAULT_POOLSIZE; if( !pool_okay ) init_pool(n); else log_error("Oops, secure memory pool already initialized\n"); } } void * secmem_malloc( size_t size ) { MEMBLOCK *mb, *mb2; int compressed=0; if( !pool_okay ) { log_info( "operation is not possible without initialized secure memory\n"); log_info("(you may have used the wrong program for this task)\n"); exit(2); } if( show_warning && !suspend_warning ) { show_warning = 0; print_warn(); } /* blocks are always a multiple of 32 */ size += sizeof(MEMBLOCK); size = ((size + 31) / 32) * 32; retry: /* try to get it from the used blocks */ for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next ) if( mb->size >= size ) { if( mb2 ) mb2->u.next = mb->u.next; else unused_blocks = mb->u.next; goto leave; } /* allocate a new block */ if( (poollen + size <= poolsize) ) { mb = (void*)((char*)pool + poollen); poollen += size; mb->size = size; } else if( !compressed ) { compressed=1; compress_pool(); goto retry; } else return NULL; leave: cur_alloced += mb->size; cur_blocks++; if( cur_alloced > max_alloced ) max_alloced = cur_alloced; if( cur_blocks > max_blocks ) max_blocks = cur_blocks; memset (&mb->u.aligned.c, 0, size - (size_t) &((struct memblock_struct *) 0)->u.aligned.c); return &mb->u.aligned.c; } void * secmem_realloc( void *p, size_t newsize ) { MEMBLOCK *mb; size_t size; void *a; if (! p) return secmem_malloc(newsize); mb = (MEMBLOCK*) (void *) ((char *) p - offsetof (MEMBLOCK, u.aligned.c)); size = mb->size; if( newsize < size ) return p; /* it is easier not to shrink the memory */ a = secmem_malloc( newsize ); memcpy(a, p, size); memset((char*)a+size, 0, newsize-size); secmem_free(p); return a; } void secmem_free( void *a ) { MEMBLOCK *mb; size_t size; if( !a ) return; mb = (MEMBLOCK*) (void *) ((char*)a - offsetof (MEMBLOCK, u.aligned.c)); size = mb->size; /* This does not make much sense: probably this memory is held in the * cache. We do it anyway: */ wipememory2(mb, 0xff, size ); wipememory2(mb, 0xaa, size ); wipememory2(mb, 0x55, size ); wipememory2(mb, 0x00, size ); mb->size = size; mb->u.next = unused_blocks; unused_blocks = mb; cur_blocks--; cur_alloced -= size; } int m_is_secure( const void *p ) { return p >= pool && p < (void*)((char*)pool+poolsize); } void secmem_term(void) { if( !pool_okay ) return; wipememory2( pool, 0xff, poolsize); wipememory2( pool, 0xaa, poolsize); wipememory2( pool, 0x55, poolsize); wipememory2( pool, 0x00, poolsize); #if HAVE_MMAP if( pool_is_mmapped ) munmap( pool, poolsize ); #endif pool = NULL; pool_okay = 0; poolsize=0; poollen=0; unused_blocks=NULL; } void secmem_dump_stats(void) { if( disable_secmem ) return; fprintf(stderr, "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n", cur_alloced, max_alloced, cur_blocks, max_blocks, (ulong)poollen, (ulong)poolsize ); } size_t secmem_get_max_size (void) { return poolsize; } diff --git a/secmem/memory.h b/secmem/secmem.h similarity index 100% rename from secmem/memory.h rename to secmem/secmem.h diff --git a/tqt/secqstring.h b/tqt/secqstring.h index e8fa554..9b9f496 100644 --- a/tqt/secqstring.h +++ b/tqt/secqstring.h @@ -1,307 +1,307 @@ /* secntqstring.h - Secure version of TQString. * Copyright (C) 1992-2002 Trolltech AS. All rights reserved. * Copyright (C) 2003 g10 Code GmbH * * The license of the original ntqstring.h file from which this file is * derived can be found below. Modified by Marcus Brinkmann * <marcus@g10code.de>. All modifications are licensed as follows, so * that the intersection of the two licenses is then the GNU General * Public License version 2. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0 */ /**************************************************************************** ** $Id$ ** ** Definition of the SecTQString class, and related Unicode functions. ** ** Created : 920609 ** ** Copyright (C) 1992-2002 Trolltech AS. All rights reserved. ** ** This file is part of the tools module of the TQt GUI Toolkit. ** ** This file may be distributed under the terms of the Q Public License ** as defined by Trolltech AS of Norway and appearing in the file ** LICENSE.TQPL included in the packaging of this file. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** Licensees holding valid TQt Enterprise Edition or TQt Professional Edition ** licenses may use this file in accordance with the TQt Commercial License ** Agreement provided with the Software. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for ** information about TQt Commercial License Agreements. ** See http://www.trolltech.com/qpl/ for TQPL licensing information. ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #ifndef SECTQSTRING_H #define SECTQSTRING_H extern "C" { -#include "memory.h" +#include "../secmem/secmem.h" } /* We need the original qchar and qstring for transparent conversion from TQChar to TQChar and TQString to SecTQString (but not the other way round). */ #include <ntqstring.h> #ifndef QT_H #include "ntqcstring.h" #endif // QT_H /***************************************************************************** SecTQString class *****************************************************************************/ class SecTQString; class SecTQCharRef; template <class T> class TQDeepCopy; #include <stdio.h> // internal struct Q_EXPORT SecTQStringData : public TQShared { SecTQStringData() : TQShared(), unicode(0), len(0), maxl(0) { ref(); } SecTQStringData(TQChar *u, uint l, uint m) : TQShared(), unicode(u), len(l), maxl(m) { } ~SecTQStringData() { if ( unicode ) ::secmem_free ((char*) unicode); } void deleteSelf(); TQChar *unicode; #ifdef Q_OS_MAC9 uint len; #else uint len : 30; #endif #ifdef Q_OS_MAC9 uint maxl; #else uint maxl : 30; #endif }; class Q_EXPORT SecTQString { public: SecTQString(); // make null string SecTQString( TQChar ); // one-char string SecTQString( const SecTQString & ); // impl-shared copy /* We need a way to convert a TQString to a SecTQString ("importing" it). Having no conversion for the other way prevents accidential bugs where the secure string is copied to insecure memory. */ SecTQString( const TQString & ); // deep copy SecTQString( const TQChar* unicode, uint length ); // deep copy ~SecTQString(); SecTQString &operator=( const SecTQString & ); // impl-shared copy QT_STATIC_CONST SecTQString null; bool isNull() const; bool isEmpty() const; uint length() const; void truncate( uint pos ); SecTQString left( uint len ) const; SecTQString right( uint len ) const; SecTQString mid( uint index, uint len=0xffffffff) const; SecTQString &insert( uint index, const SecTQString & ); SecTQString &insert( uint index, const TQChar*, uint len ); SecTQString &remove( uint index, uint len ); SecTQString &replace( uint index, uint len, const SecTQString & ); SecTQString &replace( uint index, uint len, const TQChar*, uint clen ); SecTQString &operator+=( const SecTQString &str ); TQChar at( uint i ) const { return i < d->len ? d->unicode[i] : TQChar::null; } TQChar operator[]( int i ) const { return at((uint)i); } SecTQCharRef at( uint i ); SecTQCharRef operator[]( int i ); TQChar constref(uint i) const { return at(i); } TQChar& ref(uint i) { // Optimized for easy-inlining by simple compilers. if ( d->count != 1 || i >= d->len ) subat( i ); return d->unicode[i]; } const TQChar* unicode() const { return d->unicode; } uchar* utf8() const; void setLength( uint newLength ); bool isRightToLeft() const; private: SecTQString( int size, bool /* dummy */ ); // allocate size incl. \0 void deref(); void real_detach(); void subat( uint ); void grow( uint newLength ); SecTQStringData *d; static SecTQStringData* shared_null; static SecTQStringData* makeSharedNull(); friend class SecTQConstString; friend class TQTextStream; SecTQString( SecTQStringData* dd, bool /* dummy */ ) : d(dd) { } // needed for TQDeepCopy void detach(); friend class TQDeepCopy<SecTQString>; }; class Q_EXPORT SecTQCharRef { friend class SecTQString; SecTQString& s; uint p; SecTQCharRef(SecTQString* str, uint pos) : s(*str), p(pos) { } public: // most TQChar operations repeated here // all this is not documented: We just say "like TQChar" and let it be. #ifndef Q_QDOC ushort unicode() const { return s.constref(p).unicode(); } // An operator= for each TQChar cast constructors SecTQCharRef operator=(char c ) { s.ref(p)=c; return *this; } SecTQCharRef operator=(uchar c ) { s.ref(p)=c; return *this; } SecTQCharRef operator=(TQChar c ) { s.ref(p)=c; return *this; } SecTQCharRef operator=(const SecTQCharRef& c ) { s.ref(p)=c.unicode(); return *this; } SecTQCharRef operator=(ushort rc ) { s.ref(p)=rc; return *this; } SecTQCharRef operator=(short rc ) { s.ref(p)=rc; return *this; } SecTQCharRef operator=(uint rc ) { s.ref(p)=rc; return *this; } SecTQCharRef operator=(int rc ) { s.ref(p)=rc; return *this; } operator TQChar () const { return s.constref(p); } // each function... bool isNull() const { return unicode()==0; } bool isPrint() const { return s.constref(p).isPrint(); } bool isPunct() const { return s.constref(p).isPunct(); } bool isSpace() const { return s.constref(p).isSpace(); } bool isMark() const { return s.constref(p).isMark(); } bool isLetter() const { return s.constref(p).isLetter(); } bool isNumber() const { return s.constref(p).isNumber(); } bool isLetterOrNumber() { return s.constref(p).isLetterOrNumber(); } bool isDigit() const { return s.constref(p).isDigit(); } int digitValue() const { return s.constref(p).digitValue(); } TQChar lower() const { return s.constref(p).lower(); } TQChar upper() const { return s.constref(p).upper(); } TQChar::Category category() const { return s.constref(p).category(); } TQChar::Direction direction() const { return s.constref(p).direction(); } TQChar::Joining joining() const { return s.constref(p).joining(); } bool mirrored() const { return s.constref(p).mirrored(); } TQChar mirroredChar() const { return s.constref(p).mirroredChar(); } // const SecTQString &decomposition() const { return s.constref(p).decomposition(); } TQChar::Decomposition decompositionTag() const { return s.constref(p).decompositionTag(); } unsigned char combiningClass() const { return s.constref(p).combiningClass(); } // Not the non-const ones of these. uchar cell() const { return s.constref(p).cell(); } uchar row() const { return s.constref(p).row(); } #endif }; inline SecTQCharRef SecTQString::at( uint i ) { return SecTQCharRef(this,i); } inline SecTQCharRef SecTQString::operator[]( int i ) { return at((uint)i); } class Q_EXPORT SecTQConstString : private SecTQString { public: SecTQConstString( const TQChar* unicode, uint length ); ~SecTQConstString(); const SecTQString& string() const { return *this; } }; /***************************************************************************** SecTQString inline functions *****************************************************************************/ // These two move code into makeSharedNull() and deletesData() // to improve cache-coherence (and reduce code bloat), while // keeping the common cases fast. // // No safe way to pre-init shared_null on ALL compilers/linkers. inline SecTQString::SecTQString() : d(shared_null ? shared_null : makeSharedNull()) { d->ref(); } // inline SecTQString::~SecTQString() { if ( d->deref() ) { if ( d != shared_null ) d->deleteSelf(); } } // needed for TQDeepCopy inline void SecTQString::detach() { real_detach(); } inline bool SecTQString::isNull() const { return unicode() == 0; } inline uint SecTQString::length() const { return d->len; } inline bool SecTQString::isEmpty() const { return length() == 0; } /***************************************************************************** SecTQString non-member operators *****************************************************************************/ Q_EXPORT inline const SecTQString operator+( const SecTQString &s1, const SecTQString &s2 ) { SecTQString tmp( s1 ); tmp += s2; return tmp; } #endif // SECTQSTRING_H diff --git a/tty/pinentry-tty.c b/tty/pinentry-tty.c index c4d85c6..3951bc8 100644 --- a/tty/pinentry-tty.c +++ b/tty/pinentry-tty.c @@ -1,612 +1,611 @@ /* pinentry-tty.c - A minimalist dumb terminal mechanism for PIN entry * Copyright (C) 2014 Serge Voilokov * Copyright (C) 2015 Daniel Kahn Gillmor <dkg@fifthhorseman.net> * Copyright (C) 2015 g10 Code GmbH * * This file is part of PINENTRY. * * PINENTRY is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * PINENTRY is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <termios.h> #ifdef HAVE_UTIME_H #include <utime.h> #endif /*HAVE_UTIME_H*/ #include <sys/types.h> #include <sys/stat.h> #include <ctype.h> #include <gpg-error.h> #include "pinentry.h" -#include "memory.h" #ifndef HAVE_DOSISH_SYSTEM static int timed_out; #endif static struct termios n_term; static struct termios o_term; static int terminal_save (int fd) { if ((tcgetattr (fd, &o_term)) == -1) return -1; return 0; } static void terminal_restore (int fd) { tcsetattr (fd, TCSANOW, &o_term); } static int terminal_setup (int fd, int line_edit) { n_term = o_term; if (line_edit) n_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); else { n_term.c_lflag &= ~(ECHO|ICANON); n_term.c_lflag |= ISIG; n_term.c_cc[VMIN] = 1; n_term.c_cc[VTIME]= 0; } if ((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1) return -1; return 1; } #define UNDERLINE_START "\033[4m" /* Bold, red. */ #define ALERT_START "\033[1;31m" #define NORMAL_RESTORE "\033[0m" static void fputs_highlighted (char *text, char *highlight, FILE *ttyfo) { for (; *text; text ++) { /* Skip accelerator prefix. */ if (*text == '_') { text ++; if (! *text) break; } if (text == highlight) fputs (UNDERLINE_START, ttyfo); fputc (*text, ttyfo); if (text == highlight) fputs (NORMAL_RESTORE, ttyfo); } } static char button (char *text, char *default_text, FILE *ttyfo) { char *highlight; int use_default = 0; if (! text) return 0; /* Skip any leading white space. */ while (*text == ' ') text ++; highlight = text; while ((highlight = strchr (highlight, '_'))) { highlight = highlight + 1; if (*highlight == '_') { /* Escaped underscore. Skip both characters. */ highlight++; continue; } if (!isalnum (*highlight)) /* Unusable accelerator. */ continue; break; } if (! highlight) /* Not accelerator. Take the first alpha-numeric character. */ { highlight = text; while (*highlight && !isalnum (*highlight)) highlight ++; } if (! *highlight) /* Hmm, no alpha-numeric characters. */ { if (! default_text) return 0; highlight = default_text; use_default = 1; } fputs (" ", ttyfo); fputs_highlighted (text, highlight, ttyfo); if (use_default) { fputs (" (", ttyfo); fputs_highlighted (default_text, highlight, ttyfo); fputc (')', ttyfo); } fputc ('\n', ttyfo); return tolower (*highlight); } static void dump_error_text (FILE *ttyfo, const char *text) { int lines = 0; if (! text || ! *text) return; for (;;) { const char *eol = strchr (text, '\n'); if (! eol) eol = text + strlen (text); lines ++; fwrite ("\n *** ", 6, 1, ttyfo); fputs (ALERT_START, ttyfo); fwrite (text, (size_t) (eol - text), 1, ttyfo); fputs (NORMAL_RESTORE, ttyfo); if (! *eol) break; text = eol + 1; } if (lines > 1) fputc ('\n', ttyfo); else fwrite (" ***\n", 5, 1, ttyfo); fputc ('\n', ttyfo); } static int confirm (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo) { char *msg; char *msgbuffer = NULL; char ok = 0; char notok = 0; char cancel = 0; int ret; dump_error_text (ttyfo, pinentry->error); msg = pinentry->description; if (! msg) { /* If there is no description, fallback to the title. */ msg = msgbuffer = pinentry_get_title (pinentry); } if (! msg) msg = "Confirm:"; if (msg) { fputs (msg, ttyfo); fputc ('\n', ttyfo); } free (msgbuffer); fflush (ttyfo); if (pinentry->ok) ok = button (pinentry->ok, "OK", ttyfo); else if (pinentry->default_ok) ok = button (pinentry->default_ok, "OK", ttyfo); else ok = button ("OK", NULL, ttyfo); if (! pinentry->one_button) { if (pinentry->cancel) cancel = button (pinentry->cancel, "Cancel", ttyfo); else if (pinentry->default_cancel) cancel = button (pinentry->default_cancel, "Cancel", ttyfo); if (pinentry->notok) notok = button (pinentry->notok, "No", ttyfo); } while (1) { int input; if (pinentry->one_button) fprintf (ttyfo, "Press any key to continue."); else { fputc ('[', ttyfo); if (ok) fputc (ok, ttyfo); if (cancel) fputc (cancel, ttyfo); if (notok) fputc (notok, ttyfo); fputs("]? ", ttyfo); } fflush (ttyfo); input = fgetc (ttyfi); if (input == EOF) { pinentry->close_button = 1; pinentry->canceled = 1; #ifndef HAVE_DOSISH_SYSTEM if (!timed_out && errno == EINTR) pinentry->specific_err = gpg_error (GPG_ERR_FULLY_CANCELED); #endif ret = 0; break; } else { fprintf (ttyfo, "%c\n", input); input = tolower (input); } if (pinentry->one_button) { ret = 1; break; } if (cancel && input == cancel) { pinentry->canceled = 1; ret = 0; break; } else if (notok && input == notok) { ret = 0; break; } else if (ok && input == ok) { ret = 1; break; } else { fprintf (ttyfo, "Invalid selection.\n"); } } #ifndef HAVE_DOSISH_SYSTEM if (timed_out) pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); #endif return ret; } static char * read_password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo) { int done = 0; int len = 128; int count = 0; char *buffer; (void) ttyfo; buffer = secmem_malloc (len); if (! buffer) return NULL; while (!done) { int c; if (count == len - 1) /* Double the buffer's size. Note: we check if count is len - 1 and not len so that we always have space for the NUL character. */ { int new_len = 2 * len; char *tmp = secmem_realloc (buffer, new_len); if (! tmp) { secmem_free (tmp); return NULL; } buffer = tmp; len = new_len; } c = fgetc (ttyfi); switch (c) { case EOF: done = -1; #ifndef HAVE_DOSISH_SYSTEM if (!timed_out && errno == EINTR) pinentry->specific_err = gpg_error (GPG_ERR_FULLY_CANCELED); #endif break; case '\n': done = 1; break; default: buffer[count ++] = c; break; } } buffer[count] = '\0'; if (done == -1) { secmem_free (buffer); return NULL; } return buffer; } static int password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo) { char *msg; char *msgbuffer = NULL; int done = 0; msg = pinentry->description; if (! msg) msg = msgbuffer = pinentry_get_title (pinentry); if (! msg) msg = "Enter your passphrase."; dump_error_text (ttyfo, pinentry->error); fprintf (ttyfo, "%s\n", msg); free (msgbuffer); while (! done) { char *passphrase; char *prompt = pinentry->prompt; if (! prompt || !*prompt) prompt = "PIN"; fprintf (ttyfo, "%s%s ", prompt, /* Make sure the prompt ends in a : or a question mark. */ (prompt[strlen(prompt) - 1] == ':' || prompt[strlen(prompt) - 1] == '?') ? "" : ":"); fflush (ttyfo); passphrase = read_password (pinentry, ttyfi, ttyfo); fputc ('\n', ttyfo); if (! passphrase) { done = -1; break; } if (! pinentry->repeat_passphrase) done = 1; else { char *passphrase2; prompt = pinentry->repeat_passphrase; fprintf (ttyfo, "%s%s ", prompt, /* Make sure the prompt ends in a : or a question mark. */ (prompt[strlen(prompt) - 1] == ':' || prompt[strlen(prompt) - 1] == '?') ? "" : ":"); fflush (ttyfo); passphrase2 = read_password (pinentry, ttyfi, ttyfo); fputc ('\n', ttyfo); if (! passphrase2) { done = -1; break; } if (strcmp (passphrase, passphrase2) == 0) { pinentry->repeat_okay = 1; done = 1; } else dump_error_text (ttyfo, pinentry->repeat_error_string ?: "Passphrases don't match."); secmem_free (passphrase2); } if (done == 1) pinentry_setbuffer_use (pinentry, passphrase, 0); else secmem_free (passphrase); } #ifndef HAVE_DOSISH_SYSTEM if (timed_out) pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); #endif return done; } /* If a touch has been registered, touch that file. */ static void do_touch_file(pinentry_t pinentry) { #ifdef HAVE_UTIME_H struct stat st; time_t tim; if (!pinentry->touch_file || !*pinentry->touch_file) return; if (stat(pinentry->touch_file, &st)) return; /* Oops. */ /* Make sure that we actually update the mtime. */ while ((tim = time(NULL)) == st.st_mtime) sleep(1); /* Update but ignore errors as we can't do anything in that case. Printing error messages may even clubber the display further. */ utime (pinentry->touch_file, NULL); #endif /*HAVE_UTIME_H*/ } #ifndef HAVE_DOSISH_SYSTEM static void catchsig (int sig) { if (sig == SIGALRM) timed_out = 1; } #endif int tty_cmd_handler (pinentry_t pinentry) { int rc = 0; FILE *ttyfi = stdin; FILE *ttyfo = stdout; int saved_errno = 0; #ifndef HAVE_DOSISH_SYSTEM timed_out = 0; if (pinentry->timeout) { struct sigaction sa; memset (&sa, 0, sizeof(sa)); sa.sa_handler = catchsig; sigaction (SIGALRM, &sa, NULL); sigaction (SIGINT, &sa, NULL); alarm (pinentry->timeout); } #endif if (pinentry->ttyname) { ttyfi = fopen (pinentry->ttyname, "r"); if (!ttyfi) return -1; ttyfo = fopen (pinentry->ttyname, "w"); if (!ttyfo) { saved_errno = errno; fclose (ttyfi); errno = saved_errno; return -1; } } if (terminal_save (fileno (ttyfi)) < 0) rc = -1; else { if (terminal_setup (fileno (ttyfi), !!pinentry->pin) == -1) { saved_errno = errno; fprintf (stderr, "terminal_setup failure, exiting\n"); rc = -1; } else { if (pinentry->pin) rc = password (pinentry, ttyfi, ttyfo); else rc = confirm (pinentry, ttyfi, ttyfo); terminal_restore (fileno (ttyfi)); do_touch_file (pinentry); } } if (pinentry->ttyname) { fclose (ttyfi); fclose (ttyfo); } if (saved_errno) errno = saved_errno; return rc; } pinentry_cmd_handler_t pinentry_cmd_handler = tty_cmd_handler; int main (int argc, char *argv[]) { pinentry_init ("pinentry-tty"); /* Consumes all arguments. */ pinentry_parse_opts(argc, argv); if (pinentry_loop ()) return 1; return 0; } diff --git a/w32/main.c b/w32/main.c index d8a4833..bb41363 100644 --- a/w32/main.c +++ b/w32/main.c @@ -1,719 +1,718 @@ /* main.c - Secure W32 dialog for PIN entry. * Copyright (C) 2004, 2007 g10 Code GmbH * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-2.0+ */ #include <config.h> #include <stdio.h> #include <stdlib.h> #if WINVER < 0x0403 # define WINVER 0x0403 /* Required for SendInput. */ #endif #include <windows.h> #ifdef HAVE_W32CE_SYSTEM # include <winioctl.h> # include <sipapi.h> #endif #include "pinentry.h" -#include "memory.h" #include "resource.h" /* #include "msgcodes.h" */ #define PGMNAME "pinentry-w32" #ifndef LSFW_LOCK # define LSFW_LOCK 1 # define LSFW_UNLOCK 2 #endif #ifndef debugfp #define debugfp stderr #endif /* This function pointer gets initialized in main. */ #ifndef HAVE_W32CE_SYSTEM static BOOL WINAPI (*lock_set_foreground_window)(UINT); #endif static int w32_cmd_handler (pinentry_t pe); static void ok_button_clicked (HWND dlg, pinentry_t pe); /* We use global variables for the state, because there should never ever be a second instance. */ static HWND dialog_handle; static int confirm_mode; static int passphrase_ok; static int confirm_yes; /* The file descriptors for the loop. */ static int w32_infd; static int w32_outfd; /* Connect this module to the pinentry framework. */ pinentry_cmd_handler_t pinentry_cmd_handler = w32_cmd_handler; const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); #ifdef HAVE_W32CE_SYSTEM /* There is only a wchar_t FormatMessage. It does not make much sense to play the conversion game; we print only the code. */ snprintf (strerr, sizeof strerr, "ec=%d", ec); #else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, sizeof strerr - 1, NULL); #endif return strerr; } #ifdef HAVE_W32CE_SYSTEM /* Create a pipe. WRITE_END shall have the opposite value of the one pssed to _assuan_w32ce_prepare_pipe; see there for more details. */ #define GPGCEDEV_IOCTL_MAKE_PIPE \ CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS) static HANDLE w32ce_finish_pipe (int rvid, int write_end) { HANDLE hd; hd = CreateFile (L"GPG1:", write_end? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL); if (hd != INVALID_HANDLE_VALUE) { if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_MAKE_PIPE, &rvid, sizeof rvid, NULL, 0, NULL, NULL)) { DWORD lastrc = GetLastError (); CloseHandle (hd); hd = INVALID_HANDLE_VALUE; SetLastError (lastrc); } } return hd; } #endif /*HAVE_W32CE_SYSTEM*/ /* static HWND */ /* show_window_hierarchy (HWND parent, int level) */ /* { */ /* HWND child; */ /* child = GetWindow (parent, GW_CHILD); */ /* while (child) */ /* { */ /* char buf[1024+1]; */ /* char name[200]; */ /* int nname; */ /* char *pname; */ /* memset (buf, 0, sizeof (buf)); */ /* GetWindowText (child, buf, sizeof (buf)-1); */ /* nname = GetClassName (child, name, sizeof (name)-1); */ /* if (nname) */ /* pname = name; */ /* else */ /* pname = NULL; */ /* fprintf (debugfp, "### %*shwnd=%p (%s) `%s'\n", level*2, "", child, */ /* pname? pname:"", buf); */ /* show_window_hierarchy (child, level+1); */ /* child = GetNextWindow (child, GW_HWNDNEXT); */ /* } */ /* return NULL; */ /* } */ /* Convert a wchar to UTF8. Caller needs to release the string. Returns NULL on error. */ static char * wchar_to_utf8 (const wchar_t *string, size_t len, int secure) { int n; char *result; /* Note, that CP_UTF8 is not defined in Windows versions earlier than NT. */ n = WideCharToMultiByte (CP_UTF8, 0, string, len, NULL, 0, NULL, NULL); if (n < 0) return NULL; result = secure? secmem_malloc (n+1) : malloc (n+1); if (!result) return NULL; n = WideCharToMultiByte (CP_UTF8, 0, string, len, result, n, NULL, NULL); if (n < 0) { if (secure) secmem_free (result); else free (result); return NULL; } return result; } /* Convert a UTF8 string to wchar. Returns NULL on error. Caller needs to free the returned value. */ wchar_t * utf8_to_wchar (const char *string) { int n; wchar_t *result; size_t len = strlen (string); n = MultiByteToWideChar (CP_UTF8, 0, string, len, NULL, 0); if (n < 0) return NULL; result = calloc ((n+1), sizeof *result); if (!result) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, len, result, n); if (n < 0) { free (result); return NULL; } result[n] = 0; return result; } /* Raise the software input panel. */ static void raise_sip (HWND dlg) { #ifdef HAVE_W32CE_SYSTEM SIPINFO si; SetForegroundWindow (dlg); memset (&si, 0, sizeof si); si.cbSize = sizeof si; if (SipGetInfo (&si)) { si.fdwFlags |= SIPF_ON; SipSetInfo (&si); } #else (void)dlg; #endif } /* Center the window CHILDWND with the desktop as its parent window. STYLE is passed as second arg to SetWindowPos.*/ static void center_window (HWND childwnd, HWND style) { #ifndef HAVE_W32CE_SYSTEM HWND parwnd; RECT rchild, rparent; HDC hdc; int wchild, hchild, wparent, hparent; int wscreen, hscreen, xnew, ynew; int flags = SWP_NOSIZE | SWP_NOZORDER; parwnd = GetDesktopWindow (); GetWindowRect (childwnd, &rchild); wchild = rchild.right - rchild.left; hchild = rchild.bottom - rchild.top; GetWindowRect (parwnd, &rparent); wparent = rparent.right - rparent.left; hparent = rparent.bottom - rparent.top; hdc = GetDC (childwnd); wscreen = GetDeviceCaps (hdc, HORZRES); hscreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (childwnd, hdc); xnew = rparent.left + ((wparent - wchild) / 2); if (xnew < 0) xnew = 0; else if ((xnew+wchild) > wscreen) xnew = wscreen - wchild; ynew = rparent.top + ((hparent - hchild) / 2); if (ynew < 0) ynew = 0; else if ((ynew+hchild) > hscreen) ynew = hscreen - hchild; if (style == HWND_TOPMOST || style == HWND_NOTOPMOST) flags = SWP_NOMOVE | SWP_NOSIZE; SetWindowPos (childwnd, style? style : NULL, xnew, ynew, 0, 0, flags); #endif } static void move_mouse_and_click (HWND hwnd) { #ifndef HAVE_W32CE_SYSTEM RECT rect; HDC hdc; int wscreen, hscreen, x, y, normx, normy; INPUT inp[3]; int idx; hdc = GetDC (hwnd); wscreen = GetDeviceCaps (hdc, HORZRES); hscreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (hwnd, hdc); if (wscreen < 10 || hscreen < 10) return; GetWindowRect (hwnd, &rect); x = rect.left; y = rect.bottom; normx = x * (65535 / wscreen); if (normx < 0 || normx > 65535) return; normy = y * (65535 / hscreen); if (normy < 0 || normy > 65535) return; for (idx=0; idx < 3; idx++) memset (&inp[idx], 0, sizeof inp[idx]); idx=0; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dx = normx; inp[idx].mi.dy = normy; inp[idx].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; idx++; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; idx++; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTUP; idx++; if ( (SendInput (idx, inp, sizeof (INPUT)) != idx) && debugfp) fprintf (debugfp, "SendInput failed: %s\n", w32_strerror (-1)); #endif } /* Resize the button so that STRING fits into it. */ static void resize_button (HWND hwnd, const char *string) { if (!hwnd) return; /* FIXME: Need to figure out how to convert dialog coorddnates to screen coordinates and how buttons should be placed. */ /* SetWindowPos (hbutton, NULL, */ /* 10, 180, */ /* strlen (string+2), 14, */ /* (SWP_NOZORDER)); */ } /* Call SetDlgItemTextW with an UTF8 string. */ static void set_dlg_item_text (HWND dlg, int item, const char *string) { if (!string || !*string) SetDlgItemTextW (dlg, item, L""); else { wchar_t *wbuf; wbuf = utf8_to_wchar (string); if (!wbuf) SetDlgItemTextW (dlg, item, L"[out of core]"); else { SetDlgItemTextW (dlg, item, wbuf); free (wbuf); } } } /* Load our butmapped icon from the resource and display it. */ static void set_bitmap (HWND dlg, int item) { HWND hwnd; HBITMAP bitmap; RECT rect; int resid; hwnd = GetDlgItem (dlg, item); if (!hwnd) return; rect.left = 0; rect.top = 0; rect.right = 32; rect.bottom = 32; if (!MapDialogRect (dlg, &rect)) { fprintf (stderr, "MapDialogRect failed: %s\n", w32_strerror (-1)); return; } /* fprintf (stderr, "MapDialogRect: %d/%d\n", rect.right, rect.bottom); */ switch (rect.right) { case 32: resid = IDB_ICON_32; break; case 48: resid = IDB_ICON_48; break; case 64: resid = IDB_ICON_64; break; case 96: resid = IDB_ICON_96; break; default: resid = IDB_ICON_128;break; } bitmap = LoadImage (GetModuleHandle (NULL), MAKEINTRESOURCE (resid), IMAGE_BITMAP, rect.right, rect.bottom, (LR_SHARED | LR_LOADTRANSPARENT | LR_LOADMAP3DCOLORS)); if (!bitmap) { fprintf (stderr, "LoadImage failed: %s\n", w32_strerror (-1)); return; } SendMessage(hwnd, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)bitmap); } /* Dialog processing loop. */ static BOOL CALLBACK dlg_proc (HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam) { static pinentry_t pe; /* static int item; */ /* { */ /* int idx; */ /* for (idx=0; msgcodes[idx].string; idx++) */ /* if (msg == msgcodes[idx].msg) */ /* break; */ /* if (msgcodes[idx].string) */ /* fprintf (debugfp, "received %s\n", msgcodes[idx].string); */ /* else */ /* fprintf (debugfp, "received WM_%u\n", msg); */ /* } */ switch (msg) { case WM_INITDIALOG: dialog_handle = dlg; pe = (pinentry_t)lparam; if (!pe) abort (); set_dlg_item_text (dlg, IDC_PINENT_PROMPT, pe->prompt); set_dlg_item_text (dlg, IDC_PINENT_DESC, pe->description); set_dlg_item_text (dlg, IDC_PINENT_TEXT, ""); set_bitmap (dlg, IDC_PINENT_ICON); if (pe->ok) { set_dlg_item_text (dlg, IDOK, pe->ok); resize_button (GetDlgItem (dlg, IDOK), pe->ok); } if (pe->cancel) { set_dlg_item_text (dlg, IDCANCEL, pe->cancel); resize_button (GetDlgItem (dlg, IDCANCEL), pe->cancel); } if (pe->error) set_dlg_item_text (dlg, IDC_PINENT_ERR, pe->error); if (confirm_mode) { EnableWindow (GetDlgItem (dlg, IDC_PINENT_TEXT), FALSE); SetWindowPos (GetDlgItem (dlg, IDC_PINENT_TEXT), NULL, 0, 0, 0, 0, (SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_HIDEWINDOW)); /* item = IDOK; */ } /* else */ /* item = IDC_PINENT_TEXT; */ center_window (dlg, HWND_TOP); /* Unfortunately we can't use SetForegroundWindow because there is no easy eay to have all the calling processes do an AllowSetForegroundWindow. What we do instead is to bad hack by simulating a click to the Window. */ /* if (SetForegroundWindow (dlg) && lock_set_foreground_window) */ /* { */ /* lock_set_foreground_window (LSFW_LOCK); */ /* } */ /* show_window_hierarchy (GetDesktopWindow (), 0); */ ShowWindow (dlg, SW_SHOW); move_mouse_and_click ( GetDlgItem (dlg, IDC_PINENT_PROMPT) ); raise_sip (dlg); break; case WM_COMMAND: switch (LOWORD (wparam)) { case IDOK: if (confirm_mode) confirm_yes = 1; else ok_button_clicked (dlg, pe); EndDialog (dlg, TRUE); break; case IDCANCEL: pe->result = -1; EndDialog (dlg, FALSE); break; } break; case WM_KEYDOWN: if (wparam == VK_RETURN) { if (confirm_mode) confirm_yes = 1; else ok_button_clicked (dlg, pe); EndDialog (dlg, TRUE); } break; case WM_CTLCOLORSTATIC: if ((HWND)lparam == GetDlgItem (dlg, IDC_PINENT_ERR)) { /* Display the error prompt in red. */ SetTextColor ((HDC)wparam, RGB (255, 0, 0)); SetBkMode ((HDC)wparam, TRANSPARENT); return (BOOL)GetStockObject (NULL_BRUSH); } break; } return FALSE; } /* The okay button has been clicked or the enter enter key in the text field. */ static void ok_button_clicked (HWND dlg, pinentry_t pe) { char *s_utf8; wchar_t *w_buffer; size_t w_buffer_size = 255; unsigned int nchar; pe->locale_err = 1; w_buffer = secmem_malloc ((w_buffer_size + 1) * sizeof *w_buffer); if (!w_buffer) return; nchar = GetDlgItemTextW (dlg, IDC_PINENT_TEXT, w_buffer, w_buffer_size); s_utf8 = wchar_to_utf8 (w_buffer, nchar, 1); secmem_free (w_buffer); if (s_utf8) { passphrase_ok = 1; pinentry_setbufferlen (pe, strlen (s_utf8) + 1); if (pe->pin) strcpy (pe->pin, s_utf8); secmem_free (s_utf8); pe->locale_err = 0; pe->result = pe->pin? strlen (pe->pin) : 0; } } static int w32_cmd_handler (pinentry_t pe) { /* HWND lastwindow = GetForegroundWindow (); */ confirm_mode = !pe->pin; passphrase_ok = confirm_yes = 0; dialog_handle = NULL; DialogBoxParam (GetModuleHandle (NULL), MAKEINTRESOURCE (IDD_PINENT), GetDesktopWindow (), dlg_proc, (LPARAM)pe); if (dialog_handle) { /* if (lock_set_foreground_window) */ /* lock_set_foreground_window (LSFW_UNLOCK); */ /* if (lastwindow) */ /* SetForegroundWindow (lastwindow); */ } else return -1; if (confirm_mode) return confirm_yes; else if (passphrase_ok && pe->pin) return strlen (pe->pin); else return -1; } /* WindowsCE uses a very strange way of handling the standard streams. There is a function SetStdioPath to associate a standard stream with a file or a device but what we really want is to use pipes as standard streams. Despite that we implement pipes using a device, we would have some limitations on the number of open pipes due to the 3 character limit of device file name. Thus we don't take this path. Another option would be to install a file system driver with support for pipes; this would allow us to get rid of the device name length limitation. However, with GnuPG we can get away be redefining the standard streams and passing the handles to be used on the command line. This has also the advantage that it makes creating a process much easier and does not require the SetStdioPath set and restore game. The caller needs to pass the rendezvous ids using up to three options: -&S0=<rvid> -&S1=<rvid> -&S2=<rvid> They are all optional but they must be the first arguments on the command line. Parsing stops as soon as an invalid option is found. These rendezvous ids are then used to finish the pipe creation.*/ #ifdef HAVE_W32CE_SYSTEM static void parse_std_file_handles (int *argcp, char ***argvp) { int argc = *argcp; char **argv = *argvp; const char *s; int fd; int i; int fixup = 0; if (!argc) return; for (argc--, argv++; argc; argc--, argv++) { s = *argv; if (*s == '-' && s[1] == '&' && s[2] == 'S' && (s[3] == '0' || s[3] == '1' || s[3] == '2') && s[4] == '=' && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null"))) { if (s[5] == 'n') fd = (int)(-1); else fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0'); if (s[3] == '0' && fd != -1) w32_infd = fd; else if (s[3] == '1' && fd != -1) w32_outfd = fd; fixup++; } else break; } if (fixup) { argc = *argcp; argc -= fixup; *argcp = argc; argv = *argvp; for (i=1; i < argc; i++) argv[i] = argv[i + fixup]; for (; i < argc + fixup; i++) argv[i] = NULL; } } #endif /*HAVE_W32CE_SYSTEM*/ int main (int argc, char **argv) { #ifndef HAVE_W32CE_SYSTEM void *handle; #endif w32_infd = STDIN_FILENO; w32_outfd = STDOUT_FILENO; #ifdef HAVE_W32CE_SYSTEM parse_std_file_handles (&argc, &argv); #endif pinentry_init (PGMNAME); pinentry_parse_opts (argc, argv); /* debugfp = fopen ("pinentry.log", "w"); */ /* if (!debugfp) */ /* debugfp = stderr; */ /* We need to load a function because that one is only available since W2000 but not in older NTs. */ #ifndef HAVE_W32CE_SYSTEM handle = LoadLibrary ("user32.dll"); if (handle) { void *foo; foo = GetProcAddress (handle, "LockSetForegroundWindow"); if (foo) lock_set_foreground_window = foo; else CloseHandle (handle); } #endif if (pinentry_loop2 (w32_infd, w32_outfd)) return 1; #ifdef HAVE_W32CE_SYSTEM Sleep (400); #endif return 0; }