diff --git a/Recipe b/Recipe index ea561b63..c3d4a916 100644 --- a/Recipe +++ b/Recipe @@ -330,7 +330,9 @@ KEYGEN = sshrsag sshdssg sshecdsag # X/GTK Unix app, [U] for command-line Unix app. putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS + + screenshot testback puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS + + screenshot testback plink : [C] winplink wincons console NONSSH WINSSH W_BE_ALL logging WINMISC + winx11 plink.res winnojmp sessprep noterm winnohlp winselcli + clicons wincliloop console LIBS @@ -353,6 +355,7 @@ puttygen : [G] winpgen KEYGEN SSHPRIME sshdes ARITH sshmd5 version + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshprng + sshauxcrypt sshhmac winsecur winmiscs sshsha3 sshblake2 sshargon2 + + screenshot pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg diff --git a/settings.c b/settings.c index 32a53c54..4e089c4f 100644 --- a/settings.c +++ b/settings.c @@ -1302,6 +1302,8 @@ static int sessioncmp(const void *av, const void *bv) return strcmp(a, b); /* otherwise, compare normally */ } +bool sesslist_demo_mode = false; + void get_sesslist(struct sesslist *list, bool allocate) { int i; @@ -1311,12 +1313,18 @@ void get_sesslist(struct sesslist *list, bool allocate) if (allocate) { strbuf *sb = strbuf_new(); - if ((handle = enum_settings_start()) != NULL) { - while (enum_settings_next(handle, sb)) - put_byte(sb, '\0'); - enum_settings_finish(handle); + if (sesslist_demo_mode) { + put_asciz(sb, "demo-server"); + put_asciz(sb, "demo-server-2"); + } else { + if ((handle = enum_settings_start()) != NULL) { + while (enum_settings_next(handle, sb)) + put_byte(sb, '\0'); + enum_settings_finish(handle); + } + put_byte(sb, '\0'); } - put_byte(sb, '\0'); + list->buffer = strbuf_to_str(sb); /* diff --git a/testback.c b/testback.c index 173786ed..2561d47b 100644 --- a/testback.c +++ b/testback.c @@ -32,11 +32,8 @@ #include "putty.h" -static char *null_init(const BackendVtable *, Seat *, Backend **, LogContext *, - Conf *, const char *, int, char **, bool, bool); static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, Conf *, const char *, int, char **, bool, bool); -static void null_free(Backend *); static void loop_free(Backend *); static void null_reconfig(Backend *, Conf *); static size_t null_send(Backend *, const char *, size_t); @@ -54,8 +51,8 @@ static void null_unthrottle(Backend *, size_t); static int null_cfg_info(Backend *); const BackendVtable null_backend = { - .init = null_init, - .free = null_free, + .init = loop_init, + .free = loop_free, .reconfig = null_reconfig, .send = null_send, .sendbuffer = null_sendbuffer, @@ -102,17 +99,6 @@ struct loop_state { Backend backend; }; -static char *null_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) { - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - *backend_handle = NULL; - return NULL; -} - static char *loop_init(const BackendVtable *vt, Seat *seat, Backend **backend_handle, LogContext *logctx, Conf *conf, const char *host, int port, @@ -123,15 +109,14 @@ static char *loop_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); st->seat = seat; + st->backend.vt = vt; *backend_handle = &st->backend; + + *realhost = dupstr(host); + return NULL; } -static void null_free(Backend *be) -{ - -} - static void loop_free(Backend *be) { struct loop_state *st = container_of(be, struct loop_state, backend); diff --git a/windows/screenshot.c b/windows/screenshot.c new file mode 100644 index 00000000..4b09b29f --- /dev/null +++ b/windows/screenshot.c @@ -0,0 +1,114 @@ +#include "putty.h" + +#include + +char *save_screenshot(HWND hwnd, const char *outfile) +{ + HDC dcWindow = NULL, dcSave = NULL; + HBITMAP bmSave = NULL; + uint8_t *buffer = NULL; + char *err = NULL; + + static HMODULE dwmapi_module; + DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute, + (HWND, DWORD, PVOID, DWORD)); + + if (!dwmapi_module) { + dwmapi_module = load_system32_dll("dwmapi.dll"); + GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute); + } + + dcWindow = GetDC(NULL); + if (!dcWindow) { + err = dupprintf("GetDC(window): %s", win_strerror(GetLastError())); + goto out; + } + + int x, y, w, h; + RECT wr; + + /* Use DwmGetWindowAttribute in place of GetWindowRect to exclude + * drop shadow, otherwise we get a load of unwanted desktop + * background under the shadow */ + if (p_DwmGetWindowAttribute && + 0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, + &wr, sizeof(wr))) { + x = wr.left; + y = wr.top; + w = wr.right - wr.left; + h = wr.bottom - wr.top; + } else { + BITMAP bmhdr; + memset(&bmhdr, 0, sizeof(bmhdr)); + GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP), + sizeof(bmhdr), &bmhdr); + x = y = 0; + w = bmhdr.bmWidth; + h = bmhdr.bmHeight; + } + + dcSave = CreateCompatibleDC(dcWindow); + if (!dcSave) { + err = dupprintf("CreateCompatibleDC(desktop window dc): %s", + win_strerror(GetLastError())); + goto out; + } + + bmSave = CreateCompatibleBitmap(dcWindow, w, h); + if (!bmSave) { + err = dupprintf("CreateCompatibleBitmap: %s", + win_strerror(GetLastError())); + goto out; + } + + if (!SelectObject(dcSave, bmSave)) { + err = dupprintf("SelectObject: %s", win_strerror(GetLastError())); + goto out; + } + + if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) { + err = dupprintf("BitBlt: %s", win_strerror(GetLastError())); + goto out; + } + + BITMAPINFO bmInfo; + memset(&bmInfo, 0, sizeof(bmInfo)); + bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader); + bmInfo.bmiHeader.biWidth = w; + bmInfo.bmiHeader.biHeight = h; + bmInfo.bmiHeader.biPlanes = 1; + bmInfo.bmiHeader.biBitCount = 32; + bmInfo.bmiHeader.biCompression = BI_RGB; + + size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4; + buffer = snewn(bmBytes, uint8_t); + + if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS)) + err = dupprintf("GetDIBits (get data): %s", + win_strerror(GetLastError())); + + FILE *fp = fopen(outfile, "wb"); + if (!fp) { + err = dupprintf("'%s': unable to open file", outfile); + goto out; + } + + BITMAPFILEHEADER bmFileHdr; + bmFileHdr.bfType = 'B' | ('M' << 8); + bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes; + bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader); + fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp); + fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp); + fwrite((void *)buffer, 1, bmBytes, fp); + fclose(fp); + + out: + if (dcWindow) + ReleaseDC(NULL, dcWindow); + if (bmSave) + DeleteObject(bmSave); + if (dcSave) + DeleteObject(dcSave); + sfree(buffer); + return err; +} diff --git a/windows/windlg.c b/windows/windlg.c index 9c5fdb76..3f48b8f0 100644 --- a/windows/windlg.c +++ b/windows/windlg.c @@ -388,6 +388,8 @@ static void create_controls(HWND hwnd, char *path) } } +const char *dialog_box_demo_screenshot_filename = NULL; + /* * This function is the configuration box. * (Being a dialog procedure, in general it returns 0 if the default @@ -396,6 +398,7 @@ static void create_controls(HWND hwnd, char *path) static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + const int DEMO_SCREENSHOT_TIMER_ID = 1230; HWND hw, treeview; struct treeview_faff tvfaff; int ret; @@ -565,6 +568,21 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, * spurious firing during the above setup procedure. */ SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); + + if (dialog_box_demo_screenshot_filename) + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); + return 0; + case WM_TIMER: + if (dialog_box_demo_screenshot_filename && + (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + const char *err = save_screenshot( + hwnd, dialog_box_demo_screenshot_filename); + if (err) + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + SaneEndDialog(hwnd, 0); + } return 0; case WM_LBUTTONUP: /* diff --git a/windows/window.c b/windows/window.c index a37f6525..72ca89a0 100644 --- a/windows/window.c +++ b/windows/window.c @@ -231,6 +231,11 @@ static int compose_state = 0; static UINT wm_mousewheel = WM_MOUSEWHEEL; +extern bool sesslist_demo_mode; +extern const char *dialog_box_demo_screenshot_filename; +static strbuf *demo_terminal_data = NULL; +static const char *terminal_demo_screenshot_filename; + #define IS_HIGH_VARSEL(wch1, wch2) \ ((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF)) #define IS_LOW_VARSEL(wch) \ @@ -369,7 +374,10 @@ static void start_backend(void) * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (demo_terminal_data) + vt = &null_backend; + else + vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); if (!vt) { char *str = dupprintf("%s Internal Error", appname); MessageBox(NULL, "Unsupported protocol number found", @@ -520,6 +528,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { char *p; bool special_launchable_argument = false; + bool demo_config_box = false; settings_set_default_protocol(be_default_protocol); /* Find the appropriate default port. */ @@ -646,6 +655,30 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints_msgbox(NULL); exit(1); + } else if (!strcmp(p, "-demo-config-box")) { + if (i+1 >= argc) { + cmdline_error("%s expects an output filename", p); + } else { + demo_config_box = true; + dialog_box_demo_screenshot_filename = argv[++i]; + } + } else if (!strcmp(p, "-demo-terminal")) { + if (i+2 >= argc) { + cmdline_error("%s expects input and output filenames", + p); + } else { + const char *infile = argv[++i]; + terminal_demo_screenshot_filename = argv[++i]; + FILE *fp = fopen(infile, "rb"); + if (!fp) + cmdline_error("can't open input file '%s'", argv); + demo_terminal_data = strbuf_new(); + char buf[4096]; + int retd; + while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0) + put_data(demo_terminal_data, buf, retd); + fclose(fp); + } } else if (*p != '-') { cmdline_error("unexpected argument \"%s\"", p); } else { @@ -656,13 +689,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) cmdline_run_saved(conf); - /* - * Bring up the config dialog if the command line hasn't - * (explicitly) specified a launchable configuration. - */ - if (!(special_launchable_argument || cmdline_host_ok(conf))) { - if (!do_config(conf)) - cleanup_exit(0); + if (demo_config_box) { + sesslist_demo_mode = true; + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + do_config(conf); + cleanup_exit(0); + } else if (demo_terminal_data) { + /* Ensure conf will cause an immediate session launch */ + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + conf_set_int(conf, CONF_close_on_exit, FORCE_OFF); + } else { + /* + * Bring up the config dialog if the command line hasn't + * (explicitly) specified a launchable configuration. + */ + if (!(special_launchable_argument || cmdline_host_ok(conf))) { + if (!do_config(conf)) + cleanup_exit(0); + } } prepare_session(conf); @@ -885,6 +931,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); UpdateWindow(wgs.term_hwnd); + gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend); + while (1) { HANDLE *handles; int nhandles, n; @@ -5858,3 +5906,19 @@ static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y) *y = r.bottom - r.top; return true; } + +static void demo_terminal_screenshot(void *ctx, unsigned long now) +{ + HWND hwnd = (HWND)ctx; + save_screenshot(hwnd, terminal_demo_screenshot_filename); + cleanup_exit(0); +} + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ + if (demo_terminal_data) { + ptrlen data = ptrlen_from_strbuf(demo_terminal_data); + seat_stdout(seat, data.ptr, data.len); + schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd); + } +} diff --git a/windows/winpgen.c b/windows/winpgen.c index 56c6a8db..8493c834 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -27,6 +27,8 @@ #define DEFAULT_EDCURVE_INDEX 0 static char *cmdline_keyfile = NULL; +static ptrlen cmdline_demo_keystr; +static const char *demo_screenshot_filename = NULL; /* * Print a modal (Really Bad) message box and perform a fatal exit. @@ -966,6 +968,65 @@ void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) } } +static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, + const char *passphrase, int type, + RSAKey *newkey1, ssh2_userkey *newkey2) +{ + SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, passphrase); + SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase); + + if (type == SSH_KEYTYPE_SSH1) { + char *fingerprint, *savecomment; + + state->ssh2 = false; + state->commentptr = &state->key.comment; + state->key = *newkey1; /* structure copy */ + + /* + * Set the key fingerprint. + */ + savecomment = state->key.comment; + state->key.comment = NULL; + fingerprint = rsa_ssh1_fingerprint(&state->key); + state->key.comment = savecomment; + SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); + sfree(fingerprint); + + /* + * Construct a decimal representation of the key, for pasting + * into .ssh/authorized_keys on a Unix box. + */ + setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->key); + } else { + char *fp; + char *savecomment; + + state->ssh2 = true; + state->commentptr = &state->ssh2key.comment; + state->ssh2key = *newkey2; /* structure copy */ + sfree(newkey2); + + savecomment = state->ssh2key.comment; + state->ssh2key.comment = NULL; + fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + state->ssh2key.comment = savecomment; + + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + + setupbigedit2(hwnd, IDC_KEYDISPLAY, + IDC_PKSTATIC, &state->ssh2key); + } + SetDlgItemText(hwnd, IDC_COMMENTEDIT, + *state->commentptr); + + /* + * Finally, hide the progress bar and show the key data. + */ + ui_set_state(hwnd, state, 2); + state->key_exists = true; +} + void load_key_file(HWND hwnd, struct MainDlgState *state, Filename *filename, bool was_import_cmd) { @@ -1055,65 +1116,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, * Now update the key controls with all the * key data. */ - { - SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, - passphrase); - SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, - passphrase); - if (type == SSH_KEYTYPE_SSH1) { - char *fingerprint, *savecomment; - - state->ssh2 = false; - state->commentptr = &state->key.comment; - state->key = newkey1; - - /* - * Set the key fingerprint. - */ - savecomment = state->key.comment; - state->key.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&state->key); - state->key.comment = savecomment; - SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); - sfree(fingerprint); - - /* - * Construct a decimal representation - * of the key, for pasting into - * .ssh/authorized_keys on a Unix box. - */ - setupbigedit1(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->key); - } else { - char *fp; - char *savecomment; - - state->ssh2 = true; - state->commentptr = - &state->ssh2key.comment; - state->ssh2key = *newkey2; /* structure copy */ - sfree(newkey2); - - savecomment = state->ssh2key.comment; - state->ssh2key.comment = NULL; - fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - state->ssh2key.comment = savecomment; - - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); - } - SetDlgItemText(hwnd, IDC_COMMENTEDIT, - *state->commentptr); - } - /* - * Finally, hide the progress bar and show - * the key data. - */ - ui_set_state(hwnd, state, 2); - state->key_exists = true; + update_ui_after_load(hwnd, state, passphrase, type, &newkey1, newkey2); /* * If the user has imported a foreign key @@ -1179,6 +1182,7 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + const int DEMO_SCREENSHOT_TIMER_ID = 1230; static const char entropy_msg[] = "Please generate some randomness by moving the mouse over the blank area."; struct MainDlgState *state; @@ -1402,9 +1406,33 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, Filename *fn = filename_from_str(cmdline_keyfile); load_key_file(hwnd, state, fn, false); filename_free(fn); + } else if (cmdline_demo_keystr.ptr) { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr); + const char *errmsg; + ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg); + assert(!errmsg); + + update_ui_after_load(hwnd, state, "demo passphrase", + SSH_KEYTYPE_SSH2, NULL, k); + /* BODGE for the 0.66 backport */ + ui_set_key_type(hwnd, state, IDC_KEYSSH2EDDSA); + SetDlgItemInt(hwnd, IDC_BITS, 255, false); + + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); } return 1; + case WM_TIMER: + if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + const char *err = save_screenshot(hwnd, demo_screenshot_filename); + if (err) + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + EndDialog(hwnd, 0); + } + return 0; case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->collecting_entropy && @@ -2006,6 +2034,21 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) !strcmp(argv[i], "-restrict_acl") || !strcmp(argv[i], "-restrictacl")) { restrict_process_acl(); + } else if (!strcmp(argv[i], "-demo-screenshot")) { + demo_screenshot_filename = (i+1 < argc ? argv[++i] : + "puttygen.bmp"); + cmdline_demo_keystr = PTRLEN_LITERAL( + "PuTTY-User-Key-File-3: ssh-ed25519\n" + "Encryption: none\n" + "Comment: ed25519-key-20220402\n" + "Public-Lines: 2\n" + "AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ" + "8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n" + "xa/F\n" + "Private-Lines: 1\n" + "AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n" + "Private-MAC: 1159e9628259b35933b397379bbe8a14" + "a1f1d97fe91e446e45a9581a3408b70e\n"); } else { /* * Assume the first argument to be a private key file, and diff --git a/windows/winstuff.h b/windows/winstuff.h index c0df5a31..b70b3b8d 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -701,4 +701,7 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); bool cliloop_null_post(void *vctx, size_t); +char *save_screenshot(HWND hwnd, const char *outfile); +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); + #endif