Coverage Report

Created: 2026-01-09 06:49

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