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