Skip to content

Commit 348e487

Browse files
committed
Add OSC 52 terminal clipboard fallback for -c
xclip requires an X11 display, so -c silently did nothing useful over a plain SSH session into a headless box. Fall back to the OSC 52 terminal escape sequence (supported by Windows Terminal, PuTTY, mintty, iTerm2, kitty, ...) when no X display is reachable, and report which clipboard method was used in the output.
1 parent dd3e11c commit 348e487

4 files changed

Lines changed: 126 additions & 35 deletions

File tree

pattern.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,10 @@ vg_output_match_console(vg_context_t *vcp, EC_KEY *pkey, const char *pattern)
554554
}
555555

556556
int clipped = vcp->vc_clip ?
557-
vg_clipboard_copy_secret(privkey_buf, 45) : 0;
557+
vg_clipboard_copy_secret(privkey_buf, 45) : VG_CLIPBOARD_NONE;
558+
const char *clip_method =
559+
(clipped == VG_CLIPBOARD_XCLIP) ? "xclip" :
560+
(clipped == VG_CLIPBOARD_OSC52) ? "terminal (OSC 52)" : "";
558561

559562
if (!vcp->vc_result_file || (vcp->vc_verbose > 0)) {
560563
printf("\r%79s\rPattern: %s\n", "", pattern);
@@ -568,8 +571,8 @@ vg_output_match_console(vg_context_t *vcp, EC_KEY *pkey, const char *pattern)
568571
dumphex(key_buf, len);
569572
if (clipped) {
570573
printf("Privkey (hex): "
571-
"(copied to clipboard, "
572-
"clearing in 45s)\n");
574+
"(copied to clipboard via %s, "
575+
"clearing in 45s)\n", clip_method);
573576
} else {
574577
printf("Privkey (hex): ");
575578
dumpbn(EC_KEY_get0_private_key(pkey));
@@ -587,8 +590,9 @@ vg_output_match_console(vg_context_t *vcp, EC_KEY *pkey, const char *pattern)
587590
printf("P2SHAddress: %s\n", addr2_buf);
588591
if (clipped) {
589592
printf("Address: %s\n"
590-
"%s: (copied to clipboard, clearing in 45s)\n",
591-
addr_buf, keytype);
593+
"%s: (copied to clipboard via %s, "
594+
"clearing in 45s)\n",
595+
addr_buf, keytype, clip_method);
592596
} else {
593597
printf("Address: %s\n"
594598
"%s: %s\n",

util.c

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,50 +1093,34 @@ vg_read_file(FILE *fp, char ***result, int *rescount)
10931093
}
10941094

10951095

1096+
#if !defined(_WIN32)
10961097
/*
1097-
* Copy a secret (e.g. a private key) to the X11 clipboard using xclip,
1098-
* and schedule it to be cleared after timeout_secs -- but only if the
1099-
* clipboard still holds exactly what we put there, so we don't clobber
1100-
* something else the user copied in the meantime. Modeled after the
1101-
* clipboard handling in "pass" (passwordstore.org).
1098+
* Copy a secret to the X11 clipboard using xclip, and schedule it to be
1099+
* cleared after timeout_secs -- but only if the clipboard still holds
1100+
* exactly what we put there, so we don't clobber something else the user
1101+
* copied in the meantime. Modeled after the clipboard handling in "pass"
1102+
* (passwordstore.org).
11021103
*
1103-
* Returns 1 if the secret was copied to the clipboard, 0 if no clipboard
1104-
* tool was available (in which case the caller should fall back to
1105-
* printing the secret).
1104+
* Returns 1 on success, 0 if xclip is unavailable or failed.
11061105
*/
1107-
int
1108-
vg_clipboard_copy_secret(const char *secret, int timeout_secs)
1106+
static int
1107+
vg_clipboard_copy_xclip(const char *secret, int timeout_secs)
11091108
{
1110-
#if defined(_WIN32)
1111-
return 0;
1112-
#else
11131109
FILE *fp;
11141110
pid_t pid;
11151111
char cmd[256];
11161112
int status;
11171113

1118-
if (system("command -v xclip >/dev/null 2>&1") != 0) {
1119-
fprintf(stderr,
1120-
"vanitygen: xclip not found in PATH, "
1121-
"printing private key instead\n");
1114+
if (system("command -v xclip >/dev/null 2>&1") != 0)
11221115
return 0;
1123-
}
11241116

1125-
fp = popen("xclip -selection clipboard", "w");
1126-
if (!fp) {
1127-
fprintf(stderr,
1128-
"vanitygen: could not run xclip (%s), "
1129-
"printing private key instead\n", strerror(errno));
1117+
fp = popen("xclip -selection clipboard 2>/dev/null", "w");
1118+
if (!fp)
11301119
return 0;
1131-
}
11321120
fputs(secret, fp);
11331121
status = pclose(fp);
1134-
if (status != 0) {
1135-
fprintf(stderr,
1136-
"vanitygen: xclip exited with status %d, "
1137-
"printing private key instead\n", status);
1122+
if (status != 0)
11381123
return 0;
1139-
}
11401124

11411125
pid = fork();
11421126
if (pid < 0)
@@ -1167,5 +1151,103 @@ vg_clipboard_copy_secret(const char *secret, int timeout_secs)
11671151
/* Reap the intermediate child, which exits immediately */
11681152
waitpid(pid, NULL, 0);
11691153
return 1;
1154+
}
1155+
1156+
/*
1157+
* Copy a secret to the terminal's clipboard using the OSC 52 escape
1158+
* sequence, understood by most modern terminal emulators (Windows
1159+
* Terminal, iTerm2, kitty, xterm, mintty, ...) without requiring an X11
1160+
* server -- this is what makes it work over a plain SSH session into a
1161+
* headless box. Scheduled clearing has no readback to compare against
1162+
* (OSC 52 has no reliable, portable query), so it unconditionally sends
1163+
* an empty payload after timeout_secs, which terminals interpret as
1164+
* "clear the clipboard".
1165+
*
1166+
* Returns 1 on success, 0 if no terminal is attached.
1167+
*/
1168+
static int
1169+
vg_clipboard_copy_osc52(const char *secret, int timeout_secs)
1170+
{
1171+
FILE *tty;
1172+
char *b64;
1173+
int b64_len;
1174+
pid_t pid;
1175+
1176+
tty = fopen("/dev/tty", "w");
1177+
if (!tty)
1178+
return 0;
1179+
1180+
b64_len = 4 * ((strlen(secret) + 2) / 3) + 1;
1181+
b64 = malloc(b64_len);
1182+
if (!b64) {
1183+
fclose(tty);
1184+
return 0;
1185+
}
1186+
EVP_EncodeBlock((unsigned char *)b64,
1187+
(const unsigned char *)secret, strlen(secret));
1188+
1189+
fprintf(tty, "\x1b]52;c;%s\x07", b64);
1190+
fflush(tty);
1191+
fclose(tty);
1192+
free(b64);
1193+
1194+
pid = fork();
1195+
if (pid < 0)
1196+
/* Copy succeeded even though we can't schedule the clear */
1197+
return 1;
1198+
1199+
if (pid == 0) {
1200+
/* Detach a grandchild so the parent doesn't have to wait */
1201+
if (fork() > 0)
1202+
_exit(0);
1203+
setsid();
1204+
close(0);
1205+
close(1);
1206+
close(2);
1207+
1208+
sleep(timeout_secs);
1209+
tty = fopen("/dev/tty", "w");
1210+
if (tty) {
1211+
fprintf(tty, "\x1b]52;c;\x07");
1212+
fflush(tty);
1213+
fclose(tty);
1214+
}
1215+
_exit(0);
1216+
}
1217+
1218+
/* Reap the intermediate child, which exits immediately */
1219+
waitpid(pid, NULL, 0);
1220+
return 1;
1221+
}
1222+
#endif /* !defined(_WIN32) */
1223+
1224+
/*
1225+
* Copy a secret (e.g. a private key) to the clipboard, preferring the X11
1226+
* clipboard via xclip when a display is available, and falling back to
1227+
* the OSC 52 terminal escape sequence otherwise (e.g. over a plain SSH
1228+
* session with no X server).
1229+
*
1230+
* Returns VG_CLIPBOARD_XCLIP or VG_CLIPBOARD_OSC52 if the secret was
1231+
* copied to the clipboard, VG_CLIPBOARD_NONE if no clipboard mechanism
1232+
* was available (in which case the caller should fall back to printing
1233+
* the secret).
1234+
*/
1235+
int
1236+
vg_clipboard_copy_secret(const char *secret, int timeout_secs)
1237+
{
1238+
#if defined(_WIN32)
1239+
return VG_CLIPBOARD_NONE;
1240+
#else
1241+
if (getenv("DISPLAY") &&
1242+
vg_clipboard_copy_xclip(secret, timeout_secs))
1243+
return VG_CLIPBOARD_XCLIP;
1244+
1245+
if (vg_clipboard_copy_osc52(secret, timeout_secs))
1246+
return VG_CLIPBOARD_OSC52;
1247+
1248+
fprintf(stderr,
1249+
"vanitygen: no clipboard available, "
1250+
"printing private key instead\n");
1251+
return VG_CLIPBOARD_NONE;
11701252
#endif
11711253
}

util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ extern int vg_check_password_complexity(const char *pass, int verbose);
7575

7676
extern int vg_read_file(FILE *fp, char ***result, int *rescount);
7777

78+
#define VG_CLIPBOARD_NONE 0
79+
#define VG_CLIPBOARD_XCLIP 1
80+
#define VG_CLIPBOARD_OSC52 2
81+
7882
extern int vg_clipboard_copy_secret(const char *secret, int timeout_secs);
7983

8084
#endif /* !defined (__VG_UTIL_H__) */

vanitygen.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ usage(const char *name)
312312
"-k Keep pattern and continue search after finding a match\n"
313313
"-1 Stop after first match\n"
314314
"-c Copy private key to the clipboard instead of printing it\n"
315-
" (cleared automatically after 45 seconds; requires xclip)\n"
315+
" (cleared automatically after 45 seconds; uses xclip if an\n"
316+
" X display is available, otherwise the terminal clipboard)\n"
316317
"-N Generate namecoin address\n"
317318
"-T Generate bitcoin testnet address\n"
318319
"-X <version> Generate address with the given version\n"

0 commit comments

Comments
 (0)