2 * Platform-independent routines shared between all PuTTY programs.
\r
4 * This file contains functions that use the kind of infrastructure
\r
5 * like conf.c that tends to only live in the main applications, or
\r
6 * that do things that only something like a main PuTTY application
\r
7 * would need. So standalone test programs should generally be able to
\r
8 * avoid linking against it.
\r
10 * More standalone functions that depend on nothing but the C library
\r
25 #define BASE64_CHARS_NOEQ \
\r
26 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
\r
27 #define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "="
\r
29 void seat_connection_fatal(Seat *seat, const char *fmt, ...)
\r
35 msg = dupvprintf(fmt, ap);
\r
38 seat->vt->connection_fatal(seat, msg);
\r
39 sfree(msg); /* if we return */
\r
42 prompts_t *new_prompts(void)
\r
44 prompts_t *p = snew(prompts_t);
\r
46 p->n_prompts = p->prompts_size = 0;
\r
48 p->to_server = true; /* to be on the safe side */
\r
49 p->name = p->instruction = NULL;
\r
50 p->name_reqd = p->instr_reqd = false;
\r
53 void add_prompt(prompts_t *p, char *promptstr, bool echo)
\r
55 prompt_t *pr = snew(prompt_t);
\r
56 pr->prompt = promptstr;
\r
58 pr->result = strbuf_new_nm();
\r
59 sgrowarray(p->prompts, p->prompts_size, p->n_prompts);
\r
60 p->prompts[p->n_prompts++] = pr;
\r
62 void prompt_set_result(prompt_t *pr, const char *newstr)
\r
64 strbuf_clear(pr->result);
\r
65 put_datapl(pr->result, ptrlen_from_asciz(newstr));
\r
67 const char *prompt_get_result_ref(prompt_t *pr)
\r
69 return pr->result->s;
\r
71 char *prompt_get_result(prompt_t *pr)
\r
73 return dupstr(pr->result->s);
\r
75 void free_prompts(prompts_t *p)
\r
78 for (i=0; i < p->n_prompts; i++) {
\r
79 prompt_t *pr = p->prompts[i];
\r
80 strbuf_free(pr->result);
\r
86 sfree(p->instruction);
\r
91 * Determine whether or not a Conf represents a session which can
\r
92 * sensibly be launched right now.
\r
94 bool conf_launchable(Conf *conf)
\r
96 if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
\r
97 return conf_get_str(conf, CONF_serline)[0] != 0;
\r
99 return conf_get_str(conf, CONF_host)[0] != 0;
\r
102 char const *conf_dest(Conf *conf)
\r
104 if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
\r
105 return conf_get_str(conf, CONF_serline);
\r
107 return conf_get_str(conf, CONF_host);
\r
111 * Validate a manual host key specification (either entered in the
\r
112 * GUI, or via -hostkey). If valid, we return true, and update 'key'
\r
113 * to contain a canonicalised version of the key string in 'key'
\r
114 * (which is guaranteed to take up at most as much space as the
\r
115 * original version), suitable for putting into the Conf. If not
\r
116 * valid, we return false.
\r
118 bool validate_manual_hostkey(char *key)
\r
120 char *p, *q, *r, *s;
\r
123 * Step through the string word by word, looking for a word that's
\r
124 * in one of the formats we like.
\r
127 while ((p += strspn(p, " \t"))[0]) {
\r
129 p += strcspn(p, " \t");
\r
130 if (*p) *p++ = '\0';
\r
133 * Now q is our word.
\r
136 if (strstartswith(q, "SHA256:")) {
\r
137 /* Test for a valid SHA256 key fingerprint. */
\r
139 if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0)
\r
144 if (strstartswith(r, "MD5:"))
\r
146 if (strlen(r) == 16*3 - 1 &&
\r
147 r[strspn(r, "0123456789abcdefABCDEF:")] == 0) {
\r
149 * Test for a valid MD5 key fingerprint. Check the colons
\r
150 * are in the right places, and if so, return the same
\r
151 * fingerprint canonicalised into lowercase.
\r
154 for (i = 0; i < 16; i++)
\r
155 if (r[3*i] == ':' || r[3*i+1] == ':')
\r
156 goto not_fingerprint; /* sorry */
\r
157 for (i = 0; i < 15; i++)
\r
158 if (r[3*i+2] != ':')
\r
159 goto not_fingerprint; /* sorry */
\r
160 for (i = 0; i < 16*3 - 1; i++)
\r
161 key[i] = tolower(r[i]);
\r
162 key[16*3 - 1] = '\0';
\r
168 * Before we check for a public-key blob, trim newlines out of
\r
169 * the middle of the word, in case someone's managed to paste
\r
170 * in a public-key blob _with_ them.
\r
172 for (r = s = q; *r; r++)
\r
173 if (*r != '\n' && *r != '\r')
\r
177 if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
\r
178 q[strspn(q, BASE64_CHARS_ALL)] == 0) {
\r
180 * Might be a base64-encoded SSH-2 public key blob. Check
\r
181 * that it starts with a sensible algorithm string. No
\r
182 * canonicalisation is necessary for this string type.
\r
184 * The algorithm string must be at most 64 characters long
\r
185 * (RFC 4251 section 6).
\r
187 unsigned char decoded[6];
\r
192 len += base64_decode_atom(q, decoded+len);
\r
194 goto not_ssh2_blob; /* sorry */
\r
195 len += base64_decode_atom(q+4, decoded+len);
\r
197 goto not_ssh2_blob; /* sorry */
\r
199 alglen = GET_32BIT_MSB_FIRST(decoded);
\r
201 goto not_ssh2_blob; /* sorry */
\r
203 minlen = ((alglen + 4) + 2) / 3;
\r
204 if (strlen(q) < minlen)
\r
205 goto not_ssh2_blob; /* sorry */
\r
216 char *buildinfo(const char *newline)
\r
218 strbuf *buf = strbuf_new();
\r
220 strbuf_catf(buf, "Build platform: %d-bit %s",
\r
221 (int)(CHAR_BIT * sizeof(void *)),
\r
222 BUILDINFO_PLATFORM);
\r
224 #ifdef __clang_version__
\r
225 #define FOUND_COMPILER
\r
226 strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__);
\r
227 #elif defined __GNUC__ && defined __VERSION__
\r
228 #define FOUND_COMPILER
\r
229 strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__);
\r
232 #if defined _MSC_VER
\r
233 #ifndef FOUND_COMPILER
\r
234 #define FOUND_COMPILER
\r
235 strbuf_catf(buf, "%sCompiler: ", newline);
\r
237 strbuf_catf(buf, ", emulating ");
\r
239 strbuf_catf(buf, "Visual Studio");
\r
243 * List of _MSC_VER values and their translations taken from
\r
244 * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
\r
246 * The pointless #if 0 branch containing this comment is there so
\r
247 * that every real clause can start with #elif and there's no
\r
248 * anomalous first clause. That way the patch looks nicer when you
\r
251 #elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500
\r
253 * 16.9 and 16.8 have the same _MSC_VER value, and have to be
\r
254 * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not
\r
255 * mentioned on the above page, but see e.g.
\r
256 * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120
\r
257 * which says that 16.9 builds will have versions starting at
\r
258 * 19.28.29500.* and going up. Hence, 19 28 29500 is what we
\r
259 * compare _MSC_FULL_VER against above.
\r
261 strbuf_catf(buf, " 2019 (16.9)");
\r
262 #elif _MSC_VER == 1928
\r
263 strbuf_catf(buf, " 2019 (16.8)");
\r
264 #elif _MSC_VER == 1927
\r
265 strbuf_catf(buf, " 2019 (16.7)");
\r
266 #elif _MSC_VER == 1926
\r
267 strbuf_catf(buf, " 2019 (16.6)");
\r
268 #elif _MSC_VER == 1925
\r
269 strbuf_catf(buf, " 2019 (16.5)");
\r
270 #elif _MSC_VER == 1924
\r
271 strbuf_catf(buf, " 2019 (16.4)");
\r
272 #elif _MSC_VER == 1923
\r
273 strbuf_catf(buf, " 2019 (16.3)");
\r
274 #elif _MSC_VER == 1922
\r
275 strbuf_catf(buf, " 2019 (16.2)");
\r
276 #elif _MSC_VER == 1921
\r
277 strbuf_catf(buf, " 2019 (16.1)");
\r
278 #elif _MSC_VER == 1920
\r
279 strbuf_catf(buf, " 2019 (16.0)");
\r
280 #elif _MSC_VER == 1916
\r
281 strbuf_catf(buf, " 2017 version 15.9");
\r
282 #elif _MSC_VER == 1915
\r
283 strbuf_catf(buf, " 2017 version 15.8");
\r
284 #elif _MSC_VER == 1914
\r
285 strbuf_catf(buf, " 2017 version 15.7");
\r
286 #elif _MSC_VER == 1913
\r
287 strbuf_catf(buf, " 2017 version 15.6");
\r
288 #elif _MSC_VER == 1912
\r
289 strbuf_catf(buf, " 2017 version 15.5");
\r
290 #elif _MSC_VER == 1911
\r
291 strbuf_catf(buf, " 2017 version 15.3");
\r
292 #elif _MSC_VER == 1910
\r
293 strbuf_catf(buf, " 2017 RTW (15.0)");
\r
294 #elif _MSC_VER == 1900
\r
295 strbuf_catf(buf, " 2015 (14.0)");
\r
296 #elif _MSC_VER == 1800
\r
297 strbuf_catf(buf, " 2013 (12.0)");
\r
298 #elif _MSC_VER == 1700
\r
299 strbuf_catf(buf, " 2012 (11.0)");
\r
300 #elif _MSC_VER == 1600
\r
301 strbuf_catf(buf, " 2010 (10.0)");
\r
302 #elif _MSC_VER == 1500
\r
303 strbuf_catf(buf, " 2008 (9.0)");
\r
304 #elif _MSC_VER == 1400
\r
305 strbuf_catf(buf, " 2005 (8.0)");
\r
306 #elif _MSC_VER == 1310
\r
307 strbuf_catf(buf, " .NET 2003 (7.1)");
\r
308 #elif _MSC_VER == 1300
\r
309 strbuf_catf(buf, " .NET 2002 (7.0)");
\r
310 #elif _MSC_VER == 1200
\r
311 strbuf_catf(buf, " 6.0");
\r
313 strbuf_catf(buf, ", unrecognised version");
\r
315 strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER);
\r
318 #ifdef BUILDINFO_GTK
\r
320 char *gtk_buildinfo = buildinfo_gtk_version();
\r
321 if (gtk_buildinfo) {
\r
322 strbuf_catf(buf, "%sCompiled against GTK version %s",
\r
323 newline, gtk_buildinfo);
\r
324 sfree(gtk_buildinfo);
\r
328 #if defined _WINDOWS
\r
330 int echm = has_embedded_chm();
\r
332 strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline,
\r
333 echm ? "yes" : "no");
\r
337 #if defined _WINDOWS && defined MINEFIELD
\r
338 strbuf_catf(buf, "%sBuild option: MINEFIELD", newline);
\r
341 strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline);
\r
343 #ifdef NO_SECUREZEROMEMORY
\r
344 strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline);
\r
347 strbuf_catf(buf, "%sBuild option: NO_IPV6", newline);
\r
350 strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline);
\r
352 #ifdef STATIC_GSSAPI
\r
353 strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline);
\r
356 strbuf_catf(buf, "%sBuild option: UNPROTECT", newline);
\r
359 strbuf_catf(buf, "%sBuild option: FUZZING", newline);
\r
362 strbuf_catf(buf, "%sBuild option: DEBUG", newline);
\r
365 strbuf_catf(buf, "%sSource commit: %s", newline, commitid);
\r
367 return strbuf_to_str(buf);
\r
370 size_t nullseat_output(
\r
371 Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; }
\r
372 bool nullseat_eof(Seat *seat) { return true; }
\r
373 int nullseat_get_userpass_input(
\r
374 Seat *seat, prompts_t *p, bufchain *input) { return 0; }
\r
375 void nullseat_notify_remote_exit(Seat *seat) {}
\r
376 void nullseat_connection_fatal(Seat *seat, const char *message) {}
\r
377 void nullseat_update_specials_menu(Seat *seat) {}
\r
378 char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; }
\r
379 void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
\r
380 int nullseat_verify_ssh_host_key(
\r
381 Seat *seat, const char *host, int port, const char *keytype,
\r
382 char *keystr, const char *keydisp, char **key_fingerprints,
\r
383 void (*callback)(void *ctx, int result), void *ctx) { return 0; }
\r
384 int nullseat_confirm_weak_crypto_primitive(
\r
385 Seat *seat, const char *algtype, const char *algname,
\r
386 void (*callback)(void *ctx, int result), void *ctx) { return 0; }
\r
387 int nullseat_confirm_weak_cached_hostkey(
\r
388 Seat *seat, const char *algname, const char *betteralgs,
\r
389 void (*callback)(void *ctx, int result), void *ctx) { return 0; }
\r
390 bool nullseat_is_never_utf8(Seat *seat) { return false; }
\r
391 bool nullseat_is_always_utf8(Seat *seat) { return true; }
\r
392 void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {}
\r
393 const char *nullseat_get_x_display(Seat *seat) { return NULL; }
\r
394 bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; }
\r
395 bool nullseat_get_window_pixel_size(
\r
396 Seat *seat, int *width, int *height) { return false; }
\r
397 StripCtrlChars *nullseat_stripctrl_new(
\r
398 Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;}
\r
399 bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; }
\r
400 bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; }
\r
401 bool nullseat_verbose_no(Seat *seat) { return false; }
\r
402 bool nullseat_verbose_yes(Seat *seat) { return true; }
\r
403 bool nullseat_interactive_no(Seat *seat) { return false; }
\r
404 bool nullseat_interactive_yes(Seat *seat) { return true; }
\r
405 bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; }
\r
407 bool null_lp_verbose_no(LogPolicy *lp) { return false; }
\r
408 bool null_lp_verbose_yes(LogPolicy *lp) { return true; }
\r
410 void sk_free_peer_info(SocketPeerInfo *pi)
\r
413 sfree((char *)pi->addr_text);
\r
414 sfree((char *)pi->log_text);
\r
419 void out_of_memory(void)
\r
421 modalfatalbox("Out of memory");
\r