10SN/A/* 29330SN/A * Copyright (c) 2000-2002 Damien Miller. All rights reserved. 30SN/A * 40SN/A * Redistribution and use in source and binary forms, with or without 50SN/A * modification, are permitted provided that the following conditions 60SN/A * are met: 72362SN/A * 1. Redistributions of source code must retain the above copyright 80SN/A * notice, this list of conditions and the following disclaimer. 92362SN/A * 2. Redistributions in binary form must reproduce the above copyright 100SN/A * notice, this list of conditions and the following disclaimer in the 110SN/A * documentation and/or other materials provided with the distribution. 120SN/A * 130SN/A * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 140SN/A * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 150SN/A * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 160SN/A * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 170SN/A * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 180SN/A * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 190SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 200SN/A * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 212362SN/A * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 222362SN/A * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 232362SN/A */ 240SN/A 250SN/A/* GTK2 support by Nalin Dahyabhai <nalin@redhat.com> */ 260SN/A 270SN/A/* 280SN/A * This is a simple GNOME SSH passphrase grabber. To use it, set the 290SN/A * environment variable SSH_ASKPASS to point to the location of 300SN/A * gnome-ssh-askpass before calling "ssh-add < /dev/null". 310SN/A * 320SN/A * There is only two run-time options: if you set the environment variable 330SN/A * "GNOME_SSH_ASKPASS_GRAB_SERVER=true" then gnome-ssh-askpass will grab 340SN/A * the X server. If you set "GNOME_SSH_ASKPASS_GRAB_POINTER=true", then the 350SN/A * pointer will be grabbed too. These may have some benefit to security if 360SN/A * you don't trust your X server. We grab the keyboard always. 370SN/A */ 380SN/A 390SN/A#define GRAB_TRIES 16 400SN/A#define GRAB_WAIT 250 /* milliseconds */ 410SN/A 420SN/A#define PROMPT_ENTRY 0 430SN/A#define PROMPT_CONFIRM 1 440SN/A#define PROMPT_NONE 2 450SN/A 460SN/A/* 470SN/A * Compile with: 480SN/A * 490SN/A * cc -Wall `pkg-config --cflags gtk+-2.0` \ 500SN/A * gnome-ssh-askpass2.c -o gnome-ssh-askpass \ 510SN/A * `pkg-config --libs gtk+-2.0` 520SN/A * 530SN/A */ 540SN/A 550SN/A#include <stdlib.h> 560SN/A#include <stdio.h> 570SN/A#include <string.h> 580SN/A#include <unistd.h> 590SN/A 600SN/A#include <X11/Xlib.h> 610SN/A#include <gtk/gtk.h> 620SN/A#include <gdk/gdkx.h> 630SN/A#include <gdk/gdkkeysyms.h> 640SN/A 650SN/Astatic void 660SN/Aok_dialog(GtkWidget *entry, gpointer dialog) 670SN/A{ 680SN/A g_return_if_fail(GTK_IS_DIALOG(dialog)); 690SN/A gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); 700SN/A} 710SN/A 720SN/Astatic gboolean 730SN/Acheck_none(GtkWidget *widget, GdkEventKey *event, gpointer dialog) 740SN/A{ 750SN/A switch (event->keyval) { 760SN/A case GDK_KEY_Escape: 770SN/A /* esc -> close dialog */ 780SN/A gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE); 790SN/A return TRUE; 800SN/A case GDK_KEY_Tab: 810SN/A /* tab -> focus close button */ 820SN/A gtk_widget_grab_focus(gtk_dialog_get_widget_for_response( 830SN/A dialog, GTK_RESPONSE_CLOSE)); 840SN/A return TRUE; 850SN/A default: 860SN/A /* eat all other key events */ 870SN/A return TRUE; 880SN/A } 890SN/A} 900SN/A 910SN/Astatic int 920SN/Aparse_env_hex_color(const char *env, GdkColor *c) 930SN/A{ 940SN/A const char *s; 950SN/A unsigned long ul; 960SN/A char *ep; 970SN/A size_t n; 980SN/A 990SN/A if ((s = getenv(env)) == NULL) 1000SN/A return 0; 1010SN/A 1020SN/A memset(c, 0, sizeof(*c)); 1030SN/A 1040SN/A /* Permit hex rgb or rrggbb optionally prefixed by '#' or '0x' */ 1050SN/A if (*s == '#') 1060SN/A s++; 1070SN/A else if (strncmp(s, "0x", 2) == 0) 1080SN/A s += 2; 1090SN/A n = strlen(s); 1100SN/A if (n != 3 && n != 6) 1110SN/A goto bad; 1120SN/A ul = strtoul(s, &ep, 16); 1130SN/A if (*ep != '\0' || ul > 0xffffff) { 1140SN/A bad: 1150SN/A fprintf(stderr, "Invalid $%s - invalid hex color code\n", env); 1160SN/A return 0; 1170SN/A } 1180SN/A /* Valid hex sequence; expand into a GdkColor */ 1190SN/A if (n == 3) { 12011882Savstepan /* 4-bit RGB */ 1210SN/A c->red = ((ul >> 8) & 0xf) << 12; 1220SN/A c->green = ((ul >> 4) & 0xf) << 12; 1230SN/A c->blue = (ul & 0xf) << 12; 1240SN/A } else { 1250SN/A /* 8-bit RGB */ 1260SN/A c->red = ((ul >> 16) & 0xff) << 8; 1270SN/A c->green = ((ul >> 8) & 0xff) << 8; 1280SN/A c->blue = (ul & 0xff) << 8; 1290SN/A } 1300SN/A return 1; 1310SN/A} 1320SN/A 1330SN/Astatic int 1340SN/Apassphrase_dialog(char *message, int prompt_type) 1350SN/A{ 1360SN/A const char *failed; 1370SN/A char *passphrase, *local; 1380SN/A int result, grab_tries, grab_server, grab_pointer; 1390SN/A int buttons, default_response; 1400SN/A GtkWidget *parent_window, *dialog, *entry, *err; 1410SN/A GdkGrabStatus status; 1420SN/A GdkColor fg, bg; 1430SN/A GdkSeat *seat; 1440SN/A GdkDisplay *display; 1450SN/A GdkSeatCapabilities caps; 1460SN/A int fg_set = 0, bg_set = 0; 1470SN/A 1480SN/A grab_server = (getenv("GNOME_SSH_ASKPASS_GRAB_SERVER") != NULL); 1490SN/A grab_pointer = (getenv("GNOME_SSH_ASKPASS_GRAB_POINTER") != NULL); 1500SN/A grab_tries = 0; 1510SN/A 1524487SN/A fg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_FG_COLOR", &fg); 1530SN/A bg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_BG_COLOR", &bg); 1540SN/A 1550SN/A /* Create an invisible parent window so that GtkDialog doesn't 1560SN/A * complain. */ 1570SN/A parent_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 1580SN/A 1590SN/A switch (prompt_type) { 1600SN/A case PROMPT_CONFIRM: 1610SN/A buttons = GTK_BUTTONS_YES_NO; 1620SN/A default_response = GTK_RESPONSE_YES; 1630SN/A break; 1640SN/A case PROMPT_NONE: 1650SN/A buttons = GTK_BUTTONS_CLOSE; 1660SN/A default_response = GTK_RESPONSE_CLOSE; 1670SN/A break; 1688565SN/A default: 1690SN/A buttons = GTK_BUTTONS_OK_CANCEL; 1700SN/A default_response = GTK_RESPONSE_OK; 1710SN/A break; 1720SN/A } 1730SN/A 1740SN/A dialog = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0, 1750SN/A GTK_MESSAGE_QUESTION, buttons, "%s", message); 1760SN/A 1770SN/A gtk_window_set_title(GTK_WINDOW(dialog), "OpenSSH"); 1780SN/A gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); 1790SN/A gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE); 1800SN/A gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_response); 1810SN/A gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE); 1820SN/A 1830SN/A if (fg_set) 1844487SN/A gtk_widget_modify_fg(dialog, GTK_STATE_NORMAL, &fg); 1850SN/A if (bg_set) 1860SN/A gtk_widget_modify_bg(dialog, GTK_STATE_NORMAL, &bg); 1870SN/A 1880SN/A if (prompt_type == PROMPT_ENTRY || prompt_type == PROMPT_NONE) { 1890SN/A entry = gtk_entry_new(); 1900SN/A if (fg_set) 1910SN/A gtk_widget_modify_fg(entry, GTK_STATE_NORMAL, &fg); 1920SN/A if (bg_set) 1930SN/A gtk_widget_modify_bg(entry, GTK_STATE_NORMAL, &bg); 1940SN/A gtk_box_pack_start( 1950SN/A GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), 1960SN/A entry, FALSE, FALSE, 0); 1970SN/A gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); 1980SN/A gtk_widget_grab_focus(entry); 1990SN/A if (prompt_type == PROMPT_ENTRY) { 2000SN/A gtk_widget_show(entry); 2010SN/A /* Make <enter> close dialog */ 2020SN/A g_signal_connect(G_OBJECT(entry), "activate", 2034487SN/A G_CALLBACK(ok_dialog), dialog); 2040SN/A } else { 2050SN/A /* 2060SN/A * Ensure the 'close' button is not focused by default 2070SN/A * but is still reachable via tab. This is a bit of a 2080SN/A * hack - it uses a hidden entry that responds to a 2094487SN/A * couple of keypress events (escape and tab only). 2100SN/A */ 2110SN/A gtk_widget_realize(entry); 2120SN/A g_signal_connect(G_OBJECT(entry), "key_press_event", 2130SN/A G_CALLBACK(check_none), dialog); 2140SN/A } 2154487SN/A } 2160SN/A /* Grab focus */ 2170SN/A gtk_widget_show_now(dialog); 2180SN/A display = gtk_widget_get_display(GTK_WIDGET(dialog)); 2190SN/A seat = gdk_display_get_default_seat(display); 2200SN/A caps = GDK_SEAT_CAPABILITY_KEYBOARD; 2214487SN/A if (grab_pointer) 2220SN/A caps |= GDK_SEAT_CAPABILITY_ALL_POINTING; 2230SN/A if (grab_server) 2240SN/A caps = GDK_SEAT_CAPABILITY_ALL; 2250SN/A for (;;) { 2260SN/A status = gdk_seat_grab(seat, gtk_widget_get_window(dialog), 2270SN/A caps, TRUE, NULL, NULL, NULL, NULL); 2280SN/A if (status == GDK_GRAB_SUCCESS) 2290SN/A break; 2300SN/A usleep(GRAB_WAIT * 1000); 2310SN/A if (++grab_tries > GRAB_TRIES) 2320SN/A goto nograb; 2330SN/A } 2344487SN/A 2350SN/A result = gtk_dialog_run(GTK_DIALOG(dialog)); 2360SN/A 2370SN/A /* Ungrab */ 2380SN/A gdk_seat_ungrab(seat); 2390SN/A gdk_display_flush(display); 2400SN/A 2414487SN/A /* Report passphrase if user selected OK */ 2420SN/A if (prompt_type == PROMPT_ENTRY) { 2430SN/A passphrase = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry))); 2440SN/A if (result == GTK_RESPONSE_OK) { 2450SN/A local = g_locale_from_utf8(passphrase, 2460SN/A strlen(passphrase), NULL, NULL, NULL); 2470SN/A if (local != NULL) { 2480SN/A puts(local); 2490SN/A memset(local, '\0', strlen(local)); 2500SN/A g_free(local); 2510SN/A } else { 2524487SN/A puts(passphrase); 2534487SN/A } 2540SN/A } 2550SN/A /* Zero passphrase in memory */ 2560SN/A memset(passphrase, '\b', strlen(passphrase)); 2570SN/A gtk_entry_set_text(GTK_ENTRY(entry), passphrase); 2580SN/A memset(passphrase, '\0', strlen(passphrase)); 2590SN/A g_free(passphrase); 2600SN/A } 2610SN/A 2620SN/A gtk_widget_destroy(dialog); 2630SN/A if (result != GTK_RESPONSE_OK && result != GTK_RESPONSE_YES) 2640SN/A return -1; 265 return 0; 266 267 nograb: 268 gtk_widget_destroy(dialog); 269 err = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0, 270 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 271 "Could not grab input. A malicious client may be eavesdropping " 272 "on your session."); 273 gtk_window_set_position(GTK_WINDOW(err), GTK_WIN_POS_CENTER); 274 gtk_dialog_run(GTK_DIALOG(err)); 275 gtk_widget_destroy(err); 276 return -1; 277} 278 279int 280main(int argc, char **argv) 281{ 282 char *message, *prompt_mode; 283 int result, prompt_type = PROMPT_ENTRY; 284 285 gtk_init(&argc, &argv); 286 287 if (argc > 1) { 288 message = g_strjoinv(" ", argv + 1); 289 } else { 290 message = g_strdup("Enter your OpenSSH passphrase:"); 291 } 292 293 if ((prompt_mode = getenv("SSH_ASKPASS_PROMPT")) != NULL) { 294 if (strcasecmp(prompt_mode, "confirm") == 0) 295 prompt_type = PROMPT_CONFIRM; 296 else if (strcasecmp(prompt_mode, "none") == 0) 297 prompt_type = PROMPT_NONE; 298 } 299 300 setvbuf(stdout, 0, _IONBF, 0); 301 result = passphrase_dialog(message, prompt_type); 302 g_free(message); 303 304 return (result); 305} 306