diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index bf5f6c8a..d0a331ad 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -14,6 +14,7 @@ #cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID #cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES #cmakedefine01 HAVE_STRTOUMAX +#cmakedefine01 HAVE_DWMAPI_H #cmakedefine NOT_X_WINDOWS diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index c2a760fa..e9e301fb 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -51,6 +51,8 @@ check_symbol_exists(GetNamedPipeClientProcessId "windows.h" HAVE_GETNAMEDPIPECLIENTPROCESSID) check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY) +check_include_files("windows.h;dwmapi.h" HAVE_DWMAPI_H) + check_c_source_compiles(" #include GCP_RESULTSW gcpw; diff --git a/otherbackends/testback.c b/otherbackends/testback.c index dbe282ec..f46d1d98 100644 --- a/otherbackends/testback.c +++ b/otherbackends/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 void null_send(Backend *, const char *, size_t); @@ -55,8 +52,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, @@ -106,17 +103,6 @@ struct loop_state { size_t sendbuffer; }; -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, @@ -127,15 +113,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/settings.c b/settings.c index ff2bb6c4..98313d17 100644 --- a/settings.c +++ b/settings.c @@ -1307,6 +1307,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; @@ -1316,12 +1318,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/windows/CMakeLists.txt b/windows/CMakeLists.txt index aad2f5af..b988792a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -28,6 +28,7 @@ add_sources_from_current_dir(utils utils/platform_get_x_display.c utils/registry_get_string.c utils/request_file.c + utils/screenshot.c utils/security.c utils/split_into_argv.c utils/version.c diff --git a/windows/dialog.c b/windows/dialog.c index 31bf19e6..38af0f9b 100644 --- a/windows/dialog.c +++ b/windows/dialog.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/platform.h b/windows/platform.h index 52970239..07a82f81 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -759,4 +759,7 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val); bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); bool aux_match_done(AuxMatchOpt *amo); +char *save_screenshot(HWND hwnd, const char *outfile); +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/pterm.c b/windows/pterm.c index 2cdef30c..0df849f9 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -45,3 +45,7 @@ const wchar_t *get_app_user_model_id(void) { return L"SimonTatham.Pterm"; } + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ +} diff --git a/windows/putty.c b/windows/putty.c index b17ad7dc..83594d61 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -1,10 +1,16 @@ #include "putty.h" #include "storage.h" +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; + void gui_term_process_cmdline(Conf *conf, char *cmdline) { char *p; bool special_launchable_argument = false; + bool demo_config_box = false; settings_set_default_protocol(be_default_protocol); /* Find the appropriate default port. */ @@ -81,6 +87,29 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) } 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'", infile); + 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 { @@ -91,13 +120,26 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) 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); @@ -105,6 +147,10 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) const struct BackendVtable *backend_vt_from_conf(Conf *conf) { + if (demo_terminal_data) { + return &null_backend; + } + /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. @@ -125,3 +171,19 @@ const wchar_t *get_app_user_model_id(void) { return L"SimonTatham.PuTTY"; } + +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/puttygen.c b/windows/puttygen.c index 6cda5817..281d17c0 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.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. @@ -992,6 +994,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) { @@ -1081,65 +1142,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 @@ -1205,6 +1208,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; @@ -1428,9 +1432,30 @@ 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); + + 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->entropy && state->entropy_got < state->entropy_required) { @@ -2175,6 +2200,22 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unrecognised PPK parameter '%s'\n", val); } } + } else if (match_optval("-demo-screenshot")) { + demo_screenshot_filename = val; + 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"); + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 255; } else { opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } diff --git a/windows/utils/screenshot.c b/windows/utils/screenshot.c new file mode 100644 index 00000000..777520fd --- /dev/null +++ b/windows/utils/screenshot.c @@ -0,0 +1,126 @@ +#include "putty.h" + +#if HAVE_DWMAPI_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; +} + +#else /* HAVE_DWMAPI_H */ + +/* Without we can't get the right window rectangle */ +char *save_screenshot(HWND hwnd, const char *outfile) +{ + return dupstr("Demo screenshots not compiled in to this build"); +} + +#endif /* HAVE_DWMAPI_H */ diff --git a/windows/window.c b/windows/window.c index d9d44906..bd13d228 100644 --- a/windows/window.c +++ b/windows/window.c @@ -810,6 +810,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) { int n; DWORD timeout;