X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/cb5ca81381886483b4ec2a6ff4560434edde8f80..3ca5c28cafade2c9e0ada4d5b30c1d9b1be32f4c:/pageant.c diff --git a/pageant.c b/pageant.c index fc5e8791..20aaaf48 100644 --- a/pageant.c +++ b/pageant.c @@ -12,6 +12,7 @@ #include #include "ssh.h" +#include "misc.h" #include "tree234.h" #define IDI_MAINICON 200 @@ -31,18 +32,22 @@ #define IDM_CLOSE 0x0010 #define IDM_VIEWKEYS 0x0020 #define IDM_ADDKEY 0x0030 -#define IDM_ABOUT 0x0040 +#define IDM_HELP 0x0040 +#define IDM_ABOUT 0x0050 #define APPNAME "Pageant" extern char ver[]; static HINSTANCE instance; -static HWND hwnd; +static HWND main_hwnd; static HWND keylist; static HWND aboutbox; static HMENU systray_menu; static int already_running; +static int requested_help; + +static char *help_path; static tree234 *rsakeys, *ssh2keys; @@ -65,15 +70,17 @@ int agent_exists(void); * pads its data with random bytes. Since we only use rsadecrypt() * and the signing functions, which are deterministic, this should * never be called. - * + * * If it _is_ called, there is a _serious_ problem, because it * won't generate true random numbers. So we must scream, panic, * and exit immediately if that should happen. */ int random_byte(void) { - MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR); + MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR); exit(0); + /* this line can't be reached but it placates MSVC's warnings :-) */ + return 0; } /* @@ -113,6 +120,22 @@ struct PassphraseProcStruct { char *comment; }; +static tree234 *passphrases = NULL; + +/* + * After processing a list of filenames, we want to forget the + * passphrases. + */ +static void forget_passphrases(void) +{ + while (count234(passphrases) > 0) { + char *pp = index234(passphrases, 0); + memset(pp, 0, strlen(pp)); + delpos234(passphrases, 0); + free(pp); + } +} + /* * Dialog-box function for the Licence box. */ @@ -235,6 +258,26 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, } /* + * 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); +} + +/* * Update the visible key list. */ static void keylist_update(void) @@ -305,7 +348,8 @@ static void add_keyfile(char *filename) char *comment; struct PassphraseProcStruct pps; int ver; - + int original_pass; + ver = keyfile_version(filename); if (ver == 0) { MessageBox(NULL, "Couldn't load private key.", APPNAME, @@ -322,18 +366,26 @@ static void add_keyfile(char *filename) rkey = smalloc(sizeof(*rkey)); pps.passphrase = passphrase; pps.comment = comment; + original_pass = 0; do { if (needs_pass) { - int dlgret; - dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210), - NULL, PassphraseProc, (LPARAM) & pps); - passphrase_box = NULL; - if (!dlgret) { - if (comment) - sfree(comment); - if (ver == 1) - sfree(rkey); - return; /* operation cancelled */ + /* try all the remembered passphrases first */ + char *pp = index234(passphrases, attempts); + if(pp) { + strcpy(passphrase, pp); + } else { + int dlgret; + original_pass = 1; + dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210), + NULL, PassphraseProc, (LPARAM) & pps); + passphrase_box = NULL; + if (!dlgret) { + if (comment) + sfree(comment); + if (ver == 1) + sfree(rkey); + return; /* operation cancelled */ + } } } else *passphrase = '\0'; @@ -350,6 +402,13 @@ static void add_keyfile(char *filename) } attempts++; } while (ret == -1); + + /* if they typed in an ok passphrase, remember it */ + if(original_pass && ret) { + char *pp = dupstr(passphrase); + addpos234(passphrases, pp, 0); + } + if (comment) sfree(comment); if (ret == 0) { @@ -611,7 +670,7 @@ static void answer_msg(void *msg) break; case SSH2_AGENTC_SIGN_REQUEST: /* - * Reply with either SSH2_AGENT_RSA_RESPONSE or + * Reply with either SSH2_AGENT_SIGN_RESPONSE or * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ @@ -648,16 +707,19 @@ static void answer_msg(void *msg) { struct RSAKey *key; char *comment; + int commentlen; key = smalloc(sizeof(struct RSAKey)); - memset(key, 0, sizeof(key)); + memset(key, 0, sizeof(struct RSAKey)); p += makekey(p, key, NULL, 1); p += makeprivate(p, key); - p += ssh1_read_bignum(p, key->iqmp); /* p^-1 mod q */ - p += ssh1_read_bignum(p, key->p); /* p */ - p += ssh1_read_bignum(p, key->q); /* q */ - comment = smalloc(GET_32BIT(p)); + p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */ + p += ssh1_read_bignum(p, &key->p); /* p */ + p += ssh1_read_bignum(p, &key->q); /* q */ + commentlen = GET_32BIT(p); + comment = smalloc(commentlen+1); if (comment) { - memcpy(comment, p + 4, GET_32BIT(p)); + memcpy(comment, p + 4, commentlen); + comment[commentlen] = '\0'; key->comment = comment; } PUT_32BIT(ret, 1); @@ -691,6 +753,8 @@ static void answer_msg(void *msg) /* Add further algorithm names here. */ if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7)) key->alg = &ssh_rsa; + else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7)) + key->alg = &ssh_dss; else { sfree(key); goto failure; @@ -953,27 +1017,66 @@ static void prompt_add_keyfile(void) { OPENFILENAME of; char filename[FILENAME_MAX]; + char *filelist = smalloc(8192); + char *filewalker; + int n, dirlen; + memset(&of, 0, sizeof(of)); #ifdef OPENFILENAME_SIZE_VERSION_400 of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else of.lStructSize = sizeof(of); #endif - of.hwndOwner = hwnd; + of.hwndOwner = main_hwnd; of.lpstrFilter = "All Files\0*\0\0\0"; of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; - of.lpstrFile = filename; - *filename = '\0'; - of.nMaxFile = sizeof(filename); + of.lpstrFile = filelist; + *filelist = '\0'; + of.nMaxFile = FILENAME_MAX; of.lpstrFileTitle = NULL; of.lpstrInitialDir = NULL; of.lpstrTitle = "Select Private Key File"; - of.Flags = 0; + of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; if (GetOpenFileName(&of)) { - add_keyfile(filename); + if(strlen(filelist) > of.nFileOffset) + /* Only one filename returned? */ + add_keyfile(filelist); + 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. + */ + filewalker = filelist; + dirlen = strlen(filewalker); + if(dirlen > FILENAME_MAX - 8) return; + memcpy(filename, filewalker, dirlen); + + filewalker += dirlen + 1; + filename[dirlen++] = '\\'; + + /* then go over names one by one */ + for(;;) { + n = strlen(filewalker) + 1; + /* end of the list */ + if(n == 1) + break; + /* too big, shouldn't happen */ + if(n + dirlen > FILENAME_MAX) + break; + + memcpy(filename + dirlen, filewalker, n); + filewalker += n; + + add_keyfile(filename); + } + } + keylist_update(); + forget_passphrases(); } + sfree(filelist); } /* @@ -1002,6 +1105,16 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg, rd.right - rd.left, rd.bottom - rd.top, TRUE); } + if (help_path) + SetWindowLong(hwnd, GWL_EXSTYLE, + GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP); + else { + HWND item = GetDlgItem(hwnd, 103); /* the Help button */ + if (item) + DestroyWindow(item); + } + requested_help = FALSE; + keylist = hwnd; { static int tabs[] = { 35, 60, 210 }; @@ -1032,34 +1145,93 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg, case 102: /* remove key */ if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { - int n = SendDlgItemMessage(hwnd, 100, LB_GETCURSEL, 0, 0); int i; - if (n == LB_ERR) { + 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, 100, LB_GETSELCOUNT, 0, 0); + + /* none selected? that was silly */ + if (numSelected == 0) { MessageBeep(0); break; } - for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) - if (n-- == 0) - break; - if (rkey) { - del234(rsakeys, rkey); - freersakey(rkey); - sfree(rkey); - } else { - for (i = 0; NULL != (skey = index234(ssh2keys, i)); - i++) if (n-- == 0) - break; - if (skey) { - del234(ssh2keys, skey); - skey->alg->freekey(skey->data); - sfree(skey); - } + + /* get item indices in an array */ + selectedArray = smalloc(numSelected * sizeof(int)); + SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS, + numSelected, (WPARAM)selectedArray); + + itemNum = numSelected - 1; + rCount = count234(rsakeys); + sCount = count234(ssh2keys); + + /* 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--) { + skey = index234(ssh2keys, i); + + if (selectedArray[itemNum] == rCount + i) { + del234(ssh2keys, skey); + skey->alg->freekey(skey->data); + sfree(skey); + itemNum--; + } + } + + /* do the same for the rsa keys */ + for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { + rkey = index234(rsakeys, i); + + if(selectedArray[itemNum] == i) { + del234(rsakeys, rkey); + freersakey(rkey); + sfree(rkey); + itemNum--; + } } + + sfree(selectedArray); keylist_update(); } return 0; + case 103: /* help */ + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + if (help_path) { + WinHelp(main_hwnd, help_path, HELP_COMMAND, + (DWORD)"JI(`',`pageant.general')"); + requested_help = TRUE; + } + } + return 0; } return 0; + case WM_HELP: + if (help_path) { + int id = ((LPHELPINFO)lParam)->iCtrlId; + char *cmd = NULL; + switch (id) { + case 100: cmd = "JI(`',`pageant.keylist')"; break; + case 101: cmd = "JI(`',`pageant.addkey')"; break; + case 102: cmd = "JI(`',`pageant.remkey')"; break; + } + if (cmd) { + WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd); + requested_help = TRUE; + } else { + MessageBeep(0); + } + } + break; case WM_CLOSE: keylist = NULL; DestroyWindow(hwnd); @@ -1068,13 +1240,54 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg, 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(instance, MAKEINTRESOURCE(201)); + strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); + + res = Shell_NotifyIcon(NIM_ADD, &tnid); + + if (hicon) DestroyIcon(hicon); + + return res; +} + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int ret; static int 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; @@ -1140,9 +1353,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); } break; + case IDM_HELP: + if (help_path) { + WinHelp(main_hwnd, help_path, HELP_COMMAND, + (DWORD)"JI(`',`pageant.general')"); + requested_help = TRUE; + } + break; } break; case WM_DESTROY: + if (requested_help) { + WinHelp(main_hwnd, help_path, HELP_QUIT, 0); + requested_help = FALSE; + } PostQuitMessage(0); return 0; case WM_COPYDATA: @@ -1246,10 +1470,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, /* * Fork and Exec the command in cmdline. [DBW] */ -void spawn_cmd(char *cmdline, int show) +void spawn_cmd(char *cmdline, char * args, int show) { if (ShellExecute(NULL, _T("open"), cmdline, - NULL, NULL, show) <= (HINSTANCE) 32) { + args, NULL, show) <= (HINSTANCE) 32) { TCHAR sMsg[140]; sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline, (int)GetLastError()); @@ -1305,6 +1529,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) instance = inst; /* + * See if we can find our Help file. + */ + { + char b[2048], *p, *q, *r; + FILE *fp; + GetModuleFileName(NULL, b, sizeof(b) - 1); + 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.hlp"); + if ( (fp = fopen(b, "r")) != NULL) { + help_path = dupstr(b); + fclose(fp); + } else + help_path = NULL; + } + + /* * Find out if Pageant is already running. */ already_running = FALSE; @@ -1327,47 +1571,29 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) RegisterClass(&wndclass); } - hwnd = keylist = NULL; + main_hwnd = keylist = NULL; - hwnd = CreateWindow(APPNAME, APPNAME, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); + main_hwnd = CreateWindow(APPNAME, APPNAME, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); /* Set up a system tray icon */ - { - 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(instance, MAKEINTRESOURCE(201)); - strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); - - res = Shell_NotifyIcon(NIM_ADD, &tnid); - - if (hicon) - DestroyIcon(hicon); - - systray_menu = CreatePopupMenu(); - /* accelerators used: vkxa */ - AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, - "&View Keys"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About"); - AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit"); - } - - ShowWindow(hwnd, SW_HIDE); + AddTrayIcon(main_hwnd); + + systray_menu = CreatePopupMenu(); + /* accelerators used: vkxa */ + AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, + "&View Keys"); + AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); + AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); + if (help_path) + 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"); + + ShowWindow(main_hwnd, SW_HIDE); /* * Initialise storage for RSA keys. @@ -1378,6 +1604,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } /* + * Initialise storage for short-term passphrase cache. + */ + passphrases = newtree234(NULL); + + /* * Process the command line and add keys as listed on it. * If we already determined that we need to spawn a program from above we * need to ignore the first two arguments. [DBW] @@ -1389,10 +1620,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) while (*p) { while (*p && isspace(*p)) p++; - if (*p && !isspace(*p)) { + if (*p && !isspace(*p)) { char *q = p, *pp = p; while (*p && (inquotes || !isspace(*p))) { - if (*p == '"') { + if (*p == '"') { inquotes = !inquotes; p++; continue; @@ -1422,8 +1653,24 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } } - if (command) - spawn_cmd(command, show); + /* + * Forget any passphrase that we retained while going over + * command line keyfiles. + */ + forget_passphrases(); + + 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 @@ -1453,7 +1700,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) NOTIFYICONDATA tnid; tnid.cbSize = sizeof(NOTIFYICONDATA); - tnid.hWnd = hwnd; + tnid.hWnd = main_hwnd; tnid.uID = 1; Shell_NotifyIcon(NIM_DELETE, &tnid);