aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/plink/cmdline.c1
-rw-r--r--tools/plink/putty.h17
-rw-r--r--tools/plink/settings.c7
-rw-r--r--tools/plink/ssh.c8
-rw-r--r--tools/plink/version.c2
-rw-r--r--tools/plink/winhelp.h1
-rw-r--r--tools/plink/winpgntc.c91
-rw-r--r--tools/plink/winstore.c173
-rw-r--r--tools/plink/winstuff.h48
9 files changed, 342 insertions, 6 deletions
diff --git a/tools/plink/cmdline.c b/tools/plink/cmdline.c
index 6492132f1..aa376a053 100644
--- a/tools/plink/cmdline.c
+++ b/tools/plink/cmdline.c
@@ -172,6 +172,7 @@ int cmdline_process_param(char *p, char *value, int need_save, Config *cfg)
* saved. */
do_defaults(value, cfg);
loaded_session = TRUE;
+ cmdline_session_name = dupstr(value);
return 2;
}
if (!strcmp(p, "-ssh")) {
diff --git a/tools/plink/putty.h b/tools/plink/putty.h
index ac2701133..c72d8eb76 100644
--- a/tools/plink/putty.h
+++ b/tools/plink/putty.h
@@ -470,6 +470,7 @@ struct config_tag {
int sshprot; /* use v1 or v2 when both available */
int ssh2_des_cbc; /* "des-cbc" unrecommended SSH-2 cipher */
int ssh_no_userauth; /* bypass "ssh-userauth" (SSH-2 only) */
+ int ssh_show_banner; /* show USERAUTH_BANNERs (SSH-2 only) */
int try_tis_auth;
int try_ki_auth;
int try_gssapi_auth; /* attempt gssapi auth */
@@ -625,6 +626,7 @@ struct config_tag {
FontSpec wideboldfont;
int shadowboldoffset;
int crhaslf;
+ char winclass[256];
};
/*
@@ -666,6 +668,10 @@ GLOBAL int default_port;
* This is set TRUE by cmdline.c iff a session is loaded with "-load".
*/
GLOBAL int loaded_session;
+/*
+ * This is set to the name of the loaded session.
+ */
+GLOBAL char *cmdline_session_name;
struct RSAKey; /* be a little careful of scope */
@@ -1246,4 +1252,15 @@ void expire_timer_context(void *ctx);
int run_timers(long now, long *next);
void timer_change_notify(long next);
+/*
+ * Define no-op macros for the jump list functions, on platforms that
+ * don't support them. (This is a bit of a hack, and it'd be nicer to
+ * localise even the calls to those functions into the Windows front
+ * end, but it'll do for the moment.)
+ */
+#ifndef JUMPLIST_SUPPORTED
+#define add_session_to_jumplist(x) ((void)0)
+#define remove_session_from_jumplist(x) ((void)0)
+#endif
+
#endif
diff --git a/tools/plink/settings.c b/tools/plink/settings.c
index bd6b97495..46e19f49c 100644
--- a/tools/plink/settings.c
+++ b/tools/plink/settings.c
@@ -348,6 +348,7 @@ void save_open_settings(void *sesskey, Config *cfg)
write_setting_i(sesskey, "RekeyTime", cfg->ssh_rekey_time);
write_setting_s(sesskey, "RekeyBytes", cfg->ssh_rekey_data);
write_setting_i(sesskey, "SshNoAuth", cfg->ssh_no_userauth);
+ write_setting_i(sesskey, "SshBanner", cfg->ssh_show_banner);
write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth);
write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth);
write_setting_i(sesskey, "AuthGSSAPI", cfg->try_gssapi_auth);
@@ -498,6 +499,7 @@ void save_open_settings(void *sesskey, Config *cfg)
write_setting_i(sesskey, "SerialStopHalfbits", cfg->serstopbits);
write_setting_i(sesskey, "SerialParity", cfg->serparity);
write_setting_i(sesskey, "SerialFlowControl", cfg->serflow);
+ write_setting_s(sesskey, "WindowClass", cfg->winclass);
}
void load_settings(char *section, Config * cfg)
@@ -507,6 +509,9 @@ void load_settings(char *section, Config * cfg)
sesskey = open_settings_r(section);
load_open_settings(sesskey, cfg);
close_settings_r(sesskey);
+
+ if (cfg_launchable(cfg))
+ add_session_to_jumplist(section);
}
void load_open_settings(void *sesskey, Config *cfg)
@@ -642,6 +647,7 @@ void load_open_settings(void *sesskey, Config *cfg)
gpps(sesskey, "LogHost", "", cfg->loghost, sizeof(cfg->loghost));
gppi(sesskey, "SSH2DES", 0, &cfg->ssh2_des_cbc);
gppi(sesskey, "SshNoAuth", 0, &cfg->ssh_no_userauth);
+ gppi(sesskey, "SshBanner", 1, &cfg->ssh_show_banner);
gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth);
gppi(sesskey, "AuthKI", 1, &cfg->try_ki_auth);
gppi(sesskey, "AuthGSSAPI", 1, &cfg->try_gssapi_auth);
@@ -853,6 +859,7 @@ void load_open_settings(void *sesskey, Config *cfg)
gppi(sesskey, "SerialStopHalfbits", 2, &cfg->serstopbits);
gppi(sesskey, "SerialParity", SER_PAR_NONE, &cfg->serparity);
gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, &cfg->serflow);
+ gpps(sesskey, "WindowClass", "", cfg->winclass, sizeof(cfg->winclass));
}
void do_defaults(char *session, Config * cfg)
diff --git a/tools/plink/ssh.c b/tools/plink/ssh.c
index 7c9b929de..0982f84a4 100644
--- a/tools/plink/ssh.c
+++ b/tools/plink/ssh.c
@@ -2862,6 +2862,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
x11_close(c->u.x11.s);
break;
case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
pfd_close(c->u.pfd.s);
break;
}
@@ -7194,12 +7195,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
}
/*
- * Buffer banner messages for later display at some convenient point.
+ * Buffer banner messages for later display at some convenient point,
+ * if we're going to display them.
*/
static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
{
/* Arbitrary limit to prevent unbounded inflation of buffer */
- if (bufchain_size(&ssh->banner) <= 131072) {
+ if (ssh->cfg.ssh_show_banner &&
+ bufchain_size(&ssh->banner) <= 131072) {
char *banner = NULL;
int size = 0;
ssh_pkt_getstring(pktin, &banner, &size);
@@ -9355,6 +9358,7 @@ static void ssh_free(void *handle)
x11_close(c->u.x11.s);
break;
case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
if (c->u.pfd.s != NULL)
pfd_close(c->u.pfd.s);
break;
diff --git a/tools/plink/version.c b/tools/plink/version.c
index 483495771..bcb233272 100644
--- a/tools/plink/version.c
+++ b/tools/plink/version.c
@@ -5,7 +5,7 @@
#define STR1(x) #x
#define STR(x) STR1(x)
-#define SVN_REV 9025
+#define SVN_REV 9080
#if defined SNAPSHOT
diff --git a/tools/plink/winhelp.h b/tools/plink/winhelp.h
index 9ee140fef..d670c0cb8 100644
--- a/tools/plink/winhelp.h
+++ b/tools/plink/winhelp.h
@@ -102,6 +102,7 @@
#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
+#define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner"
#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey"
#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd:config-ssh-agentfwd"
#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser:config-ssh-changeuser"
diff --git a/tools/plink/winpgntc.c b/tools/plink/winpgntc.c
index 3bfafc972..2a5aa734f 100644
--- a/tools/plink/winpgntc.c
+++ b/tools/plink/winpgntc.c
@@ -7,6 +7,10 @@
#include "putty.h"
+#ifndef NO_SECURITY
+#include <aclapi.h>
+#endif
+
#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
#define AGENT_MAX_MSGLEN 8192
@@ -66,6 +70,33 @@ DWORD WINAPI agent_query_thread(LPVOID param)
#endif
+/*
+ * Dynamically load advapi32.dll for SID manipulation. In its absence,
+ * we degrade gracefully.
+ */
+#ifndef NO_SECURITY
+int advapi_initialised = FALSE;
+static HMODULE advapi;
+DECL_WINDOWS_FUNCTION(static, BOOL, OpenProcessToken,
+ (HANDLE, DWORD, PHANDLE));
+DECL_WINDOWS_FUNCTION(static, BOOL, GetTokenInformation,
+ (HANDLE, TOKEN_INFORMATION_CLASS,
+ LPVOID, DWORD, PDWORD));
+DECL_WINDOWS_FUNCTION(static, BOOL, InitializeSecurityDescriptor,
+ (PSECURITY_DESCRIPTOR, DWORD));
+DECL_WINDOWS_FUNCTION(static, BOOL, SetSecurityDescriptorOwner,
+ (PSECURITY_DESCRIPTOR, PSID, BOOL));
+static int init_advapi(void)
+{
+ advapi = load_system32_dll("advapi32.dll");
+ return advapi &&
+ GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
+ GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
+ GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
+ GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner);
+}
+#endif
+
int agent_query(void *in, int inlen, void **out, int *outlen,
void (*callback)(void *, void *, int), void *callback_ctx)
{
@@ -75,6 +106,10 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
unsigned char *p, *ret;
int id, retlen;
COPYDATASTRUCT cds;
+ SECURITY_ATTRIBUTES sa, *psa;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ HANDLE proc, tok;
+ TOKEN_USER *user = NULL;
*out = NULL;
*outlen = 0;
@@ -83,7 +118,57 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
if (!hwnd)
return 1; /* *out == NULL, so failure */
mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
- filemap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
+
+#ifndef NO_SECURITY
+ if (advapi_initialised || init_advapi()) {
+ /*
+ * Make the file mapping we create for communication with
+ * Pageant owned by the user SID rather than the default. This
+ * should make communication between processes with slightly
+ * different contexts more reliable: in particular, command
+ * prompts launched as administrator should still be able to
+ * run PSFTPs which refer back to the owning user's
+ * unprivileged Pageant.
+ */
+
+ if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
+ GetCurrentProcessId())) != NULL) {
+ if (p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) {
+ DWORD retlen;
+ p_GetTokenInformation(tok, TokenUser, NULL, 0, &retlen);
+ user = (TOKEN_USER *)LocalAlloc(LPTR, retlen);
+ if (!p_GetTokenInformation(tok, TokenUser,
+ user, retlen, &retlen)) {
+ LocalFree(user);
+ user = NULL;
+ }
+ CloseHandle(tok);
+ }
+ CloseHandle(proc);
+ }
+
+ psa = NULL;
+ if (user) {
+ psd = (PSECURITY_DESCRIPTOR)
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (psd) {
+ if (p_InitializeSecurityDescriptor
+ (psd, SECURITY_DESCRIPTOR_REVISION) &&
+ p_SetSecurityDescriptorOwner(psd, user->User.Sid, FALSE)) {
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = psd;
+ psa = &sa;
+ } else {
+ LocalFree(psd);
+ psd = NULL;
+ }
+ }
+ }
+ }
+#endif /* NO_SECURITY */
+
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
0, AGENT_MAX_MSGLEN, mapname);
if (filemap == NULL || filemap == INVALID_HANDLE_VALUE)
return 1; /* *out == NULL, so failure */
@@ -134,5 +219,9 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
}
UnmapViewOfFile(p);
CloseHandle(filemap);
+ if (psd)
+ LocalFree(psd);
+ if (user)
+ LocalFree(user);
return 1;
}
diff --git a/tools/plink/winstore.c b/tools/plink/winstore.c
index f7dd98965..13ee184a5 100644
--- a/tools/plink/winstore.c
+++ b/tools/plink/winstore.c
@@ -17,6 +17,8 @@
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
+static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
+static const char *const reg_jumplist_value = "Recent sessions";
static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
static const char hex[16] = "0123456789ABCDEF";
@@ -243,6 +245,8 @@ void del_settings(const char *sessionname)
sfree(p);
RegCloseKey(subkey1);
+
+ remove_session_from_jumplist(sessionname);
}
struct enumsettings {
@@ -582,6 +586,169 @@ void write_random_seed(void *data, int len)
}
/*
+ * Internal function supporting the jump list registry code. All the
+ * functions to add, remove and read the list have substantially
+ * similar content, so this is a generalisation of all of them which
+ * transforms the list in the registry by prepending 'add' (if
+ * non-null), removing 'rem' from what's left (if non-null), and
+ * returning the resulting concatenated list of strings in 'out' (if
+ * non-null).
+ */
+static int transform_jumplist_registry
+ (const char *add, const char *rem, char **out)
+{
+ int ret;
+ HKEY pjumplist_key, psettings_tmp;
+ DWORD type;
+ int value_length;
+ char *old_value, *new_value;
+ char *piterator_old, *piterator_new, *piterator_tmp;
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
+ &pjumplist_key, NULL);
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
+ }
+
+ /* Get current list of saved sessions in the registry. */
+ value_length = 200;
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ /* When the passed buffer is too small, ERROR_MORE_DATA is
+ * returned and the required size is returned in the length
+ * argument. */
+ if (ret == ERROR_MORE_DATA) {
+ sfree(old_value);
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ }
+
+ if (ret == ERROR_FILE_NOT_FOUND) {
+ /* Value doesn't exist yet. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ } else if (ret != ERROR_SUCCESS) {
+ /* Some non-recoverable error occurred. */
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ } else if (type != REG_MULTI_SZ) {
+ /* The value present in the registry has the wrong type: we
+ * try to delete it and start from an empty value. */
+ ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
+ if (ret != ERROR_SUCCESS) {
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ }
+
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /* Check validity of registry data: REG_MULTI_SZ value must end
+ * with \0\0. */
+ piterator_tmp = old_value;
+ while (((piterator_tmp - old_value) < (value_length - 1)) &&
+ !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
+ ++piterator_tmp;
+ }
+
+ if ((piterator_tmp - old_value) >= (value_length-1)) {
+ /* Invalid value. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /*
+ * Modify the list, if we're modifying.
+ */
+ if (add || rem) {
+ /* Walk through the existing list and construct the new list of
+ * saved sessions. */
+ new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
+ piterator_new = new_value;
+ piterator_old = old_value;
+
+ /* First add the new item to the beginning of the list. */
+ if (add) {
+ strcpy(piterator_new, add);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ /* Now add the existing list, taking care to leave out the removed
+ * item, if it was already in the existing list. */
+ while (*piterator_old != '\0') {
+ if (!rem || strcmp(piterator_old, rem) != 0) {
+ /* Check if this is a valid session, otherwise don't add. */
+ psettings_tmp = open_settings_r(piterator_old);
+ if (psettings_tmp != NULL) {
+ close_settings_r(psettings_tmp);
+ strcpy(piterator_new, piterator_old);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ }
+ piterator_old += strlen(piterator_old) + 1;
+ }
+ *piterator_new = '\0';
+ ++piterator_new;
+
+ /* Save the new list to the registry. */
+ ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
+ new_value, piterator_new - new_value);
+
+ sfree(old_value);
+ old_value = new_value;
+ } else
+ ret = ERROR_SUCCESS;
+
+ /*
+ * Either return or free the result.
+ */
+ if (out)
+ *out = old_value;
+ else
+ sfree(old_value);
+
+ /* Clean up and return. */
+ RegCloseKey(pjumplist_key);
+
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
+ } else {
+ return JUMPLISTREG_OK;
+ }
+}
+
+/* Adds a new entry to the jumplist entries in the registry. */
+int add_to_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(item, item, NULL);
+}
+
+/* Removes an item from the jumplist entries in the registry. */
+int remove_from_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(NULL, item, NULL);
+}
+
+/* Returns the jumplist entries from the registry. Caller must free
+ * the returned pointer. */
+char *get_jumplist_registry_entries (void)
+{
+ char *list_value;
+
+ if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) {
+ list_value = snewn(2, char);
+ *list_value = '\0';
+ *(list_value + 1) = '\0';
+ }
+ return list_value;
+}
+
+/*
* Recursively delete a registry key and everything under it.
*/
static void registry_recursive_remove(HKEY key)
@@ -613,6 +780,12 @@ void cleanup_all(void)
access_random_seed(DEL);
/* ------------------------------------------------------------
+ * Ask Windows to delete any jump list information associated
+ * with this installation of PuTTY.
+ */
+ clear_jumplist();
+
+ /* ------------------------------------------------------------
* Destroy all registry information associated with PuTTY.
*/
diff --git a/tools/plink/winstuff.h b/tools/plink/winstuff.h
index eeceea17a..1cc48348e 100644
--- a/tools/plink/winstuff.h
+++ b/tools/plink/winstuff.h
@@ -96,9 +96,9 @@ struct FontSpec {
#define STR1(x) #x
#define STR(x) STR1(x)
#define GET_WINDOWS_FUNCTION_PP(module, name) \
- p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL
+ (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL)
#define GET_WINDOWS_FUNCTION(module, name) \
- p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL
+ (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL)
/*
* Global variables. Most modules declare these `extern', but
@@ -126,6 +126,14 @@ typedef struct terminal_tag Terminal;
#define PUTTY_REG_GPARENT "Software"
#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
+/* Result values for the jumplist registry functions. */
+#define JUMPLISTREG_OK 0
+#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
+#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
+#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
+#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
+#define JUMPLISTREG_ERROR_INVALID_VALUE 5
+
#define PUTTY_HELP_FILE "putty.hlp"
#define PUTTY_CHM_FILE "putty.chm"
#define PUTTY_HELP_CONTENTS "putty.cnt"
@@ -308,6 +316,7 @@ struct dlgparam {
struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
tree234 *privdata; /* stores per-control private data */
int ended, endresult; /* has the dialog been ended? */
+ int fixed_pitch_fonts; /* are we constrained to fixed fonts? */
};
/*
@@ -366,6 +375,10 @@ void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
char *btext, int bid,
char *r1text, int r1id, char *r2text, int r2id);
+void dlg_auto_set_fixed_pitch_flag(void *dlg);
+int dlg_get_fixed_pitch_flag(void *dlg);
+void dlg_set_fixed_pitch_flag(void *dlg, int flag);
+
#define MAX_SHORTCUTS_PER_CTRL 16
/*
@@ -500,4 +513,35 @@ void agent_schedule_callback(void (*callback)(void *, void *, int),
*/
extern Backend serial_backend;
+/*
+ * Exports from winjump.c.
+ */
+#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */
+void add_session_to_jumplist(const char * const sessionname);
+void remove_session_from_jumplist(const char * const sessionname);
+void clear_jumplist(void);
+
+/*
+ * Extra functions in winstore.c over and above the interface in
+ * storage.h.
+ *
+ * These functions manipulate the Registry section which mirrors the
+ * current Windows 7 jump list. (Because the real jump list storage is
+ * write-only, we need to keep another copy of whatever we put in it,
+ * so that we can put in a slightly modified version the next time.)
+ */
+
+/* Adds a saved session to the registry jump list mirror. 'item' is a
+ * string naming a saved session. */
+int add_to_jumplist_registry(const char *item);
+
+/* Removes an item from the registry jump list mirror. */
+int remove_from_jumplist_registry(const char *item);
+
+/* Returns the current jump list entries from the registry. Caller
+ * must free the returned pointer, which points to a contiguous
+ * sequence of NUL-terminated strings in memory, terminated with an
+ * empty one. */
+char *get_jumplist_registry_entries(void);
+
#endif