1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Backport of the pre-0.77 demo features to 0.76.

This is a combined cherry-pick of three commits on the 0.77 branch:

896bcd5068 "Resurrect the test backends"
9294ee3496 "Windows PuTTYgen: saw load_key_file in half"
bc7e06c494 "Windows tools: assorted '-demo' options"

Of course, these commits aren't in 0.76 itself, and I don't see any
reason to publish a binary build of 0.76 containing them. This is just
a branch I can build from myself to get a version of PuTTY that will
look exactly like the real 0.76 but permit automated generation of
demo screenshots.
This commit is contained in:
Simon Tatham 2022-04-02 16:13:27 +01:00
parent 1fd7baa734
commit 11192e2e33
8 changed files with 331 additions and 93 deletions

3
Recipe
View File

@ -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

View File

@ -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);
/*

View File

@ -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);

114
windows/screenshot.c Normal file
View File

@ -0,0 +1,114 @@
#include "putty.h"
#include <dwmapi.h>
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;
}

View File

@ -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:
/*

View File

@ -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);
}
}

View File

@ -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

View File

@ -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