Coverage Report

Created: 2026-10-28 06:54

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