123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- /*
- * Copyright (c) 2000-2002 Damien Miller. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- /* GTK2 support by Nalin Dahyabhai <nalin@redhat.com> */
- /*
- * This is a simple GNOME SSH passphrase grabber. To use it, set the
- * environment variable SSH_ASKPASS to point to the location of
- * gnome-ssh-askpass before calling "ssh-add < /dev/null".
- *
- * There is only two run-time options: if you set the environment variable
- * "GNOME_SSH_ASKPASS_GRAB_SERVER=true" then gnome-ssh-askpass will grab
- * the X server. If you set "GNOME_SSH_ASKPASS_GRAB_POINTER=true", then the
- * pointer will be grabbed too. These may have some benefit to security if
- * you don't trust your X server. We grab the keyboard always.
- */
- #define GRAB_TRIES 16
- #define GRAB_WAIT 250 /* milliseconds */
- #define PROMPT_ENTRY 0
- #define PROMPT_CONFIRM 1
- #define PROMPT_NONE 2
- /*
- * Compile with:
- *
- * cc -Wall `pkg-config --cflags gtk+-2.0` \
- * gnome-ssh-askpass2.c -o gnome-ssh-askpass \
- * `pkg-config --libs gtk+-2.0`
- *
- */
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <X11/Xlib.h>
- #include <glib.h>
- #include <gtk/gtk.h>
- #include <gdk/gdkx.h>
- #include <gdk/gdkkeysyms.h>
- static void
- report_failed_grab (GtkWidget *parent_window, const char *what)
- {
- GtkWidget *err;
- err = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
- GTK_MESSAGE_ERROR,
- GTK_BUTTONS_CLOSE,
- "SSH password dialog could not grab the %s input.\n"
- "This might be caused by application such as screensaver, "
- "however it could also mean that someone may be eavesdropping "
- "on your session.\n"
- "Either close the application which grabs the %s or "
- "log out and log in again to prevent this from happening.", what, what);
- gtk_window_set_position(GTK_WINDOW(err), GTK_WIN_POS_CENTER);
- gtk_dialog_run(GTK_DIALOG(err));
- gtk_widget_destroy(err);
- }
- static void
- ok_dialog(GtkWidget *entry, gpointer dialog)
- {
- g_return_if_fail(GTK_IS_DIALOG(dialog));
- gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
- }
- static gboolean
- check_none(GtkWidget *widget, GdkEventKey *event, gpointer dialog)
- {
- switch (event->keyval) {
- case GDK_KEY_Escape:
- /* esc -> close dialog */
- gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
- return TRUE;
- case GDK_KEY_Tab:
- /* tab -> focus close button */
- gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(
- dialog, GTK_RESPONSE_CLOSE));
- return TRUE;
- default:
- /* eat all other key events */
- return TRUE;
- }
- }
- static int
- parse_env_hex_color(const char *env, GdkColor *c)
- {
- const char *s;
- unsigned long ul;
- char *ep;
- size_t n;
- if ((s = getenv(env)) == NULL)
- return 0;
- memset(c, 0, sizeof(*c));
- /* Permit hex rgb or rrggbb optionally prefixed by '#' or '0x' */
- if (*s == '#')
- s++;
- else if (strncmp(s, "0x", 2) == 0)
- s += 2;
- n = strlen(s);
- if (n != 3 && n != 6)
- goto bad;
- ul = strtoul(s, &ep, 16);
- if (*ep != '\0' || ul > 0xffffff) {
- bad:
- fprintf(stderr, "Invalid $%s - invalid hex color code\n", env);
- return 0;
- }
- /* Valid hex sequence; expand into a GdkColor */
- if (n == 3) {
- /* 4-bit RGB */
- c->red = ((ul >> 8) & 0xf) << 12;
- c->green = ((ul >> 4) & 0xf) << 12;
- c->blue = (ul & 0xf) << 12;
- } else {
- /* 8-bit RGB */
- c->red = ((ul >> 16) & 0xff) << 8;
- c->green = ((ul >> 8) & 0xff) << 8;
- c->blue = (ul & 0xff) << 8;
- }
- return 1;
- }
- static void
- move_progress(GtkWidget *entry, gpointer progress)
- {
- gdouble step;
- g_return_if_fail(GTK_IS_PROGRESS_BAR(progress));
-
- step = g_random_double_range(0.03, 0.1);
- gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(progress), step);
- gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progress));
- }
- static int
- passphrase_dialog(char *message, int prompt_type)
- {
- const char *failed;
- char *passphrase, *local;
- int result, grab_tries, grab_server, grab_pointer;
- int buttons, default_response;
- GtkWidget *parent_window, *dialog, *entry, *progress, *hbox;
- GdkGrabStatus status;
- GdkColor fg, bg;
- int fg_set = 0, bg_set = 0;
- grab_server = (getenv("GNOME_SSH_ASKPASS_GRAB_SERVER") != NULL);
- grab_pointer = (getenv("GNOME_SSH_ASKPASS_GRAB_POINTER") != NULL);
- grab_tries = 0;
- fg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_FG_COLOR", &fg);
- bg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_BG_COLOR", &bg);
- /* Create an invisible parent window so that GtkDialog doesn't
- * complain. */
- parent_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- switch (prompt_type) {
- case PROMPT_CONFIRM:
- buttons = GTK_BUTTONS_YES_NO;
- default_response = GTK_RESPONSE_YES;
- break;
- case PROMPT_NONE:
- buttons = GTK_BUTTONS_CLOSE;
- default_response = GTK_RESPONSE_CLOSE;
- break;
- default:
- buttons = GTK_BUTTONS_OK_CANCEL;
- default_response = GTK_RESPONSE_OK;
- break;
- }
- dialog = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
- GTK_MESSAGE_QUESTION, buttons, "%s", message);
- gtk_window_set_title(GTK_WINDOW(dialog), "OpenSSH");
- gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
- gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
- gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_response);
- gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
- if (fg_set)
- gtk_widget_modify_fg(dialog, GTK_STATE_NORMAL, &fg);
- if (bg_set)
- gtk_widget_modify_bg(dialog, GTK_STATE_NORMAL, &bg);
- if (prompt_type == PROMPT_ENTRY || prompt_type == PROMPT_NONE) {
- hbox = gtk_hbox_new(FALSE, 0);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE,
- FALSE, 0);
- gtk_widget_show(hbox);
- entry = gtk_entry_new();
- if (fg_set)
- gtk_widget_modify_fg(entry, GTK_STATE_NORMAL, &fg);
- if (bg_set)
- gtk_widget_modify_bg(entry, GTK_STATE_NORMAL, &bg);
- gtk_box_pack_start(
- GTK_BOX(hbox), entry, TRUE, FALSE, 0);
- gtk_entry_set_width_chars(GTK_ENTRY(entry), 2);
- gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
- gtk_widget_grab_focus(entry);
- if (prompt_type == PROMPT_ENTRY) {
- gtk_widget_show(entry);
- /* Make <enter> close dialog */
- g_signal_connect(G_OBJECT(entry), "activate",
- G_CALLBACK(ok_dialog), dialog);
- } else {
- /*
- * Ensure the 'close' button is not focused by default
- * but is still reachable via tab. This is a bit of a
- * hack - it uses a hidden entry that responds to a
- * couple of keypress events (escape and tab only).
- */
- gtk_widget_realize(entry);
- g_signal_connect(G_OBJECT(entry), "key_press_event",
- G_CALLBACK(check_none), dialog);
- }
- hbox = gtk_hbox_new(FALSE, 0);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
- hbox, FALSE, FALSE, 8);
- gtk_widget_show(hbox);
- progress = gtk_progress_bar_new();
- gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress),
- "Passphrase length hidden intentionally");
- gtk_box_pack_start(GTK_BOX(hbox), progress, TRUE,
- TRUE, 5);
- gtk_widget_show(progress);
- g_signal_connect(G_OBJECT(entry), "changed",
- G_CALLBACK(move_progress), progress);
- }
- /* Grab focus */
- gtk_widget_show_now(dialog);
- if (grab_pointer) {
- for(;;) {
- status = gdk_pointer_grab(
- (gtk_widget_get_window(GTK_WIDGET(dialog))), TRUE,
- 0, NULL, NULL, GDK_CURRENT_TIME);
- if (status == GDK_GRAB_SUCCESS)
- break;
- usleep(GRAB_WAIT * 1000);
- if (++grab_tries > GRAB_TRIES) {
- failed = "mouse";
- goto nograb;
- }
- }
- }
- for(;;) {
- status = gdk_keyboard_grab(
- gtk_widget_get_window(GTK_WIDGET(dialog)), FALSE,
- GDK_CURRENT_TIME);
- if (status == GDK_GRAB_SUCCESS)
- break;
- usleep(GRAB_WAIT * 1000);
- if (++grab_tries > GRAB_TRIES) {
- failed = "keyboard";
- goto nograbkb;
- }
- }
- if (grab_server) {
- gdk_x11_grab_server();
- }
- result = gtk_dialog_run(GTK_DIALOG(dialog));
- /* Ungrab */
- if (grab_server)
- XUngrabServer(gdk_x11_get_default_xdisplay());
- if (grab_pointer)
- gdk_pointer_ungrab(GDK_CURRENT_TIME);
- gdk_keyboard_ungrab(GDK_CURRENT_TIME);
- gdk_flush();
- /* Report passphrase if user selected OK */
- if (prompt_type == PROMPT_ENTRY) {
- passphrase = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
- if (result == GTK_RESPONSE_OK) {
- local = g_locale_from_utf8(passphrase,
- strlen(passphrase), NULL, NULL, NULL);
- if (local != NULL) {
- puts(local);
- memset(local, '\0', strlen(local));
- g_free(local);
- } else {
- puts(passphrase);
- }
- }
- /* Zero passphrase in memory */
- memset(passphrase, '\b', strlen(passphrase));
- gtk_entry_set_text(GTK_ENTRY(entry), passphrase);
- memset(passphrase, '\0', strlen(passphrase));
- g_free(passphrase);
- }
- gtk_widget_destroy(dialog);
- if (result != GTK_RESPONSE_OK && result != GTK_RESPONSE_YES)
- return -1;
- return 0;
- nograbkb:
- /*
- * At least one grab failed - ungrab what we got, and report
- * the failure to the user. Note that XGrabServer() cannot
- * fail.
- */
- gdk_pointer_ungrab(GDK_CURRENT_TIME);
- nograb:
- if (grab_server)
- XUngrabServer(gdk_x11_get_default_xdisplay());
- gtk_widget_destroy(dialog);
-
- report_failed_grab(parent_window, failed);
- return (-1);
- }
- int
- main(int argc, char **argv)
- {
- char *message, *prompt_mode;
- int result, prompt_type = PROMPT_ENTRY;
- gtk_init(&argc, &argv);
- gtk_window_set_default_icon_from_file ("/usr/share/pixmaps/ssh-askpass-gnome.png", NULL);
- if (argc > 1) {
- message = g_strjoinv(" ", argv + 1);
- } else {
- message = g_strdup("Enter your OpenSSH passphrase:");
- }
- if ((prompt_mode = getenv("SSH_ASKPASS_PROMPT")) != NULL) {
- if (strcasecmp(prompt_mode, "confirm") == 0)
- prompt_type = PROMPT_CONFIRM;
- else if (strcasecmp(prompt_mode, "none") == 0)
- prompt_type = PROMPT_NONE;
- }
- setvbuf(stdout, 0, _IONBF, 0);
- result = passphrase_dialog(message, prompt_type);
- g_free(message);
- return (result);
- }
|