Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'windows/pageant.c')
-rw-r--r--windows/pageant.c1958
1 files changed, 1958 insertions, 0 deletions
diff --git a/windows/pageant.c b/windows/pageant.c
new file mode 100644
index 00000000..d1368903
--- /dev/null
+++ b/windows/pageant.c
@@ -0,0 +1,1958 @@
+/*
+ * Pageant: the PuTTY Authentication Agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <assert.h>
+#include <tchar.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "tree234.h"
+#include "security-api.h"
+#include "cryptoapi.h"
+#include "pageant.h"
+#include "licence.h"
+#include "pageant-rc.h"
+
+#include <shellapi.h>
+
+#include <aclapi.h>
+#ifdef DEBUG_IPC
+#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
+#include <sddl.h>
+#endif
+
+#define WM_SYSTRAY (WM_APP + 6)
+#define WM_SYSTRAY2 (WM_APP + 7)
+
+#define APPNAME "Pageant"
+
+/* Titles and class names for invisible windows. IPCWINTITLE and
+ * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA
+ * based Pageant clients will call FindWindow with those parameters
+ * and expect to find the Pageant IPC receiver. */
+#define TRAYWINTITLE "Pageant"
+#define TRAYCLASSNAME "PageantSysTray"
+#define IPCWINTITLE "Pageant"
+#define IPCCLASSNAME "Pageant"
+
+static HWND traywindow;
+static HWND keylist;
+static HWND aboutbox;
+static HMENU systray_menu, session_menu;
+static bool already_running;
+static FingerprintType fptype = SSH_FPTYPE_DEFAULT;
+
+static char *putty_path;
+static bool restrict_putty_acl = false;
+
+/* CWD for "add key" file requester. */
+static filereq *keypath = NULL;
+
+/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
+ * wParam are used by Windows, and should be masked off, so we shouldn't
+ * attempt to store information in them. Hence all these identifiers have
+ * the low 4 bits clear. Also, identifiers should < 0xF000. */
+
+#define IDM_CLOSE 0x0010
+#define IDM_VIEWKEYS 0x0020
+#define IDM_ADDKEY 0x0030
+#define IDM_ADDKEY_ENCRYPTED 0x0040
+#define IDM_REMOVE_ALL 0x0050
+#define IDM_REENCRYPT_ALL 0x0060
+#define IDM_HELP 0x0070
+#define IDM_ABOUT 0x0080
+#define IDM_PUTTY 0x0090
+#define IDM_SESSIONS_BASE 0x1000
+#define IDM_SESSIONS_MAX 0x2000
+#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
+#define PUTTY_DEFAULT "Default%20Settings"
+static int initial_menuitems_count;
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(traywindow, buf, "Pageant Fatal Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(buf);
+ exit(1);
+}
+
+struct PassphraseProcStruct {
+ bool modal;
+ const char *help_topic;
+ PageantClientDialogId *dlgid;
+ char *passphrase;
+ const char *comment;
+};
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetDlgItemText(hwnd, IDC_LICENCE_TEXTBOX, LICENCE_TEXT("\r\n\r\n"));
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG: {
+ char *buildinfo_text = buildinfo("\r\n");
+ char *text = dupprintf(
+ "Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
+ ver, buildinfo_text,
+ "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
+ sfree(buildinfo_text);
+ SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text);
+ MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX);
+ sfree(text);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case IDC_ABOUT_LICENCE:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCE), hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ case IDC_ABOUT_WEBSITE:
+ /* Load web browser */
+ ShellExecute(hwnd, "open",
+ "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
+ 0, 0, SW_SHOWDEFAULT);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+static HWND modal_passphrase_hwnd = NULL;
+static HWND nonmodal_passphrase_hwnd = NULL;
+
+static void end_passphrase_dialog(HWND hwnd, INT_PTR result)
+{
+ struct PassphraseProcStruct *p = (struct PassphraseProcStruct *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ if (p->modal) {
+ EndDialog(hwnd, result);
+ } else {
+ /*
+ * Destroy this passphrase dialog box before passing the
+ * results back to the main pageant.c, to avoid re-entrancy
+ * issues.
+ *
+ * If we successfully got a passphrase from the user, but it
+ * was _wrong_, then pageant_passphrase_request_success will
+ * respond by calling back - synchronously - to our
+ * ask_passphrase() implementation, which will expect the
+ * previous value of nonmodal_passphrase_hwnd to have already
+ * been cleaned up.
+ */
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) NULL);
+ DestroyWindow(hwnd);
+ nonmodal_passphrase_hwnd = NULL;
+
+ if (result)
+ pageant_passphrase_request_success(
+ p->dlgid, ptrlen_from_asciz(p->passphrase));
+ else
+ pageant_passphrase_request_refused(p->dlgid);
+
+ burnstr(p->passphrase);
+ sfree(p);
+ }
+}
+
+/*
+ * Dialog-box function for the passphrase box.
+ */
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct PassphraseProcStruct *p;
+
+ if (msg == WM_INITDIALOG) {
+ p = (struct PassphraseProcStruct *) lParam;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) p);
+ } else {
+ p = (struct PassphraseProcStruct *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ }
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ if (p->modal)
+ modal_passphrase_hwnd = hwnd;
+
+ /*
+ * Centre the window.
+ */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+
+ SetForegroundWindow(hwnd);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ if (!p->modal)
+ SetActiveWindow(hwnd); /* this won't have happened automatically */
+ if (p->comment)
+ SetDlgItemText(hwnd, IDC_PASSPHRASE_FINGERPRINT, p->comment);
+ burnstr(p->passphrase);
+ p->passphrase = dupstr("");
+ SetDlgItemText(hwnd, IDC_PASSPHRASE_EDITBOX, p->passphrase);
+ if (!p->help_topic || !has_help()) {
+ HWND item = GetDlgItem(hwnd, IDHELP);
+ if (item)
+ DestroyWindow(item);
+ }
+ return 0;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ if (p->passphrase)
+ end_passphrase_dialog(hwnd, 1);
+ else
+ MessageBeep(0);
+ return 0;
+ case IDCANCEL:
+ end_passphrase_dialog(hwnd, 0);
+ return 0;
+ case IDHELP:
+ if (p->help_topic)
+ launch_help(hwnd, p->help_topic);
+ return 0;
+ case IDC_PASSPHRASE_EDITBOX:
+ if ((HIWORD(wParam) == EN_CHANGE) && p->passphrase) {
+ burnstr(p->passphrase);
+ p->passphrase = GetDlgItemText_alloc(
+ hwnd, IDC_PASSPHRASE_EDITBOX);
+ }
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ end_passphrase_dialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "PuTTY Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "You can perform this conversion by loading the key\n"
+ "into PuTTYgen and then saving it again.";
+
+ MessageBox(NULL, message, mbtitle, MB_OK);
+}
+
+struct keylist_update_ctx {
+ HDC hdc;
+ int algbitswidth, algwidth, bitswidth, hashwidth;
+ bool enable_remove_controls;
+ bool enable_reencrypt_controls;
+};
+
+struct keylist_display_data {
+ strbuf *alg, *bits, *hash, *comment, *info;
+};
+
+static void keylist_update_callback(
+ void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags,
+ struct pageant_pubkey *key)
+{
+ struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx;
+ FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype);
+ ptrlen fingerprint = ptrlen_from_asciz(fingerprints[this_type]);
+
+ struct keylist_display_data *disp = snew(struct keylist_display_data);
+ disp->alg = strbuf_new();
+ disp->bits = strbuf_new();
+ disp->hash = strbuf_new();
+ disp->comment = strbuf_new();
+ disp->info = strbuf_new();
+
+ /* There is at least one key, so the controls for removing keys
+ * should be enabled */
+ ctx->enable_remove_controls = true;
+
+ switch (key->ssh_version) {
+ case 1: {
+ /*
+ * Expect the fingerprint to contain two words: bit count and
+ * hash.
+ */
+ put_dataz(disp->alg, "SSH-1");
+ put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " "));
+ put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " "));
+ break;
+ }
+
+ case 2: {
+ /*
+ * Expect the fingerprint to contain three words: algorithm
+ * name, bit count, hash.
+ */
+ const ssh_keyalg *alg = pubkey_blob_to_alg(
+ ptrlen_from_strbuf(key->blob));
+
+ ptrlen keytype_word = ptrlen_get_word(&fingerprint, " ");
+ if (alg) {
+ /* Use our own human-legible algorithm names if available,
+ * because they fit better in the space. (Certificate key
+ * algorithm names in particular are terribly long.) */
+ char *alg_desc = ssh_keyalg_desc(alg);
+ put_dataz(disp->alg, alg_desc);
+ sfree(alg_desc);
+ } else {
+ put_datapl(disp->alg, keytype_word);
+ }
+
+ ptrlen bits_word = ptrlen_get_word(&fingerprint, " ");
+ if (alg && ssh_keyalg_variable_size(alg))
+ put_datapl(disp->bits, bits_word);
+
+ put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " "));
+ }
+ }
+
+ put_dataz(disp->comment, comment);
+
+ SIZE sz;
+ if (disp->bits->len) {
+ GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz);
+ if (ctx->algwidth < sz.cx) ctx->algwidth = sz.cx;
+ GetTextExtentPoint32(ctx->hdc, disp->bits->s, disp->bits->len, &sz);
+ if (ctx->bitswidth < sz.cx) ctx->bitswidth = sz.cx;
+ } else {
+ GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz);
+ if (ctx->algbitswidth < sz.cx) ctx->algbitswidth = sz.cx;
+ }
+ GetTextExtentPoint32(ctx->hdc, disp->hash->s, disp->hash->len, &sz);
+ if (ctx->hashwidth < sz.cx) ctx->hashwidth = sz.cx;
+
+ if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) {
+ put_fmt(disp->info, "(encrypted)");
+ } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) {
+ put_fmt(disp->info, "(re-encryptable)");
+
+ /* At least one key can be re-encrypted */
+ ctx->enable_reencrypt_controls = true;
+ }
+
+ /* This list box is owner-drawn but doesn't have LBS_HASSTRINGS,
+ * so we can use LB_ADDSTRING to hand the list box our display
+ * info pointer */
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_ADDSTRING, 0, (LPARAM)disp);
+}
+
+/* Column start positions for the list box, in pixels (not dialog units). */
+static int colpos_bits, colpos_hash, colpos_comment;
+
+/*
+ * Update the visible key list.
+ */
+void keylist_update(void)
+{
+ if (keylist) {
+ /*
+ * Clear the previous list box content and free their display
+ * structures.
+ */
+ {
+ int nitems = SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_GETCOUNT, 0, 0);
+ for (int i = 0; i < nitems; i++) {
+ struct keylist_display_data *disp =
+ (struct keylist_display_data *)SendDlgItemMessage(
+ keylist, IDC_KEYLIST_LISTBOX, LB_GETITEMDATA, i, 0);
+ strbuf_free(disp->alg);
+ strbuf_free(disp->bits);
+ strbuf_free(disp->hash);
+ strbuf_free(disp->comment);
+ strbuf_free(disp->info);
+ sfree(disp);
+ }
+ }
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_RESETCONTENT, 0, 0);
+
+ char *errmsg;
+ struct keylist_update_ctx ctx[1];
+ ctx->enable_remove_controls = false;
+ ctx->enable_reencrypt_controls = false;
+ ctx->algbitswidth = ctx->algwidth = 0;
+ ctx->bitswidth = ctx->hashwidth = 0;
+ ctx->hdc = GetDC(keylist);
+ SelectObject(ctx->hdc, (HFONT)SendMessage(keylist, WM_GETFONT, 0, 0));
+ int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg);
+
+ SIZE sz;
+ GetTextExtentPoint32(ctx->hdc, "MM", 2, &sz);
+ int gutter = sz.cx;
+
+ DeleteDC(ctx->hdc);
+ colpos_hash = ctx->algwidth + ctx->bitswidth + 2*gutter;
+ if (colpos_hash < ctx->algbitswidth + gutter)
+ colpos_hash = ctx->algbitswidth + gutter;
+ colpos_bits = colpos_hash - ctx->bitswidth - gutter;
+ colpos_comment = colpos_hash + ctx->hashwidth + gutter;
+ assert(status == PAGEANT_ACTION_OK);
+ assert(!errmsg);
+
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_SETCURSEL, (WPARAM) - 1, 0);
+
+ EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REMOVE),
+ ctx->enable_remove_controls);
+ EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REENCRYPT),
+ ctx->enable_reencrypt_controls);
+ }
+}
+
+static void win_add_keyfile(Filename *filename, bool encrypted)
+{
+ char *err;
+ int ret;
+
+ /*
+ * Try loading the key without a passphrase. (Or rather, without a
+ * _new_ passphrase; pageant_add_keyfile will take care of trying
+ * all the passphrases we've already stored.)
+ */
+ ret = pageant_add_keyfile(filename, NULL, &err, encrypted);
+ if (ret == PAGEANT_ACTION_OK) {
+ goto done;
+ } else if (ret == PAGEANT_ACTION_FAILURE) {
+ goto error;
+ }
+
+ /*
+ * OK, a passphrase is needed, and we've been given the key
+ * comment to use in the passphrase prompt.
+ */
+ while (1) {
+ INT_PTR dlgret;
+ struct PassphraseProcStruct pps;
+ pps.modal = true;
+ pps.help_topic = NULL; /* this dialog has no help button */
+ pps.dlgid = NULL;
+ pps.passphrase = NULL;
+ pps.comment = err;
+ dlgret = DialogBoxParam(
+ hinst, MAKEINTRESOURCE(IDD_LOAD_PASSPHRASE),
+ NULL, PassphraseProc, (LPARAM) &pps);
+ modal_passphrase_hwnd = NULL;
+
+ if (!dlgret) {
+ burnstr(pps.passphrase);
+ goto done; /* operation cancelled */
+ }
+
+ sfree(err);
+
+ assert(pps.passphrase != NULL);
+
+ ret = pageant_add_keyfile(filename, pps.passphrase, &err, false);
+ burnstr(pps.passphrase);
+
+ if (ret == PAGEANT_ACTION_OK) {
+ goto done;
+ } else if (ret == PAGEANT_ACTION_FAILURE) {
+ goto error;
+ }
+ }
+
+ error:
+ message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ done:
+ sfree(err);
+ return;
+}
+
+/*
+ * Prompt for a key file to add, and add it.
+ */
+static void prompt_add_keyfile(bool encrypted)
+{
+ OPENFILENAME of;
+ char *filelist = snewn(8192, char);
+
+ if (!keypath) keypath = filereq_new();
+ memset(&of, 0, sizeof(of));
+ of.hwndOwner = traywindow;
+ of.lpstrFilter = FILTER_KEY_FILES;
+ of.lpstrCustomFilter = NULL;
+ of.nFilterIndex = 1;
+ of.lpstrFile = filelist;
+ *filelist = '\0';
+ of.nMaxFile = 8192;
+ of.lpstrFileTitle = NULL;
+ of.lpstrTitle = "Select Private Key File";
+ of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
+ if (request_file(keypath, &of, true, false)) {
+ if(strlen(filelist) > of.nFileOffset) {
+ /* Only one filename returned? */
+ Filename *fn = filename_from_str(filelist);
+ win_add_keyfile(fn, encrypted);
+ filename_free(fn);
+ } else {
+ /* we are returned a bunch of strings, end to
+ * end. first string is the directory, the
+ * rest the filenames. terminated with an
+ * empty string.
+ */
+ char *dir = filelist;
+ char *filewalker = filelist + strlen(dir) + 1;
+ while (*filewalker != '\0') {
+ char *filename = dupcat(dir, "\\", filewalker);
+ Filename *fn = filename_from_str(filename);
+ win_add_keyfile(fn, encrypted);
+ filename_free(fn);
+ sfree(filename);
+ filewalker += strlen(filewalker) + 1;
+ }
+ }
+
+ keylist_update();
+ pageant_forget_passphrases();
+ }
+ sfree(filelist);
+}
+
+/*
+ * Dialog-box function for the key list box.
+ */
+static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ static const struct {
+ const char *name;
+ FingerprintType value;
+ } fptypes[] = {
+ {"SHA256", SSH_FPTYPE_SHA256},
+ {"MD5", SSH_FPTYPE_MD5},
+ {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT},
+ {"MD5 including certificate", SSH_FPTYPE_MD5_CERT},
+ };
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ /*
+ * Centre the window.
+ */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP);
+ if (item)
+ DestroyWindow(item);
+ }
+
+ keylist = hwnd;
+
+ int selection = 0;
+ for (size_t i = 0; i < lenof(fptypes); i++) {
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, CB_ADDSTRING,
+ 0, (LPARAM)fptypes[i].name);
+ if (fptype == fptypes[i].value)
+ selection = (int)i;
+ }
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE,
+ CB_SETCURSEL, 0, selection);
+
+ keylist_update();
+ return 0;
+ }
+ case WM_MEASUREITEM: {
+ assert(wParam == IDC_KEYLIST_LISTBOX);
+
+ MEASUREITEMSTRUCT *mi = (MEASUREITEMSTRUCT *)lParam;
+
+ /*
+ * Our list box is owner-drawn, but we put normal text in it.
+ * So the line height is the same as it would normally be,
+ * which is 8 dialog units.
+ */
+ RECT r;
+ r.left = r.right = r.top = 0;
+ r.bottom = 8;
+ MapDialogRect(hwnd, &r);
+ mi->itemHeight = r.bottom;
+
+ return 0;
+ }
+ case WM_DRAWITEM: {
+ assert(wParam == IDC_KEYLIST_LISTBOX);
+
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+
+ if (di->itemAction == ODA_FOCUS) {
+ /* Just toggle the focus rectangle either on or off. This
+ * is an XOR-type function, so it's the same call in
+ * either case. */
+ DrawFocusRect(di->hDC, &di->rcItem);
+ } else {
+ /* Draw the full text. */
+ bool selected = (di->itemState & ODS_SELECTED);
+ COLORREF newfg = GetSysColor(
+ selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
+ COLORREF newbg = GetSysColor(
+ selected ? COLOR_HIGHLIGHT : COLOR_WINDOW);
+ COLORREF oldfg = SetTextColor(di->hDC, newfg);
+ COLORREF oldbg = SetBkColor(di->hDC, newbg);
+
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ HFONT oldfont = SelectObject(di->hDC, font);
+
+ /* ExtTextOut("") is an easy way to just draw the
+ * background rectangle */
+ ExtTextOut(di->hDC, di->rcItem.left, di->rcItem.top,
+ ETO_OPAQUE | ETO_CLIPPED, &di->rcItem, "", 0, NULL);
+
+ struct keylist_display_data *disp =
+ (struct keylist_display_data *)di->itemData;
+
+ RECT r;
+
+ /* Apparently real list boxes start drawing at x=1, not x=0 */
+ r.left = r.top = r.bottom = 0;
+ r.right = 1;
+ MapDialogRect(hwnd, &r);
+ ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top,
+ ETO_CLIPPED, &di->rcItem, disp->alg->s,
+ disp->alg->len, NULL);
+
+ if (disp->bits->len) {
+ ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_bits,
+ di->rcItem.top, ETO_CLIPPED, &di->rcItem,
+ disp->bits->s, disp->bits->len, NULL);
+ }
+
+ ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_hash,
+ di->rcItem.top, ETO_CLIPPED, &di->rcItem,
+ disp->hash->s, disp->hash->len, NULL);
+
+ strbuf *sb = strbuf_new();
+ put_datapl(sb, ptrlen_from_strbuf(disp->comment));
+ if (disp->info->len) {
+ put_byte(sb, '\t');
+ put_datapl(sb, ptrlen_from_strbuf(disp->info));
+ }
+
+ TabbedTextOut(di->hDC, di->rcItem.left + r.right + colpos_comment,
+ di->rcItem.top, sb->s, sb->len, 0, NULL, 0);
+
+ strbuf_free(sb);
+
+ SetTextColor(di->hDC, oldfg);
+ SetBkColor(di->hDC, oldbg);
+ SelectObject(di->hDC, oldfont);
+
+ if (di->itemState & ODS_FOCUS)
+ DrawFocusRect(di->hDC, &di->rcItem);
+ }
+ return 0;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case IDC_KEYLIST_ADDKEY:
+ case IDC_KEYLIST_ADDKEY_ENC:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ if (modal_passphrase_hwnd) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(modal_passphrase_hwnd);
+ break;
+ }
+ prompt_add_keyfile(LOWORD(wParam) == IDC_KEYLIST_ADDKEY_ENC);
+ }
+ return 0;
+ case IDC_KEYLIST_REMOVE:
+ case IDC_KEYLIST_REENCRYPT:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ int i;
+ int rCount, sCount;
+ int *selectedArray;
+
+ /* our counter within the array of selected items */
+ int itemNum;
+
+ /* get the number of items selected in the list */
+ int numSelected = SendDlgItemMessage(
+ hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELCOUNT, 0, 0);
+
+ /* none selected? that was silly */
+ if (numSelected == 0) {
+ MessageBeep(0);
+ break;
+ }
+
+ /* get item indices in an array */
+ selectedArray = snewn(numSelected, int);
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELITEMS,
+ numSelected, (WPARAM)selectedArray);
+
+ itemNum = numSelected - 1;
+ rCount = pageant_count_ssh1_keys();
+ sCount = pageant_count_ssh2_keys();
+
+ /* go through the non-rsakeys until we've covered them all,
+ * and/or we're out of selected items to check. note that
+ * we go *backwards*, to avoid complications from deleting
+ * things hence altering the offset of subsequent items
+ */
+ for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ if (selectedArray[itemNum] == rCount + i) {
+ switch (LOWORD(wParam)) {
+ case IDC_KEYLIST_REMOVE:
+ pageant_delete_nth_ssh2_key(i);
+ break;
+ case IDC_KEYLIST_REENCRYPT:
+ pageant_reencrypt_nth_ssh2_key(i);
+ break;
+ }
+ itemNum--;
+ }
+ }
+
+ /* do the same for the rsa keys */
+ for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ if(selectedArray[itemNum] == i) {
+ switch (LOWORD(wParam)) {
+ case IDC_KEYLIST_REMOVE:
+ pageant_delete_nth_ssh1_key(i);
+ break;
+ case IDC_KEYLIST_REENCRYPT:
+ /* SSH-1 keys can't be re-encrypted */
+ break;
+ }
+ itemNum--;
+ }
+ }
+
+ sfree(selectedArray);
+ keylist_update();
+ }
+ return 0;
+ case IDC_KEYLIST_HELP:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ }
+ return 0;
+ case IDC_KEYLIST_FPTYPE:
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int selection = SendDlgItemMessage(
+ hwnd, IDC_KEYLIST_FPTYPE, CB_GETCURSEL, 0, 0);
+ if (selection >= 0 && (size_t)selection < lenof(fptypes)) {
+ fptype = fptypes[selection].value;
+ keylist_update();
+ }
+ }
+ return 0;
+ }
+ return 0;
+ case WM_HELP: {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ const char *topic = NULL;
+ switch (id) {
+ case IDC_KEYLIST_LISTBOX:
+ case IDC_KEYLIST_FPTYPE:
+ case IDC_KEYLIST_FPTYPE_STATIC:
+ topic = WINHELP_CTX_pageant_keylist; break;
+ case IDC_KEYLIST_ADDKEY: topic = WINHELP_CTX_pageant_addkey; break;
+ case IDC_KEYLIST_REMOVE: topic = WINHELP_CTX_pageant_remkey; break;
+ case IDC_KEYLIST_ADDKEY_ENC:
+ case IDC_KEYLIST_REENCRYPT:
+ topic = WINHELP_CTX_pageant_deferred; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ break;
+ }
+ case WM_CLOSE:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+/* Set up a system tray icon */
+static BOOL AddTrayIcon(HWND hwnd)
+{
+ BOOL res;
+ NOTIFYICONDATA tnid;
+ HICON hicon;
+
+#ifdef NIM_SETVERSION
+ tnid.uVersion = 0;
+ res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
+#endif
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ tnid.hWnd = hwnd;
+ tnid.uID = 1; /* unique within this systray use */
+ tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tnid.uCallbackMessage = WM_SYSTRAY;
+ tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
+ strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
+
+ res = Shell_NotifyIcon(NIM_ADD, &tnid);
+
+ if (hicon) DestroyIcon(hicon);
+
+ return res;
+}
+
+/* Update the saved-sessions menu. */
+static void update_sessions(void)
+{
+ int num_entries;
+ HKEY hkey;
+ TCHAR buf[MAX_PATH + 1];
+ MENUITEMINFO mii;
+ strbuf *sb;
+
+ int index_key, index_menu;
+
+ if (!putty_path)
+ return;
+
+ if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
+ return;
+
+ for(num_entries = GetMenuItemCount(session_menu);
+ num_entries > initial_menuitems_count;
+ num_entries--)
+ RemoveMenu(session_menu, 0, MF_BYPOSITION);
+
+ index_key = 0;
+ index_menu = 0;
+
+ sb = strbuf_new();
+ while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
+ if(strcmp(buf, PUTTY_DEFAULT) != 0) {
+ strbuf_clear(sb);
+ unescape_registry_key(buf, sb);
+
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_ENABLED;
+ mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
+ mii.dwTypeData = sb->s;
+ InsertMenuItem(session_menu, index_menu, true, &mii);
+ index_menu++;
+ }
+ index_key++;
+ }
+ strbuf_free(sb);
+
+ RegCloseKey(hkey);
+
+ if(index_menu == 0) {
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_GRAYED;
+ mii.dwTypeData = _T("(No sessions)");
+ InsertMenuItem(session_menu, index_menu, true, &mii);
+ }
+}
+
+/*
+ * Versions of Pageant prior to 0.61 expected this SID on incoming
+ * communications. For backwards compatibility, and more particularly
+ * for compatibility with derived works of PuTTY still using the old
+ * Pageant client code, we accept it as an alternative to the one
+ * returned from get_user_sid().
+ */
+PSID get_default_sid(void)
+{
+ HANDLE proc = NULL;
+ DWORD sidlen;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PSID sid = NULL, copy = NULL, ret = NULL;
+
+ if ((proc = OpenProcess(MAXIMUM_ALLOWED, false,
+ GetCurrentProcessId())) == NULL)
+ goto cleanup;
+
+ if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
+ &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
+ goto cleanup;
+
+ sidlen = GetLengthSid(sid);
+
+ copy = (PSID)smalloc(sidlen);
+
+ if (!CopySid(sidlen, copy, sid))
+ goto cleanup;
+
+ /* Success. Move sid into the return value slot, and null it out
+ * to stop the cleanup code freeing it. */
+ ret = copy;
+ copy = NULL;
+
+ cleanup:
+ if (proc != NULL)
+ CloseHandle(proc);
+ if (psd != NULL)
+ LocalFree(psd);
+ if (copy != NULL)
+ sfree(copy);
+
+ return ret;
+}
+
+struct WmCopydataTransaction {
+ char *length, *body;
+ size_t bodysize, bodylen;
+ HANDLE ev_msg_ready, ev_reply_ready;
+} wmct;
+
+static struct PageantClient wmcpc;
+
+static void wm_copydata_got_msg(void *vctx)
+{
+ pageant_handle_msg(&wmcpc, NULL, make_ptrlen(wmct.body, wmct.bodylen));
+}
+
+static void wm_copydata_got_response(
+ PageantClient *pc, PageantClientRequestId *reqid, ptrlen response)
+{
+ if (response.len > wmct.bodysize) {
+ /* Output would overflow message buffer. Replace with a
+ * failure message. */
+ static const unsigned char failure[] = { SSH_AGENT_FAILURE };
+ response = make_ptrlen(failure, lenof(failure));
+ assert(response.len <= wmct.bodysize);
+ }
+
+ PUT_32BIT_MSB_FIRST(wmct.length, response.len);
+ memcpy(wmct.body, response.ptr, response.len);
+
+ SetEvent(wmct.ev_reply_ready);
+}
+
+static bool ask_passphrase_common(PageantClientDialogId *dlgid,
+ const char *comment)
+{
+ /* Pageant core should be serialising requests, so we never expect
+ * a passphrase prompt to exist already at this point */
+ assert(!nonmodal_passphrase_hwnd);
+
+ struct PassphraseProcStruct *pps = snew(struct PassphraseProcStruct);
+ pps->modal = false;
+ pps->help_topic = WINHELP_CTX_pageant_deferred;
+ pps->dlgid = dlgid;
+ pps->passphrase = NULL;
+ pps->comment = comment;
+
+ nonmodal_passphrase_hwnd = CreateDialogParam(
+ hinst, MAKEINTRESOURCE(IDD_ONDEMAND_PASSPHRASE),
+ NULL, PassphraseProc, (LPARAM)pps);
+
+ /*
+ * Try to put this passphrase prompt into the foreground.
+ *
+ * This will probably not succeed in giving it the actual keyboard
+ * focus, because Windows is quite opposed to applications being
+ * able to suddenly steal the focus on their own initiative.
+ *
+ * That makes sense in a lot of situations, as a defensive
+ * measure. If you were about to type a password or other secret
+ * data into the window you already had focused, and some
+ * malicious app stole the focus, it might manage to trick you
+ * into typing your secrets into _it_ instead.
+ *
+ * In this case it's possible to regard the same defensive measure
+ * as counterproductive, because the effect if we _do_ steal focus
+ * is that you type something into our passphrase prompt that
+ * isn't the passphrase, and we fail to decrypt the key, and no
+ * harm is done. Whereas the effect of the user wrongly _assuming_
+ * the new passphrase prompt has the focus is much worse: now you
+ * type your highly secret passphrase into some other window you
+ * didn't mean to trust with that information - such as the
+ * agent-forwarded PuTTY in which you just ran an ssh command,
+ * which the _whole point_ was to avoid telling your passphrase to!
+ *
+ * On the other hand, I'm sure _every_ application author can come
+ * up with an argument for why they think _they_ should be allowed
+ * to steal the focus. Probably most of them include the claim
+ * that no harm is done if their application receives data
+ * intended for something else, and of course that's not always
+ * true!
+ *
+ * In any case, I don't know of anything I can do about it, or
+ * anything I _should_ do about it if I could. If anyone thinks
+ * they can improve on all this, patches are welcome.
+ */
+ SetForegroundWindow(nonmodal_passphrase_hwnd);
+
+ return true;
+}
+
+static bool wm_copydata_ask_passphrase(
+ PageantClient *pc, PageantClientDialogId *dlgid, const char *comment)
+{
+ return ask_passphrase_common(dlgid, comment);
+}
+
+static const PageantClientVtable wmcpc_vtable = {
+ .log = NULL, /* no logging in this client */
+ .got_response = wm_copydata_got_response,
+ .ask_passphrase = wm_copydata_ask_passphrase,
+};
+
+static char *answer_filemapping_message(const char *mapname)
+{
+ HANDLE maphandle = INVALID_HANDLE_VALUE;
+ void *mapaddr = NULL;
+ char *err = NULL;
+ size_t mapsize;
+ unsigned msglen;
+
+ PSID mapsid = NULL;
+ PSID expectedsid = NULL;
+ PSID expectedsid_bc = NULL;
+ PSECURITY_DESCRIPTOR psd = NULL;
+
+ wmct.length = wmct.body = NULL;
+
+#ifdef DEBUG_IPC
+ debug("mapname = \"%s\"\n", mapname);
+#endif
+
+ maphandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, mapname);
+ if (maphandle == NULL || maphandle == INVALID_HANDLE_VALUE) {
+ err = dupprintf("OpenFileMapping(\"%s\"): %s",
+ mapname, win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ debug("maphandle = %p\n", maphandle);
+#endif
+
+ if (should_have_security()) {
+ DWORD retd;
+
+ if ((expectedsid = get_user_sid()) == NULL) {
+ err = dupstr("unable to get user SID");
+ goto cleanup;
+ }
+
+ if ((expectedsid_bc = get_default_sid()) == NULL) {
+ err = dupstr("unable to get default SID");
+ goto cleanup;
+ }
+
+ if ((retd = p_GetSecurityInfo(
+ maphandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
+ &mapsid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)) {
+ err = dupprintf("unable to get owner of file mapping: "
+ "GetSecurityInfo returned: %s",
+ win_strerror(retd));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ {
+ LPTSTR ours, ours2, theirs;
+ ConvertSidToStringSid(mapsid, &theirs);
+ ConvertSidToStringSid(expectedsid, &ours);
+ ConvertSidToStringSid(expectedsid_bc, &ours2);
+ debug("got sids:\n oursnew=%s\n oursold=%s\n"
+ " theirs=%s\n", ours, ours2, theirs);
+ LocalFree(ours);
+ LocalFree(ours2);
+ LocalFree(theirs);
+ }
+#endif
+
+ if (!EqualSid(mapsid, expectedsid) &&
+ !EqualSid(mapsid, expectedsid_bc)) {
+ err = dupstr("wrong owning SID of file mapping");
+ goto cleanup;
+ }
+ } else {
+#ifdef DEBUG_IPC
+ debug("security APIs not present\n");
+#endif
+ }
+
+ mapaddr = MapViewOfFile(maphandle, FILE_MAP_WRITE, 0, 0, 0);
+ if (!mapaddr) {
+ err = dupprintf("unable to obtain view of file mapping: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ debug("mapped address = %p\n", mapaddr);
+#endif
+
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ size_t mbiSize = VirtualQuery(mapaddr, &mbi, sizeof(mbi));
+ if (mbiSize == 0) {
+ err = dupprintf("unable to query view of file mapping: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ if (mbiSize < (offsetof(MEMORY_BASIC_INFORMATION, RegionSize) +
+ sizeof(mbi.RegionSize))) {
+ err = dupstr("VirtualQuery returned too little data to get "
+ "region size");
+ goto cleanup;
+ }
+
+ mapsize = mbi.RegionSize;
+ }
+#ifdef DEBUG_IPC
+ debug("region size = %"SIZEu"\n", mapsize);
+#endif
+ if (mapsize < 5) {
+ err = dupstr("mapping smaller than smallest possible request");
+ goto cleanup;
+ }
+
+ wmct.length = (char *)mapaddr;
+ msglen = GET_32BIT_MSB_FIRST(wmct.length);
+
+#ifdef DEBUG_IPC
+ debug("msg length=%08x, msg type=%02x\n",
+ msglen, (unsigned)((unsigned char *) mapaddr)[4]);
+#endif
+
+ wmct.body = wmct.length + 4;
+ wmct.bodysize = mapsize - 4;
+
+ if (msglen > wmct.bodysize) {
+ /* Incoming length field is too large. Emit a failure response
+ * without even trying to handle the request.
+ *
+ * (We know this must fit, because we checked mapsize >= 5
+ * above.) */
+ PUT_32BIT_MSB_FIRST(wmct.length, 1);
+ *wmct.body = SSH_AGENT_FAILURE;
+ } else {
+ wmct.bodylen = msglen;
+ SetEvent(wmct.ev_msg_ready);
+ WaitForSingleObject(wmct.ev_reply_ready, INFINITE);
+ }
+
+ cleanup:
+ /* expectedsid has the lifetime of the program, so we don't free it */
+ sfree(expectedsid_bc);
+ if (psd)
+ LocalFree(psd);
+ if (mapaddr)
+ UnmapViewOfFile(mapaddr);
+ if (maphandle != NULL && maphandle != INVALID_HANDLE_VALUE)
+ CloseHandle(maphandle);
+ return err;
+}
+
+static void create_keylist_window(void)
+{
+ if (keylist)
+ return;
+
+ keylist = CreateDialog(hinst, MAKEINTRESOURCE(IDD_KEYLIST),
+ NULL, KeyListProc);
+ ShowWindow(keylist, SW_SHOWNORMAL);
+}
+
+static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ static bool menuinprogress;
+ static UINT msgTaskbarCreated = 0;
+
+ switch (message) {
+ case WM_CREATE:
+ msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
+ break;
+ default:
+ if (message==msgTaskbarCreated) {
+ /*
+ * Explorer has been restarted, so the tray icon will
+ * have been lost.
+ */
+ AddTrayIcon(hwnd);
+ }
+ break;
+
+ case WM_SYSTRAY:
+ if (lParam == WM_RBUTTONUP) {
+ POINT cursorpos;
+ GetCursorPos(&cursorpos);
+ PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
+ } else if (lParam == WM_LBUTTONDBLCLK) {
+ /* Run the default menu item. */
+ UINT menuitem = GetMenuDefaultItem(systray_menu, false, 0);
+ if (menuitem != -1)
+ PostMessage(hwnd, WM_COMMAND, menuitem, 0);
+ }
+ break;
+ case WM_SYSTRAY2:
+ if (!menuinprogress) {
+ menuinprogress = true;
+ update_sessions();
+ SetForegroundWindow(hwnd);
+ TrackPopupMenu(systray_menu,
+ TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
+ TPM_RIGHTBUTTON,
+ wParam, lParam, 0, hwnd, NULL);
+ menuinprogress = false;
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND: {
+ unsigned command = wParam & ~0xF; /* low 4 bits reserved to Windows */
+ switch (command) {
+ case IDM_PUTTY: {
+ TCHAR cmdline[10];
+ cmdline[0] = '\0';
+ if (restrict_putty_acl)
+ strcat(cmdline, "&R");
+
+ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline,
+ _T(""), SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!",
+ "Error", MB_OK | MB_ICONERROR);
+ }
+ break;
+ }
+ case IDM_CLOSE:
+ if (modal_passphrase_hwnd)
+ SendMessage(modal_passphrase_hwnd, WM_CLOSE, 0, 0);
+ SendMessage(hwnd, WM_CLOSE, 0, 0);
+ break;
+ case IDM_VIEWKEYS:
+ create_keylist_window();
+ /*
+ * Sometimes the window comes up minimised / hidden for
+ * no obvious reason. Prevent this. This also brings it
+ * to the front if it's already present (the user
+ * selected View Keys because they wanted to _see_ the
+ * thing).
+ */
+ SetForegroundWindow(keylist);
+ SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ break;
+ case IDM_ADDKEY:
+ case IDM_ADDKEY_ENCRYPTED:
+ if (modal_passphrase_hwnd) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(modal_passphrase_hwnd);
+ break;
+ }
+ prompt_add_keyfile(command == IDM_ADDKEY_ENCRYPTED);
+ break;
+ case IDM_REMOVE_ALL:
+ pageant_delete_all();
+ keylist_update();
+ break;
+ case IDM_REENCRYPT_ALL:
+ pageant_reencrypt_all();
+ keylist_update();
+ break;
+ case IDM_ABOUT:
+ if (!aboutbox) {
+ aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUT),
+ NULL, AboutProc);
+ ShowWindow(aboutbox, SW_SHOWNORMAL);
+ /*
+ * Sometimes the window comes up minimised / hidden
+ * for no obvious reason. Prevent this.
+ */
+ SetForegroundWindow(aboutbox);
+ SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ }
+ break;
+ case IDM_HELP:
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ break;
+ default: {
+ if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
+ MENUITEMINFO mii;
+ TCHAR buf[MAX_PATH + 1];
+ TCHAR param[MAX_PATH + 1];
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE;
+ mii.cch = MAX_PATH;
+ mii.dwTypeData = buf;
+ GetMenuItemInfo(session_menu, wParam, false, &mii);
+ param[0] = '\0';
+ if (restrict_putty_acl)
+ strcat(param, "&R");
+ strcat(param, "@");
+ strcat(param, mii.dwTypeData);
+ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
+ _T(""), SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!", "Error",
+ MB_OK | MB_ICONERROR);
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case WM_NETEVENT:
+ winselgui_response(wParam, lParam);
+ return 0;
+ case WM_DESTROY:
+ quit_help(hwnd);
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_COPYDATA: {
+ COPYDATASTRUCT *cds;
+ char *mapname, *err;
+
+ cds = (COPYDATASTRUCT *) lParam;
+ if (cds->dwData != AGENT_COPYDATA_ID)
+ return 0; /* not our message, mate */
+ mapname = (char *) cds->lpData;
+ if (mapname[cds->cbData - 1] != '\0')
+ return 0; /* failure to be ASCIZ! */
+ err = answer_filemapping_message(mapname);
+ if (err) {
+#ifdef DEBUG_IPC
+ debug("IPC failed: %s\n", err);
+#endif
+ sfree(err);
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+static DWORD WINAPI wm_copydata_threadfunc(void *param)
+{
+ HINSTANCE inst = *(HINSTANCE *)param;
+
+ HWND ipchwnd = CreateWindow(IPCCLASSNAME, IPCWINTITLE,
+ WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 100, 100, NULL, NULL, inst, NULL);
+ ShowWindow(ipchwnd, SW_HIDE);
+
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0) == 1) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return 0;
+}
+
+/*
+ * Fork and Exec the command in cmdline. [DBW]
+ */
+void spawn_cmd(const char *cmdline, const char *args, int show)
+{
+ if (ShellExecute(NULL, _T("open"), cmdline,
+ args, NULL, show) <= (HINSTANCE) 32) {
+ char *msg;
+ msg = dupprintf("Failed to run \"%s\": %s", cmdline,
+ win_strerror(GetLastError()));
+ MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
+ sfree(msg);
+ }
+}
+
+void noise_ultralight(NoiseSourceId id, unsigned long data)
+{
+ /* Pageant doesn't use random numbers, so we ignore this */
+}
+
+void cleanup_exit(int code)
+{
+ shutdown_help();
+ exit(code);
+}
+
+static bool winpgnt_listener_ask_passphrase(
+ PageantListenerClient *plc, PageantClientDialogId *dlgid,
+ const char *comment)
+{
+ return ask_passphrase_common(dlgid, comment);
+}
+
+struct winpgnt_client {
+ PageantListenerClient plc;
+};
+static const PageantListenerClientVtable winpgnt_vtable = {
+ .log = NULL, /* no logging */
+ .ask_passphrase = winpgnt_listener_ask_passphrase,
+};
+
+static struct winpgnt_client wpc[1];
+
+HINSTANCE hinst;
+
+static NORETURN void opt_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ char *msg = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ MessageBox(NULL, msg, "Pageant command line error", MB_ICONERROR | MB_OK);
+
+ exit(1);
+}
+
+#ifdef LEGACY_WINDOWS
+BOOL sw_PeekMessage(LPMSG msg, HWND hwnd, UINT min, UINT max, UINT remove)
+{
+ static bool unicode_unavailable = false;
+ if (!unicode_unavailable) {
+ BOOL ret = PeekMessageW(msg, hwnd, min, max, remove);
+ if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+ unicode_unavailable = true; /* don't try again */
+ else
+ return ret;
+ }
+ return PeekMessageA(msg, hwnd, min, max, remove);
+}
+#else
+#define sw_PeekMessage PeekMessageW
+#endif
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+ MSG msg;
+ const char *command = NULL;
+ const char *unixsocket = NULL;
+ bool show_keylist_on_startup = false;
+ int argc;
+ char **argv, **argstart;
+ const char *openssh_config_file = NULL;
+
+ typedef struct CommandLineKey {
+ Filename *fn;
+ bool add_encrypted;
+ } CommandLineKey;
+
+ CommandLineKey *clkeys = NULL;
+ size_t nclkeys = 0, clkeysize = 0;
+
+ dll_hijacking_protection();
+
+ hinst = inst;
+
+ if (should_have_security()) {
+ /*
+ * Attempt to get the security API we need.
+ */
+ if (!got_advapi()) {
+ MessageBox(NULL,
+ "Unable to access security APIs. Pageant will\n"
+ "not run, in case it causes a security breach.",
+ "Pageant Fatal Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ }
+
+ /*
+ * See if we can find our Help file.
+ */
+ init_help();
+
+ /*
+ * Look for the PuTTY binary (we will enable the saved session
+ * submenu if we find it).
+ */
+ {
+ char b[2048], *p, *q, *r;
+ FILE *fp;
+ GetModuleFileName(NULL, b, sizeof(b) - 16);
+ r = b;
+ p = strrchr(b, '\\');
+ if (p && p >= r) r = p+1;
+ q = strrchr(b, ':');
+ if (q && q >= r) r = q+1;
+ strcpy(r, "putty.exe");
+ if ( (fp = fopen(b, "r")) != NULL) {
+ putty_path = dupstr(b);
+ fclose(fp);
+ } else
+ putty_path = NULL;
+ }
+
+ /*
+ * Process the command line, handling anything that can be done
+ * immediately, but deferring adding keys until after we've
+ * started up the main agent. Details of keys to be added are
+ * stored in the 'clkeys' array.
+ */
+ split_into_argv(cmdline, &argc, &argv, &argstart);
+ bool add_keys_encrypted = false;
+ AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error);
+ while (!aux_match_done(&amo)) {
+ char *val;
+ #define match_opt(...) aux_match_opt( \
+ &amo, NULL, __VA_ARGS__, (const char *)NULL)
+ #define match_optval(...) aux_match_opt( \
+ &amo, &val, __VA_ARGS__, (const char *)NULL)
+
+ if (aux_match_arg(&amo, &val)) {
+ /*
+ * Non-option arguments are expected to be key files, and
+ * added to clkeys.
+ */
+ sgrowarray(clkeys, clkeysize, nclkeys);
+ CommandLineKey *clkey = &clkeys[nclkeys++];
+ clkey->fn = filename_from_str(val);
+ clkey->add_encrypted = add_keys_encrypted;
+ } else if (match_opt("-pgpfp")) {
+ pgp_fingerprints_msgbox(NULL);
+ return 1;
+ } else if (match_opt("-restrict-acl", "-restrict_acl",
+ "-restrictacl")) {
+ restrict_process_acl();
+ } else if (match_opt("-restrict-putty-acl", "-restrict_putty_acl")) {
+ restrict_putty_acl = true;
+ } else if (match_opt("-no-decrypt", "-no_decrypt",
+ "-nodecrypt", "-encrypted")) {
+ add_keys_encrypted = true;
+ } else if (match_opt("-keylist")) {
+ show_keylist_on_startup = true;
+ } else if (match_optval("-openssh-config", "-openssh_config")) {
+ openssh_config_file = val;
+ } else if (match_optval("-unix")) {
+ unixsocket = val;
+ } else if (match_opt("-c")) {
+ /*
+ * If we see `-c', then the rest of the command line
+ * should be treated as a command to be spawned.
+ */
+ if (amo.index < amo.argc)
+ command = argstart[amo.index];
+ else
+ command = "";
+ break;
+ } else {
+ opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
+ }
+ }
+
+ /*
+ * Create and lock an interprocess mutex while we figure out
+ * whether we're going to be the Pageant server or a client. That
+ * way, two Pageant processes started up simultaneously will be
+ * able to agree on which one becomes the server without a race
+ * condition.
+ */
+ HANDLE mutex;
+ {
+ char *err;
+ char *mutexname = agent_mutex_name();
+ mutex = lock_interprocess_mutex(mutexname, &err);
+ sfree(mutexname);
+ if (!mutex) {
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ }
+
+ /*
+ * Find out if Pageant is already running.
+ */
+ already_running = agent_exists();
+
+ /*
+ * If it isn't, we're going to be the primary Pageant that stays
+ * running, so set up all the machinery to answer requests.
+ */
+ if (!already_running) {
+ /*
+ * Set up the window class for the hidden window that receives
+ * all the messages to do with our presence in the system tray.
+ */
+
+ if (!prev) {
+ WNDCLASS wndclass;
+
+ memset(&wndclass, 0, sizeof(wndclass));
+ wndclass.lpfnWndProc = TrayWndProc;
+ wndclass.hInstance = inst;
+ wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
+ wndclass.lpszClassName = TRAYCLASSNAME;
+
+ RegisterClass(&wndclass);
+ }
+
+ keylist = NULL;
+
+ traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE,
+ WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 100, 100, NULL, NULL, inst, NULL);
+ winselgui_set_hwnd(traywindow);
+
+ /*
+ * Initialise the cross-platform Pageant code.
+ */
+ pageant_init();
+
+ /*
+ * Set up a named-pipe listener.
+ */
+ wpc->plc.vt = &winpgnt_vtable;
+ wpc->plc.suppress_logging = true;
+ if (should_have_security()) {
+ Plug *pl_plug;
+ struct pageant_listen_state *pl =
+ pageant_listener_new(&pl_plug, &wpc->plc);
+ char *pipename = agent_named_pipe_name();
+ Socket *sock = new_named_pipe_listener(pipename, pl_plug);
+ if (sk_socket_error(sock)) {
+ char *err = dupprintf("Unable to open named pipe at %s "
+ "for SSH agent:\n%s", pipename,
+ sk_socket_error(sock));
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ pageant_listener_got_socket(pl, sock);
+
+ /*
+ * If we've been asked to write out an OpenSSH config file
+ * pointing at the named pipe, do so.
+ */
+ if (openssh_config_file) {
+ FILE *fp = fopen(openssh_config_file, "w");
+ if (!fp) {
+ char *err = dupprintf("Unable to write OpenSSH config "
+ "file to %s", openssh_config_file);
+ MessageBox(NULL, err, "Pageant Error",
+ MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ fprintf(fp, "IdentityAgent %s\n", pipename);
+ fclose(fp);
+ }
+
+ sfree(pipename);
+ }
+
+ /*
+ * Set up an AF_UNIX listener too, if we were asked to.
+ */
+ if (unixsocket) {
+ sk_init();
+
+ /* FIXME: diagnose any error except file-not-found. Also,
+ * check the file type if possible? */
+ remove(unixsocket);
+
+ Plug *pl_plug;
+ struct pageant_listen_state *pl =
+ pageant_listener_new(&pl_plug, &wpc->plc);
+ Socket *sock = sk_newlistener_unix(unixsocket, pl_plug);
+ if (sk_socket_error(sock)) {
+ char *err = dupprintf("Unable to open AF_UNIX socket at %s "
+ "for SSH agent:\n%s", unixsocket,
+ sk_socket_error(sock));
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ pageant_listener_got_socket(pl, sock);
+ }
+
+ /*
+ * Set up the window class for the hidden window that receives
+ * the WM_COPYDATA message used by the old-style Pageant IPC
+ * system.
+ */
+ if (!prev) {
+ WNDCLASS wndclass;
+
+ memset(&wndclass, 0, sizeof(wndclass));
+ wndclass.lpfnWndProc = wm_copydata_WndProc;
+ wndclass.hInstance = inst;
+ wndclass.lpszClassName = IPCCLASSNAME;
+
+ RegisterClass(&wndclass);
+ }
+
+ /*
+ * And launch the subthread which will open that hidden window and
+ * handle WM_COPYDATA messages on it.
+ */
+ wmcpc.vt = &wmcpc_vtable;
+ wmcpc.suppress_logging = true;
+ pageant_register_client(&wmcpc);
+ DWORD wm_copydata_threadid;
+ wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL);
+ wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL);
+ HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc,
+ &inst, 0, &wm_copydata_threadid);
+ if (hThread)
+ CloseHandle(hThread); /* we don't need the thread handle */
+ add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL);
+ }
+
+ /*
+ * Now we're either a fully set up Pageant server, or we know one
+ * is running somewhere else. Either way, now it's safe to unlock
+ * the mutex.
+ */
+ unlock_interprocess_mutex(mutex);
+
+ /*
+ * Add any keys provided on the command line.
+ */
+ for (size_t i = 0; i < nclkeys; i++) {
+ CommandLineKey *clkey = &clkeys[i];
+ win_add_keyfile(clkey->fn, clkey->add_encrypted);
+ filename_free(clkey->fn);
+ }
+ sfree(clkeys);
+ /* And forget any passphrases we stashed during that loop. */
+ pageant_forget_passphrases();
+
+ /*
+ * Now our keys are present, spawn a command, if we were asked to.
+ */
+ if (command) {
+ char *args;
+ if (command[0] == '"')
+ args = strchr(++command, '"');
+ else
+ args = strchr(command, ' ');
+ if (args) {
+ *args++ = 0;
+ while(*args && isspace(*args)) args++;
+ }
+ spawn_cmd(command, args, show);
+ }
+
+ /*
+ * If Pageant was already running, we leave now. If we haven't
+ * even taken any auxiliary action (spawned a command or added
+ * keys), complain.
+ */
+ if (already_running) {
+ if (!command && !nclkeys) {
+ MessageBox(NULL, "Pageant is already running", "Pageant Error",
+ MB_ICONERROR | MB_OK);
+ }
+ return 0;
+ }
+
+ /* Set up a system tray icon */
+ AddTrayIcon(traywindow);
+
+ /* Accelerators used: nsvkxa */
+ systray_menu = CreatePopupMenu();
+ if (putty_path) {
+ session_menu = CreateMenu();
+ AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
+ AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
+ (UINT_PTR) session_menu, "&Saved Sessions");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ }
+ AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
+ "&View Keys");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY_ENCRYPTED,
+ "Add key (encrypted)");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(systray_menu, MF_ENABLED, IDM_REMOVE_ALL,
+ "Remove All Keys");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_REENCRYPT_ALL,
+ "Re-encrypt All Keys");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ if (has_help())
+ AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
+ initial_menuitems_count = GetMenuItemCount(session_menu);
+
+ /* Set the default menu item. */
+ SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, false);
+
+ ShowWindow(traywindow, SW_HIDE);
+
+ /* Open the visible key list window, if we've been asked to. */
+ if (show_keylist_on_startup)
+ create_keylist_window();
+
+ /*
+ * Main message loop.
+ */
+ while (true) {
+ int n;
+
+ HandleWaitList *hwl = get_handle_wait_list();
+
+ DWORD timeout = toplevel_callback_pending() ? 0 : INFINITE;
+ n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false,
+ timeout, QS_ALLINPUT);
+
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles)
+ handle_wait_activate(hwl, n - WAIT_OBJECT_0);
+ handle_wait_list_free(hwl);
+
+ while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message == WM_QUIT)
+ goto finished; /* two-level break */
+
+ if (IsWindow(keylist) && IsDialogMessage(keylist, &msg))
+ continue;
+ if (IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))
+ continue;
+ if (IsWindow(nonmodal_passphrase_hwnd) &&
+ IsDialogMessage(nonmodal_passphrase_hwnd, &msg))
+ continue;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ run_toplevel_callbacks();
+ }
+ finished:
+
+ /* Clean up the system tray icon */
+ {
+ NOTIFYICONDATA tnid;
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ tnid.hWnd = traywindow;
+ tnid.uID = 1;
+
+ Shell_NotifyIcon(NIM_DELETE, &tnid);
+
+ DestroyMenu(systray_menu);
+ }
+
+ if (keypath) filereq_free(keypath);
+
+ if (openssh_config_file) {
+ /*
+ * Leave this file around, but empty it, so that it doesn't
+ * refer to a pipe we aren't listening on any more.
+ */
+ FILE *fp = fopen(openssh_config_file, "w");
+ if (fp)
+ fclose(fp);
+ }
+
+ cleanup_exit(msg.wParam);
+ return msg.wParam; /* just in case optimiser complains */
+}