|
39 | 39 | #include "pattern.h" |
40 | 40 | #include "util.h" |
41 | 41 |
|
| 42 | +#if !defined(_WIN32) |
| 43 | +#include <unistd.h> |
| 44 | +#include <sys/wait.h> |
| 45 | +#endif |
| 46 | + |
42 | 47 | const char *vg_b58_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; |
43 | 48 |
|
44 | 49 | const signed char vg_b58_reverse_map[256] = { |
@@ -1086,3 +1091,163 @@ vg_read_file(FILE *fp, char ***result, int *rescount) |
1086 | 1091 |
|
1087 | 1092 | return ret; |
1088 | 1093 | } |
| 1094 | + |
| 1095 | + |
| 1096 | +#if !defined(_WIN32) |
| 1097 | +/* |
| 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). |
| 1103 | + * |
| 1104 | + * Returns 1 on success, 0 if xclip is unavailable or failed. |
| 1105 | + */ |
| 1106 | +static int |
| 1107 | +vg_clipboard_copy_xclip(const char *secret, int timeout_secs) |
| 1108 | +{ |
| 1109 | + FILE *fp; |
| 1110 | + pid_t pid; |
| 1111 | + char cmd[256]; |
| 1112 | + int status; |
| 1113 | + |
| 1114 | + if (system("command -v xclip >/dev/null 2>&1") != 0) |
| 1115 | + return 0; |
| 1116 | + |
| 1117 | + fp = popen("xclip -selection clipboard 2>/dev/null", "w"); |
| 1118 | + if (!fp) |
| 1119 | + return 0; |
| 1120 | + fputs(secret, fp); |
| 1121 | + status = pclose(fp); |
| 1122 | + if (status != 0) |
| 1123 | + return 0; |
| 1124 | + |
| 1125 | + pid = fork(); |
| 1126 | + if (pid < 0) |
| 1127 | + /* Copy succeeded even though we can't schedule the clear */ |
| 1128 | + return 1; |
| 1129 | + |
| 1130 | + if (pid == 0) { |
| 1131 | + /* Detach a grandchild so the parent doesn't have to wait */ |
| 1132 | + if (fork() > 0) |
| 1133 | + _exit(0); |
| 1134 | + setsid(); |
| 1135 | + close(0); |
| 1136 | + close(1); |
| 1137 | + close(2); |
| 1138 | + |
| 1139 | + if (setenv("VG_CLIP_SECRET", secret, 1) == 0) { |
| 1140 | + snprintf(cmd, sizeof(cmd), |
| 1141 | + "sleep %d; " |
| 1142 | + "[ \"$(xclip -selection clipboard -o " |
| 1143 | + "2>/dev/null)\" = \"$VG_CLIP_SECRET\" ] && " |
| 1144 | + "printf '' | xclip -selection clipboard", |
| 1145 | + timeout_secs); |
| 1146 | + system(cmd); |
| 1147 | + } |
| 1148 | + _exit(0); |
| 1149 | + } |
| 1150 | + |
| 1151 | + /* Reap the intermediate child, which exits immediately */ |
| 1152 | + waitpid(pid, NULL, 0); |
| 1153 | + 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; |
| 1252 | +#endif |
| 1253 | +} |
0 commit comments