Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/core/proxy.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * HTTP Proxy support
4
 *
5
 * Copyright 2016 Christian Plattner <ccpp@gmx.at>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <ctype.h>
21
#include <errno.h>
22
#include <limits.h>
23
24
#include <openssl/err.h>
25
26
#include "settings.h"
27
#include "proxy.h"
28
#include <freerdp/settings.h>
29
#include <freerdp/utils/proxy_utils.h>
30
#include <freerdp/crypto/crypto.h>
31
#include "tcp.h"
32
33
#include <winpr/assert.h>
34
#include <winpr/sysinfo.h>
35
#include <winpr/environment.h> /* For GetEnvironmentVariableA */
36
37
0
#define CRLF "\r\n"
38
39
#include <freerdp/log.h>
40
#define TAG FREERDP_TAG("core.proxy")
41
42
/* SOCKS Proxy auth methods by rfc1928 */
43
enum
44
{
45
  AUTH_M_NO_AUTH = 0,
46
  AUTH_M_GSSAPI = 1,
47
  AUTH_M_USR_PASS = 2
48
};
49
50
enum
51
{
52
  SOCKS_CMD_CONNECT = 1,
53
  SOCKS_CMD_BIND = 2,
54
  SOCKS_CMD_UDP_ASSOCIATE = 3
55
};
56
57
enum
58
{
59
  SOCKS_ADDR_IPV4 = 1,
60
  SOCKS_ADDR_FQDN = 3,
61
  SOCKS_ADDR_IPV6 = 4,
62
};
63
64
static const char logprefix[] = "SOCKS Proxy:";
65
66
/* CONN REQ replies in enum. order */
67
static const char* rplstat[] = { "succeeded",
68
                               "general SOCKS server failure",
69
                               "connection not allowed by ruleset",
70
                               "Network unreachable",
71
                               "Host unreachable",
72
                               "Connection refused",
73
                               "TTL expired",
74
                               "Command not supported",
75
                               "Address type not supported" };
76
77
WINPR_ATTR_NODISCARD
78
static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
79
                               const char* proxyPassword, const char* hostname, UINT16 port);
80
81
WINPR_ATTR_NODISCARD
82
static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
83
                                const char* proxyPassword, const char* hostname, UINT16 port);
84
static void proxy_read_environment(rdpSettings* settings, char* envname);
85
86
BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort,
87
                   const char** lpProxyUsername, const char** lpProxyPassword)
88
1.10k
{
89
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_IGNORE)
90
0
    return FALSE;
91
92
  /* For TSGateway, find the system HTTPS proxy automatically */
93
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
94
1.10k
    proxy_read_environment(settings, "https_proxy");
95
96
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
97
1.10k
    proxy_read_environment(settings, "HTTPS_PROXY");
98
99
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
100
0
    proxy_read_environment(settings, "no_proxy");
101
102
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
103
0
    proxy_read_environment(settings, "NO_PROXY");
104
105
1.10k
  if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
106
0
  {
107
0
    *lpPeerHostname = freerdp_settings_get_string(settings, FreeRDP_ProxyHostname);
108
0
    if (!*lpPeerHostname || !winpr_str_is_valid_hostname(*lpPeerHostname))
109
0
      return FALSE;
110
0
    *lpPeerPort = freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort);
111
0
    *lpProxyUsername = freerdp_settings_get_string(settings, FreeRDP_ProxyUsername);
112
0
    *lpProxyPassword = freerdp_settings_get_string(settings, FreeRDP_ProxyPassword);
113
0
    return TRUE;
114
0
  }
115
116
1.10k
  return FALSE;
117
1.10k
}
118
119
WINPR_ATTR_NODISCARD
120
static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
121
0
{
122
0
  long long rc = 0;
123
124
0
  if (!value || !result)
125
0
    return FALSE;
126
127
0
  errno = 0;
128
0
  rc = _strtoi64(value, nullptr, 0);
129
130
0
  if (errno != 0)
131
0
    return FALSE;
132
133
0
  if ((rc < min) || (rc > max))
134
0
    return FALSE;
135
136
0
  *result = rc;
137
0
  return TRUE;
138
0
}
139
140
WINPR_ATTR_NODISCARD
141
static BOOL cidr4_match(const struct in_addr* addr, const struct in_addr* net, BYTE bits)
142
0
{
143
0
  if (bits == 0)
144
0
    return TRUE;
145
146
0
  const uint32_t mask = htonl(0xFFFFFFFFu << (32 - bits));
147
0
  const uint32_t amask = addr->s_addr & mask;
148
0
  const uint32_t nmask = net->s_addr & mask;
149
0
  return amask == nmask;
150
0
}
151
152
WINPR_ATTR_NODISCARD
153
static BOOL cidr6_match(const struct in6_addr* address, const struct in6_addr* network,
154
                        uint8_t bits)
155
0
{
156
0
  const uint32_t* a = (const uint32_t*)address;
157
0
  const uint32_t* n = (const uint32_t*)network;
158
0
  const size_t bits_whole = bits >> 5;
159
0
  const size_t bits_incomplete = bits & 0x1F;
160
161
0
  if (bits_whole)
162
0
  {
163
0
    if (memcmp(a, n, bits_whole << 2) != 0)
164
0
      return FALSE;
165
0
  }
166
167
0
  if (bits_incomplete)
168
0
  {
169
0
    uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
170
171
0
    if ((a[bits_whole] ^ n[bits_whole]) & mask)
172
0
      return FALSE;
173
0
  }
174
175
0
  return TRUE;
176
0
}
177
178
WINPR_ATTR_NODISCARD
179
static BOOL option_ends_with(const char* str, const char* ext)
180
0
{
181
0
  WINPR_ASSERT(str);
182
0
  WINPR_ASSERT(ext);
183
0
  const size_t strLen = strlen(str);
184
0
  const size_t extLen = strlen(ext);
185
186
0
  if (strLen < extLen)
187
0
    return FALSE;
188
189
0
  return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
190
0
}
191
192
/* no_proxy has no proper definition, so use curl as reference:
193
 * https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/
194
 */
195
WINPR_ATTR_NODISCARD
196
static BOOL no_proxy_match_host(const char* val, const char* hostname)
197
0
{
198
0
  WINPR_ASSERT(val);
199
0
  WINPR_ASSERT(hostname);
200
201
  /* match all */
202
0
  if (_stricmp("*", val) == 0)
203
0
    return TRUE;
204
205
  /* Strip leading . */
206
0
  if (val[0] == '.')
207
0
    val++;
208
209
  /* Match suffix */
210
0
  return option_ends_with(hostname, val);
211
0
}
212
213
WINPR_ATTR_NODISCARD
214
static BOOL starts_with(const char* val, const char* prefix)
215
0
{
216
0
  const size_t plen = strlen(prefix);
217
0
  const size_t vlen = strlen(val);
218
0
  if (vlen < plen)
219
0
    return FALSE;
220
0
  return _strnicmp(val, prefix, plen) == 0;
221
0
}
222
223
WINPR_ATTR_NODISCARD
224
static BOOL no_proxy_match_ip(const char* val, const char* hostname)
225
0
{
226
0
  WINPR_ASSERT(val);
227
0
  WINPR_ASSERT(hostname);
228
229
0
  struct sockaddr_in sa4 = WINPR_C_ARRAY_INIT;
230
0
  struct sockaddr_in6 sa6 = WINPR_C_ARRAY_INIT;
231
232
0
  if (inet_pton(AF_INET, hostname, &sa4.sin_addr) == 1)
233
0
  {
234
    /* Prefix match */
235
0
    if (starts_with(hostname, val))
236
0
      return TRUE;
237
238
0
    char* sub = strchr(val, '/');
239
0
    if (sub)
240
0
      *sub++ = '\0';
241
242
0
    struct sockaddr_in mask = WINPR_C_ARRAY_INIT;
243
0
    if (inet_pton(AF_INET, val, &mask.sin_addr) == 0)
244
0
      return FALSE;
245
246
    /* IP address match */
247
0
    if (memcmp(&mask, &sa4, sizeof(mask)) == 0)
248
0
      return TRUE;
249
250
0
    if (sub)
251
0
    {
252
0
      const unsigned long usub = strtoul(sub, nullptr, 0);
253
0
      if ((errno == 0) && (usub <= UINT8_MAX))
254
0
        return cidr4_match(&sa4.sin_addr, &mask.sin_addr, (UINT8)usub);
255
0
    }
256
0
  }
257
0
  else if (inet_pton(AF_INET6, hostname, &sa6.sin6_addr) == 1)
258
0
  {
259
0
    if (val[0] == '[')
260
0
      val++;
261
262
0
    char str[INET6_ADDRSTRLEN + 1] = WINPR_C_ARRAY_INIT;
263
0
    strncpy(str, val, INET6_ADDRSTRLEN);
264
265
0
    const size_t len = strnlen(str, INET6_ADDRSTRLEN);
266
0
    if (len > 0)
267
0
    {
268
0
      if (str[len - 1] == ']')
269
0
        str[len - 1] = '\0';
270
0
    }
271
272
    /* Prefix match */
273
0
    if (starts_with(hostname, str))
274
0
      return TRUE;
275
276
0
    char* sub = strchr(str, '/');
277
0
    if (sub)
278
0
      *sub++ = '\0';
279
280
0
    struct sockaddr_in6 mask = WINPR_C_ARRAY_INIT;
281
0
    if (inet_pton(AF_INET6, str, &mask.sin6_addr) == 0)
282
0
      return FALSE;
283
284
    /* Address match */
285
0
    if (memcmp(&mask, &sa6, sizeof(mask)) == 0)
286
0
      return TRUE;
287
288
0
    if (sub)
289
0
    {
290
0
      const unsigned long usub = strtoul(sub, nullptr, 0);
291
0
      if ((errno == 0) && (usub <= UINT8_MAX))
292
0
        return cidr6_match(&sa6.sin6_addr, &mask.sin6_addr, (UINT8)usub);
293
0
    }
294
0
  }
295
296
0
  return FALSE;
297
0
}
298
299
WINPR_ATTR_NODISCARD
300
static BOOL is_ipv6_addr(const char* hostname, size_t len)
301
0
{
302
0
  struct sockaddr_in6 sa6 = WINPR_C_ARRAY_INIT;
303
0
  if (strnlen(hostname, len) > INET6_ADDRSTRLEN)
304
0
    return FALSE;
305
0
  return inet_pton(AF_INET6, hostname, &sa6.sin6_addr) == 1;
306
0
}
307
308
WINPR_ATTR_NODISCARD
309
static BOOL check_no_proxy(rdpSettings* settings, const char* no_proxy)
310
0
{
311
0
  const char* delimiter = ", ";
312
0
  BOOL result = FALSE;
313
0
  char* context = nullptr;
314
315
0
  if (!no_proxy || !settings)
316
0
    return FALSE;
317
318
0
  char* copy = _strdup(no_proxy);
319
320
0
  if (!copy)
321
0
    return FALSE;
322
323
0
  char* current = strtok_s(copy, delimiter, &context);
324
325
0
  while (current && !result)
326
0
  {
327
0
    const size_t currentlen = strlen(current);
328
329
0
    if (currentlen > 0)
330
0
    {
331
0
      const char* ServerHostname =
332
0
          freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
333
0
      WLog_DBG(TAG, "%s => %s (%" PRIuz ")", ServerHostname, current, currentlen);
334
335
0
      if (no_proxy_match_host(current, ServerHostname))
336
0
        result = TRUE;
337
0
      else if (no_proxy_match_ip(current, ServerHostname))
338
0
        result = TRUE;
339
0
    }
340
341
0
    current = strtok_s(nullptr, delimiter, &context);
342
0
  }
343
344
0
  free(copy);
345
0
  return result;
346
0
}
347
348
void proxy_read_environment(rdpSettings* settings, char* envname)
349
2.21k
{
350
2.21k
  const DWORD envlen = GetEnvironmentVariableA(envname, nullptr, 0);
351
352
2.21k
  if (!envlen || (envlen <= 1))
353
2.21k
    return;
354
355
0
  char* env = calloc(1, envlen);
356
357
0
  if (!env)
358
0
  {
359
0
    WLog_ERR(TAG, "Not enough memory");
360
0
    return;
361
0
  }
362
363
0
  if (GetEnvironmentVariableA(envname, env, envlen) == envlen - 1)
364
0
  {
365
0
    if (_strnicmp("NO_PROXY", envname, 9) == 0)
366
0
    {
367
0
      if (check_no_proxy(settings, env))
368
0
      {
369
0
        WLog_INFO(TAG, "deactivating proxy: %s [%s=%s]",
370
0
                  freerdp_settings_get_string(settings, FreeRDP_ServerHostname), envname,
371
0
                  env);
372
0
        if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_NONE))
373
0
          WLog_WARN(TAG, "failed to set FreeRDP_ProxyType=PROXY_TYPE_NONE");
374
0
      }
375
0
    }
376
0
    else
377
0
    {
378
0
      if (!proxy_parse_uri(settings, env))
379
0
      {
380
0
        WLog_WARN(
381
0
            TAG, "Error while parsing proxy URI from environment variable; ignoring proxy");
382
0
      }
383
0
    }
384
0
  }
385
386
0
  free(env);
387
0
}
388
389
BOOL proxy_parse_uri(rdpSettings* settings, const char* uri_in)
390
0
{
391
0
  BOOL rc = FALSE;
392
0
  const char* protocol = "";
393
0
  UINT16 port = 0;
394
395
0
  if (!settings || !uri_in)
396
0
    return FALSE;
397
398
0
  char* uri_copy = _strdup(uri_in);
399
0
  char* uri = uri_copy;
400
0
  if (!uri)
401
0
    goto fail;
402
403
0
  {
404
0
    char* p = strstr(uri, "://");
405
0
    if (p)
406
0
    {
407
0
      *p = '\0';
408
409
0
      if (_stricmp("no_proxy", uri) == 0)
410
0
      {
411
0
        if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE))
412
0
          goto fail;
413
0
      }
414
0
      if (_stricmp("http", uri) == 0)
415
0
      {
416
0
        if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
417
0
          goto fail;
418
0
        protocol = "http";
419
0
      }
420
0
      else if (_stricmp("socks5", uri) == 0)
421
0
      {
422
0
        if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_SOCKS))
423
0
          goto fail;
424
0
        protocol = "socks5";
425
0
      }
426
0
      else
427
0
      {
428
0
        WLog_ERR(TAG, "Only HTTP and SOCKS5 proxies supported by now");
429
0
        goto fail;
430
0
      }
431
432
0
      uri = p + 3;
433
0
    }
434
0
    else
435
0
    {
436
      /* default proxy protocol is http */
437
0
      if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
438
0
        goto fail;
439
0
      protocol = "http";
440
0
    }
441
0
  }
442
443
  /* uri is now [user:password@]hostname:port */
444
0
  {
445
0
    char* atPtr = strrchr(uri, '@');
446
447
0
    if (atPtr)
448
0
    {
449
      /* got a login / password,
450
       *         atPtr
451
       *         v
452
       * [user:password@]hostname:port
453
       *    ^
454
       *    colonPtr
455
       */
456
0
      char* colonPtr = strchr(uri, ':');
457
458
0
      if (!colonPtr || (colonPtr > atPtr))
459
0
      {
460
0
        WLog_ERR(TAG, "invalid syntax for proxy (contains no password)");
461
0
        goto fail;
462
0
      }
463
464
0
      *colonPtr = '\0';
465
0
      if (!freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, uri))
466
0
      {
467
0
        WLog_ERR(TAG, "unable to allocate proxy username");
468
0
        goto fail;
469
0
      }
470
471
0
      *atPtr = '\0';
472
473
0
      if (!freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, colonPtr + 1))
474
0
      {
475
0
        WLog_ERR(TAG, "unable to allocate proxy password");
476
0
        goto fail;
477
0
      }
478
479
0
      uri = atPtr + 1;
480
0
    }
481
0
  }
482
483
0
  {
484
0
    char* p = strchr(uri, ':');
485
486
0
    if (p)
487
0
    {
488
0
      LONGLONG val = 0;
489
490
0
      if (!value_to_int(&p[1], &val, 0, UINT16_MAX))
491
0
      {
492
0
        WLog_ERR(TAG, "invalid syntax for proxy (invalid port)");
493
0
        goto fail;
494
0
      }
495
496
0
      if (val == 0)
497
0
      {
498
0
        WLog_ERR(TAG, "invalid syntax for proxy (port missing)");
499
0
        goto fail;
500
0
      }
501
502
0
      port = (UINT16)val;
503
0
      *p = '\0';
504
0
    }
505
0
    else
506
0
    {
507
0
      if (_stricmp("http", protocol) == 0)
508
0
      {
509
        /* The default is 80. Also for Proxies. */
510
0
        port = 80;
511
0
      }
512
0
      else
513
0
      {
514
0
        port = 1080;
515
0
      }
516
517
0
      WLog_DBG(TAG, "setting default proxy port: %" PRIu16, port);
518
0
    }
519
520
0
    if (!freerdp_settings_set_uint16(settings, FreeRDP_ProxyPort, port))
521
0
      goto fail;
522
0
  }
523
0
  {
524
0
    char* p = strchr(uri, '/');
525
0
    if (p)
526
0
      *p = '\0';
527
0
    if (!freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, uri))
528
0
      goto fail;
529
0
  }
530
531
0
  if (_stricmp("", uri) == 0)
532
0
  {
533
0
    WLog_ERR(TAG, "invalid syntax for proxy (hostname missing)");
534
0
    goto fail;
535
0
  }
536
537
0
  if (freerdp_settings_get_string(settings, FreeRDP_ProxyUsername))
538
0
  {
539
0
    WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%s@%s:%" PRIu16, protocol,
540
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyUsername), "******",
541
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
542
0
              freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
543
0
  }
544
0
  else
545
0
  {
546
0
    WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%" PRIu16, protocol,
547
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
548
0
              freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
549
0
  }
550
0
  rc = TRUE;
551
552
0
fail:
553
0
  if (!rc)
554
0
    WLog_WARN(TAG, "Failed to parse proxy configuration: %s://%s:%" PRIu16, protocol, uri,
555
0
              port);
556
0
  free(uri_copy);
557
0
  return rc;
558
0
}
559
560
BOOL proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
561
                   const char* proxyPassword, const char* hostname, UINT16 port)
562
0
{
563
0
  WINPR_ASSERT(context);
564
0
  rdpSettings* settings = context->settings;
565
566
0
  switch (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType))
567
0
  {
568
0
    case PROXY_TYPE_NONE:
569
0
    case PROXY_TYPE_IGNORE:
570
0
      return TRUE;
571
572
0
    case PROXY_TYPE_HTTP:
573
0
      return http_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
574
0
                                port);
575
576
0
    case PROXY_TYPE_SOCKS:
577
0
      return socks_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
578
0
                                 port);
579
580
0
    default:
581
0
      WLog_ERR(TAG, "Invalid internal proxy configuration");
582
0
      return FALSE;
583
0
  }
584
0
}
585
586
WINPR_ATTR_NODISCARD
587
static const char* get_response_header(char* response)
588
0
{
589
0
  char* current_pos = strchr(response, '\r');
590
0
  if (!current_pos)
591
0
    current_pos = strchr(response, '\n');
592
593
0
  if (current_pos)
594
0
    *current_pos = '\0';
595
596
0
  return response;
597
0
}
598
599
static BOOL http_proxy_write_hostname(wStream* s, const char* hostname, size_t len)
600
0
{
601
0
  const BOOL isIPv6 = is_ipv6_addr(hostname, len);
602
603
0
  if (isIPv6)
604
0
  {
605
0
    if (!Stream_EnsureRemainingCapacity(s, 1))
606
0
      return FALSE;
607
0
    Stream_Write_UINT8(s, '[');
608
0
  }
609
610
0
  if (!Stream_EnsureRemainingCapacity(s, len))
611
0
    return FALSE;
612
0
  Stream_Write(s, hostname, len);
613
614
0
  if (isIPv6)
615
0
  {
616
0
    if (!Stream_EnsureRemainingCapacity(s, 1))
617
0
      return FALSE;
618
0
    Stream_Write_UINT8(s, ']');
619
0
  }
620
621
0
  return TRUE;
622
0
}
623
624
WINPR_ATTR_NODISCARD
625
static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
626
                               const char* proxyPassword, const char* hostname, UINT16 port)
627
0
{
628
0
  BOOL rc = FALSE;
629
0
  int status = 0;
630
0
  char port_str[10] = WINPR_C_ARRAY_INIT;
631
0
  char recv_buf[256] = WINPR_C_ARRAY_INIT;
632
0
  char* eol = nullptr;
633
0
  size_t resultsize = 0;
634
0
  const char connect[] = "CONNECT ";
635
0
  const char httpheader[] = " HTTP/1.1" CRLF "Host: ";
636
637
0
  WINPR_ASSERT(context);
638
0
  WINPR_ASSERT(bufferedBio);
639
0
  WINPR_ASSERT(hostname);
640
0
  const UINT32 timeout =
641
0
      freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
642
643
  // Check for IPv6 (max 45) or DNS (max 255) name. Bounded length to 256 so too long hostnames do
644
  // not silently pass
645
0
  const size_t hostLen = strnlen(hostname, 255 + 1);
646
0
  if (!is_ipv6_addr(hostname, hostLen) && !winpr_str_is_valid_hostnameN(hostname, hostLen))
647
0
    return FALSE;
648
649
0
  if (_itoa_s(port, port_str, sizeof(port_str), 10) < 0)
650
0
  {
651
0
    WLog_ERR(TAG, "itoa %s failed", port_str);
652
0
    return FALSE;
653
0
  }
654
655
0
  const size_t portLen = strnlen(port_str, sizeof(port_str));
656
0
  wStream* s = Stream_New(nullptr, 1024);
657
0
  if (!s)
658
0
    goto fail;
659
660
0
  const size_t clen = strnlen(connect, sizeof(connect));
661
0
  if (!Stream_EnsureRemainingCapacity(s, clen))
662
0
    goto fail;
663
0
  Stream_Write(s, connect, clen);
664
665
0
  if (!http_proxy_write_hostname(s, hostname, hostLen))
666
0
    goto fail;
667
668
0
  if (!Stream_EnsureRemainingCapacity(s, 1))
669
0
    goto fail;
670
0
  Stream_Write_UINT8(s, ':');
671
672
0
  if (!Stream_EnsureRemainingCapacity(s, portLen))
673
0
    goto fail;
674
0
  Stream_Write(s, port_str, portLen);
675
676
0
  const size_t httplen = strnlen(httpheader, sizeof(httpheader));
677
0
  if (!Stream_EnsureRemainingCapacity(s, httplen))
678
0
    goto fail;
679
0
  Stream_Write(s, httpheader, httplen);
680
681
0
  if (!http_proxy_write_hostname(s, hostname, hostLen))
682
0
    goto fail;
683
684
0
  if (!Stream_EnsureRemainingCapacity(s, 1))
685
0
    goto fail;
686
0
  Stream_Write_UINT8(s, ':');
687
688
0
  if (!Stream_EnsureRemainingCapacity(s, portLen))
689
0
    goto fail;
690
0
  Stream_Write(s, port_str, portLen);
691
692
0
  if (proxyUsername && proxyPassword)
693
0
  {
694
0
    const int length = _scprintf("%s:%s", proxyUsername, proxyPassword);
695
0
    if (length > 0)
696
0
    {
697
0
      const char basic[] = CRLF "Proxy-Authorization: Basic ";
698
0
      const size_t balen = strnlen(basic, sizeof(basic));
699
0
      if (!Stream_EnsureRemainingCapacity(s, balen))
700
0
        goto fail;
701
0
      Stream_Write(s, basic, balen);
702
703
0
      char* creds = nullptr;
704
0
      size_t size = 0;
705
0
      (void)winpr_asprintf(&creds, &size, "%s:%s", proxyUsername, proxyPassword);
706
0
      if (!creds || (size < 1))
707
0
      {
708
0
        free(creds);
709
0
        goto fail;
710
0
      }
711
0
      size_t b64len = 0;
712
0
      char* base64 = crypto_base64_encode_len(creds, size - 1, &b64len);
713
0
      free(creds);
714
0
      if (!base64 || !Stream_EnsureRemainingCapacity(s, b64len))
715
0
      {
716
0
        free(base64);
717
0
        goto fail;
718
0
      }
719
720
0
      Stream_Write(s, base64, strlen(base64));
721
0
      free(base64);
722
0
    }
723
0
  }
724
725
0
  if (!Stream_EnsureRemainingCapacity(s, 4))
726
0
    goto fail;
727
728
0
  Stream_Write(s, CRLF CRLF, 4);
729
0
  ERR_clear_error();
730
731
0
  {
732
0
    const size_t pos = Stream_GetPosition(s);
733
0
    if (pos > INT32_MAX)
734
0
      goto fail;
735
736
0
    status = BIO_write(bufferedBio, Stream_Buffer(s), WINPR_ASSERTING_INT_CAST(int, pos));
737
0
  }
738
739
0
  if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
740
0
  {
741
0
    WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
742
0
    goto fail;
743
0
  }
744
745
  /* Read result until CR-LF-CR-LF.
746
   * Keep recv_buf a null-terminated string. */
747
0
  {
748
0
    const UINT64 start = GetTickCount64();
749
0
    while (strstr(recv_buf, CRLF CRLF) == nullptr)
750
0
    {
751
0
      if (resultsize >= sizeof(recv_buf) - 1)
752
0
      {
753
0
        WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
754
0
        goto fail;
755
0
      }
756
0
      const size_t rdsize = sizeof(recv_buf) - resultsize - 1ULL;
757
758
0
      ERR_clear_error();
759
760
0
      WINPR_ASSERT(rdsize <= INT32_MAX);
761
0
      status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (int)rdsize);
762
763
0
      if (status < 0)
764
0
      {
765
        /* Error? */
766
0
        if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
767
0
        {
768
0
          USleep(100);
769
0
          continue;
770
0
        }
771
772
0
        WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
773
0
        goto fail;
774
0
      }
775
0
      else if (status == 0)
776
0
      {
777
0
        const UINT64 now = GetTickCount64();
778
0
        const UINT64 diff = now - start;
779
0
        if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
780
0
        {
781
          /* Error? */
782
0
          WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
783
0
          goto fail;
784
0
        }
785
0
        Sleep(10);
786
0
      }
787
788
0
      resultsize += WINPR_ASSERTING_INT_CAST(size_t, status);
789
0
    }
790
0
  }
791
792
  /* Extract HTTP status line */
793
0
  eol = strchr(recv_buf, '\r');
794
795
0
  if (!eol)
796
0
  {
797
    /* should never happen */
798
0
    goto fail;
799
0
  }
800
801
0
  *eol = '\0';
802
0
  WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
803
804
0
  if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
805
0
    goto fail;
806
807
0
  recv_buf[7] = 'X';
808
809
0
  if (strncmp(recv_buf, "HTTP/1.X 200", 12) != 0)
810
0
    goto fail;
811
812
0
  rc = TRUE;
813
0
fail:
814
0
  WLog_ERR(TAG, "Failed to connect to proxy");
815
0
  Stream_Free(s, TRUE);
816
0
  return rc;
817
0
}
818
819
WINPR_ATTR_NODISCARD
820
static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf, int len, char* reason,
821
                            BYTE checkVer)
822
0
{
823
0
  int status = 0;
824
825
0
  WINPR_ASSERT(context);
826
827
0
  const UINT32 timeout =
828
0
      freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
829
0
  const UINT64 start = GetTickCount64();
830
0
  for (;;)
831
0
  {
832
0
    ERR_clear_error();
833
0
    status = BIO_read(bufferedBio, buf, len);
834
835
0
    if (status > 0)
836
0
    {
837
0
      break;
838
0
    }
839
0
    else if (status < 0)
840
0
    {
841
      /* Error? */
842
0
      if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
843
0
      {
844
0
        USleep(100);
845
0
        continue;
846
0
      }
847
848
0
      WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
849
0
      return -1;
850
0
    }
851
0
    else if (status == 0)
852
0
    {
853
0
      const UINT64 now = GetTickCount64();
854
0
      const UINT64 diff = now - start;
855
0
      if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
856
0
      {
857
        /* Error? */
858
0
        WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
859
0
                 reason);
860
0
        return status;
861
0
      }
862
0
      Sleep(10);
863
0
    }
864
0
    else // if (status == 0)
865
0
    {
866
      /* Error? */
867
0
      WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
868
0
               reason);
869
0
      return -1;
870
0
    }
871
0
  }
872
873
0
  if (status < 2)
874
0
  {
875
0
    WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
876
0
    return -1;
877
0
  }
878
879
0
  if (buf[0] != checkVer)
880
0
  {
881
0
    WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
882
0
    return -1;
883
0
  }
884
885
0
  return status;
886
0
}
887
888
WINPR_ATTR_NODISCARD
889
static BOOL socks_proxy_userpass(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
890
                                 const char* proxyPassword)
891
0
{
892
0
  WINPR_ASSERT(context);
893
0
  WINPR_ASSERT(bufferedBio);
894
895
0
  if (!proxyUsername || !proxyPassword)
896
0
  {
897
0
    WLog_ERR(TAG, "%s invalid username (%p) or password (%p)", logprefix,
898
0
             WINPR_CXX_COMPAT_CAST(const void*, proxyUsername),
899
0
             WINPR_CXX_COMPAT_CAST(const void*, proxyPassword));
900
0
    return FALSE;
901
0
  }
902
903
0
  const size_t usernameLen = (BYTE)strnlen(proxyUsername, 256);
904
0
  if (usernameLen > 255)
905
0
  {
906
0
    WLog_ERR(TAG, "%s username too long (%" PRIuz ", max=255)", logprefix, usernameLen);
907
0
    return FALSE;
908
0
  }
909
910
0
  const size_t userpassLen = (BYTE)strnlen(proxyPassword, 256);
911
0
  if (userpassLen > 255)
912
0
  {
913
0
    WLog_ERR(TAG, "%s password too long (%" PRIuz ", max=255)", logprefix, userpassLen);
914
0
    return FALSE;
915
0
  }
916
917
  /* user/password v1 method */
918
0
  {
919
0
    BYTE buf[2 * 255 + 3] = WINPR_C_ARRAY_INIT;
920
0
    size_t offset = 0;
921
0
    buf[offset++] = 1;
922
923
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, usernameLen);
924
0
    memcpy(&buf[offset], proxyUsername, usernameLen);
925
0
    offset += usernameLen;
926
927
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, userpassLen);
928
0
    memcpy(&buf[offset], proxyPassword, userpassLen);
929
0
    offset += userpassLen;
930
931
0
    ERR_clear_error();
932
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
933
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
934
935
0
    if (status != ioffset)
936
0
    {
937
0
      WLog_ERR(TAG, "%s error writing user/password request", logprefix);
938
0
      return FALSE;
939
0
    }
940
0
  }
941
942
0
  BYTE buf[2] = WINPR_C_ARRAY_INIT;
943
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 1);
944
945
0
  if (status < 2)
946
0
    return FALSE;
947
948
0
  if (buf[1] != 0x00)
949
0
  {
950
0
    WLog_ERR(TAG, "%s invalid user/password", logprefix);
951
0
    return FALSE;
952
0
  }
953
0
  return TRUE;
954
0
}
955
956
WINPR_ATTR_NODISCARD
957
static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
958
                                const char* proxyPassword, const char* hostname, UINT16 port)
959
0
{
960
0
  WINPR_ASSERT(hostname);
961
  // Check for IPv6 (max 45) or DNS (max 255) name. Bounded length to 256 so too long hostnames do
962
  // not silently pass
963
0
  const size_t hostnlen = strnlen(hostname, 255 + 1);
964
0
  if (!is_ipv6_addr(hostname, hostnlen) && !winpr_str_is_valid_hostnameN(hostname, hostnlen))
965
0
    return FALSE;
966
967
0
  BYTE nauthMethods = 1;
968
969
0
  if (proxyUsername || proxyPassword)
970
0
    nauthMethods++;
971
972
  /* select auth. method */
973
0
  {
974
0
    const BYTE buf[] = { 5,            /* SOCKS version */
975
0
                       nauthMethods, /* #of methods offered */
976
0
                       AUTH_M_NO_AUTH, AUTH_M_USR_PASS };
977
978
0
    size_t writeLen = sizeof(buf);
979
0
    if (nauthMethods <= 1)
980
0
      writeLen--;
981
982
0
    ERR_clear_error();
983
0
    const int iwriteLen = WINPR_ASSERTING_INT_CAST(int, writeLen);
984
0
    const int status = BIO_write(bufferedBio, buf, iwriteLen);
985
986
0
    if (status != iwriteLen)
987
0
    {
988
0
      WLog_ERR(TAG, "%s SOCKS proxy: failed to write AUTH METHOD request", logprefix);
989
0
      return FALSE;
990
0
    }
991
0
  }
992
993
0
  {
994
0
    BYTE buf[2] = WINPR_C_ARRAY_INIT;
995
0
    const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 5);
996
997
0
    if (status <= 0)
998
0
      return FALSE;
999
1000
0
    switch (buf[1])
1001
0
    {
1002
0
      case AUTH_M_NO_AUTH:
1003
0
        WLog_DBG(TAG, "%s (NO AUTH) method was selected", logprefix);
1004
0
        break;
1005
1006
0
      case AUTH_M_USR_PASS:
1007
0
        if (nauthMethods < 2)
1008
0
        {
1009
0
          WLog_ERR(TAG, "%s USER/PASS method was not proposed to server", logprefix);
1010
0
          return FALSE;
1011
0
        }
1012
0
        if (!socks_proxy_userpass(context, bufferedBio, proxyUsername, proxyPassword))
1013
0
          return FALSE;
1014
0
        break;
1015
1016
0
      default:
1017
0
        WLog_ERR(TAG, "%s unknown method 0x%x was selected by proxy", logprefix, buf[1]);
1018
0
        return FALSE;
1019
0
    }
1020
0
  }
1021
  /* CONN request */
1022
0
  {
1023
0
    BYTE buf[262] = WINPR_C_ARRAY_INIT;
1024
0
    size_t offset = 0;
1025
0
    buf[offset++] = 5;                 /* SOCKS version */
1026
0
    buf[offset++] = SOCKS_CMD_CONNECT; /* command */
1027
0
    buf[offset++] = 0;                 /* 3rd octet is reserved x00 */
1028
1029
0
    if (inet_pton(AF_INET6, hostname, &buf[offset + 1]) == 1)
1030
0
    {
1031
0
      buf[offset++] = SOCKS_ADDR_IPV6;
1032
0
      offset += 16;
1033
0
    }
1034
0
    else if (inet_pton(AF_INET, hostname, &buf[offset + 1]) == 1)
1035
0
    {
1036
0
      buf[offset++] = SOCKS_ADDR_IPV4;
1037
0
      offset += 4;
1038
0
    }
1039
0
    else
1040
0
    {
1041
0
      buf[offset++] = SOCKS_ADDR_FQDN;
1042
0
      buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, hostnlen);
1043
0
      memcpy(&buf[offset], hostname, hostnlen);
1044
0
      offset += hostnlen;
1045
0
    }
1046
1047
0
    if (offset > sizeof(buf) - 2)
1048
0
    {
1049
0
      WLog_ERR(TAG, "Invalid offset %" PRIuz, offset);
1050
0
      return FALSE;
1051
0
    }
1052
1053
    /* follows DST.PORT in netw. format */
1054
0
    buf[offset++] = (port >> 8) & 0xff;
1055
0
    buf[offset++] = port & 0xff;
1056
1057
0
    ERR_clear_error();
1058
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
1059
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
1060
1061
0
    if ((status < 0) || (status != ioffset))
1062
0
    {
1063
0
      WLog_ERR(TAG, "%s SOCKS proxy: failed to write CONN REQ", logprefix);
1064
0
      return FALSE;
1065
0
    }
1066
0
  }
1067
1068
0
  BYTE buf[255] = WINPR_C_ARRAY_INIT;
1069
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
1070
1071
0
  if (status < 4)
1072
0
    return FALSE;
1073
1074
0
  if (buf[1] == 0)
1075
0
  {
1076
0
    WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
1077
0
    return TRUE;
1078
0
  }
1079
1080
0
  if ((buf[1] > 0) && (buf[1] < 9))
1081
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
1082
0
  else
1083
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
1084
1085
0
  return FALSE;
1086
0
}