Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/libfreerdp/core/proxy.c
Line
Count
Source (jump to first uncovered line)
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
  char* p = strstr(uri, "://");
380
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
418
  /* uri is now [user:password@]hostname:port */
419
0
  char* atPtr = strrchr(uri, '@');
420
421
0
  if (atPtr)
422
0
  {
423
    /* got a login / password,
424
     *         atPtr
425
     *         v
426
     * [user:password@]hostname:port
427
     *    ^
428
     *    colonPtr
429
     */
430
0
    char* colonPtr = strchr(uri, ':');
431
432
0
    if (!colonPtr || (colonPtr > atPtr))
433
0
    {
434
0
      WLog_ERR(TAG, "invalid syntax for proxy (contains no password)");
435
0
      goto fail;
436
0
    }
437
438
0
    *colonPtr = '\0';
439
0
    if (!freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, uri))
440
0
    {
441
0
      WLog_ERR(TAG, "unable to allocate proxy username");
442
0
      goto fail;
443
0
    }
444
445
0
    *atPtr = '\0';
446
447
0
    if (!freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, colonPtr + 1))
448
0
    {
449
0
      WLog_ERR(TAG, "unable to allocate proxy password");
450
0
      goto fail;
451
0
    }
452
453
0
    uri = atPtr + 1;
454
0
  }
455
456
0
  p = strchr(uri, ':');
457
458
0
  if (p)
459
0
  {
460
0
    LONGLONG val = 0;
461
462
0
    if (!value_to_int(&p[1], &val, 0, UINT16_MAX))
463
0
    {
464
0
      WLog_ERR(TAG, "invalid syntax for proxy (invalid port)");
465
0
      goto fail;
466
0
    }
467
468
0
    if (val == 0)
469
0
    {
470
0
      WLog_ERR(TAG, "invalid syntax for proxy (port missing)");
471
0
      goto fail;
472
0
    }
473
474
0
    port = (UINT16)val;
475
0
    *p = '\0';
476
0
  }
477
0
  else
478
0
  {
479
0
    if (_stricmp("http", protocol) == 0)
480
0
    {
481
      /* The default is 80. Also for Proxies. */
482
0
      port = 80;
483
0
    }
484
0
    else
485
0
    {
486
0
      port = 1080;
487
0
    }
488
489
0
    WLog_DBG(TAG, "setting default proxy port: %" PRIu16, port);
490
0
  }
491
492
0
  if (!freerdp_settings_set_uint16(settings, FreeRDP_ProxyPort, port))
493
0
    goto fail;
494
495
0
  p = strchr(uri, '/');
496
0
  if (p)
497
0
    *p = '\0';
498
0
  if (!freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, uri))
499
0
    goto fail;
500
501
0
  if (_stricmp("", uri) == 0)
502
0
  {
503
0
    WLog_ERR(TAG, "invalid syntax for proxy (hostname missing)");
504
0
    goto fail;
505
0
  }
506
507
0
  if (freerdp_settings_get_string(settings, FreeRDP_ProxyUsername))
508
0
  {
509
0
    WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%s@%s:%" PRIu16, protocol,
510
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyUsername), "******",
511
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
512
0
              freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
513
0
  }
514
0
  else
515
0
  {
516
0
    WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%" PRIu16, protocol,
517
0
              freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
518
0
              freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
519
0
  }
520
0
  rc = TRUE;
521
522
0
fail:
523
0
  if (!rc)
524
0
    WLog_WARN(TAG, "Failed to parse proxy configuration: %s://%s:%" PRIu16, protocol, uri,
525
0
              port);
526
0
  free(uri_copy);
527
0
  return rc;
528
0
}
529
530
BOOL proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
531
                   const char* proxyPassword, const char* hostname, UINT16 port)
532
0
{
533
0
  WINPR_ASSERT(context);
534
0
  rdpSettings* settings = context->settings;
535
536
0
  switch (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType))
537
0
  {
538
0
    case PROXY_TYPE_NONE:
539
0
    case PROXY_TYPE_IGNORE:
540
0
      return TRUE;
541
542
0
    case PROXY_TYPE_HTTP:
543
0
      return http_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
544
0
                                port);
545
546
0
    case PROXY_TYPE_SOCKS:
547
0
      return socks_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
548
0
                                 port);
549
550
0
    default:
551
0
      WLog_ERR(TAG, "Invalid internal proxy configuration");
552
0
      return FALSE;
553
0
  }
554
0
}
555
556
static const char* get_response_header(char* response)
557
0
{
558
0
  char* current_pos = strchr(response, '\r');
559
0
  if (!current_pos)
560
0
    current_pos = strchr(response, '\n');
561
562
0
  if (current_pos)
563
0
    *current_pos = '\0';
564
565
0
  return response;
566
0
}
567
568
static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
569
                               const char* proxyPassword, const char* hostname, UINT16 port)
570
0
{
571
0
  BOOL rc = FALSE;
572
0
  int status = 0;
573
0
  wStream* s = NULL;
574
0
  char port_str[10] = { 0 };
575
0
  char recv_buf[256] = { 0 };
576
0
  char* eol = NULL;
577
0
  size_t resultsize = 0;
578
0
  size_t reserveSize = 0;
579
0
  size_t portLen = 0;
580
0
  size_t hostLen = 0;
581
0
  const char connect[] = "CONNECT ";
582
0
  const char httpheader[] = " HTTP/1.1" CRLF "Host: ";
583
584
0
  WINPR_ASSERT(context);
585
0
  WINPR_ASSERT(bufferedBio);
586
0
  WINPR_ASSERT(hostname);
587
0
  const UINT32 timeout =
588
0
      freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
589
590
0
  _itoa_s(port, port_str, sizeof(port_str), 10);
591
592
0
  hostLen = strlen(hostname);
593
0
  portLen = strnlen(port_str, sizeof(port_str));
594
0
  reserveSize = strlen(connect) + (hostLen + 1 + portLen) * 2 + strlen(httpheader);
595
0
  s = Stream_New(NULL, reserveSize);
596
0
  if (!s)
597
0
    goto fail;
598
599
0
  Stream_Write(s, connect, strlen(connect));
600
0
  Stream_Write(s, hostname, hostLen);
601
0
  Stream_Write_UINT8(s, ':');
602
0
  Stream_Write(s, port_str, portLen);
603
0
  Stream_Write(s, httpheader, strlen(httpheader));
604
0
  Stream_Write(s, hostname, hostLen);
605
0
  Stream_Write_UINT8(s, ':');
606
0
  Stream_Write(s, port_str, portLen);
607
608
0
  if (proxyUsername && proxyPassword)
609
0
  {
610
0
    const int length = _scprintf("%s:%s", proxyUsername, proxyPassword);
611
0
    if (length > 0)
612
0
    {
613
0
      const size_t size = (size_t)length + 1ull;
614
0
      char* creds = (char*)malloc(size);
615
616
0
      if (!creds)
617
0
        goto fail;
618
0
      else
619
0
      {
620
0
        const char basic[] = CRLF "Proxy-Authorization: Basic ";
621
0
        char* base64 = NULL;
622
623
0
        (void)sprintf_s(creds, size, "%s:%s", proxyUsername, proxyPassword);
624
0
        base64 = crypto_base64_encode((const BYTE*)creds, size - 1);
625
626
0
        if (!base64 || !Stream_EnsureRemainingCapacity(s, strlen(basic) + strlen(base64)))
627
0
        {
628
0
          free(base64);
629
0
          free(creds);
630
0
          goto fail;
631
0
        }
632
0
        Stream_Write(s, basic, strlen(basic));
633
0
        Stream_Write(s, base64, strlen(base64));
634
635
0
        free(base64);
636
0
      }
637
0
      free(creds);
638
0
    }
639
0
  }
640
641
0
  if (!Stream_EnsureRemainingCapacity(s, 4))
642
0
    goto fail;
643
644
0
  Stream_Write(s, CRLF CRLF, 4);
645
0
  ERR_clear_error();
646
0
  const size_t pos = Stream_GetPosition(s);
647
0
  if (pos > INT32_MAX)
648
0
    goto fail;
649
650
0
  status = BIO_write(bufferedBio, Stream_Buffer(s), (int)pos);
651
652
0
  if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
653
0
  {
654
0
    WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
655
0
    goto fail;
656
0
  }
657
658
  /* Read result until CR-LF-CR-LF.
659
   * Keep recv_buf a null-terminated string. */
660
0
  const UINT64 start = GetTickCount64();
661
0
  while (strstr(recv_buf, CRLF CRLF) == NULL)
662
0
  {
663
0
    if (resultsize >= sizeof(recv_buf) - 1)
664
0
    {
665
0
      WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
666
0
      goto fail;
667
0
    }
668
0
    const size_t rdsize = sizeof(recv_buf) - resultsize - 1ULL;
669
670
0
    ERR_clear_error();
671
672
0
    WINPR_ASSERT(rdsize <= INT32_MAX);
673
0
    status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (int)rdsize);
674
675
0
    if (status < 0)
676
0
    {
677
      /* Error? */
678
0
      if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
679
0
      {
680
0
        USleep(100);
681
0
        continue;
682
0
      }
683
684
0
      WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
685
0
      goto fail;
686
0
    }
687
0
    else if (status == 0)
688
0
    {
689
0
      const UINT64 now = GetTickCount64();
690
0
      const UINT64 diff = now - start;
691
0
      if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
692
0
      {
693
        /* Error? */
694
0
        WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
695
0
        goto fail;
696
0
      }
697
0
      Sleep(10);
698
0
    }
699
0
    else
700
0
    {
701
      /* Error? */
702
0
      WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
703
0
      goto fail;
704
0
    }
705
706
0
    resultsize += WINPR_ASSERTING_INT_CAST(size_t, status);
707
0
  }
708
709
  /* Extract HTTP status line */
710
0
  eol = strchr(recv_buf, '\r');
711
712
0
  if (!eol)
713
0
  {
714
    /* should never happen */
715
0
    goto fail;
716
0
  }
717
718
0
  *eol = '\0';
719
0
  WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
720
721
0
  if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
722
0
    goto fail;
723
724
0
  recv_buf[7] = 'X';
725
726
0
  if (strncmp(recv_buf, "HTTP/1.X 200", 12) != 0)
727
0
    goto fail;
728
729
0
  rc = TRUE;
730
0
fail:
731
0
  Stream_Free(s, TRUE);
732
0
  return rc;
733
0
}
734
735
static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf, int len, char* reason,
736
                            BYTE checkVer)
737
0
{
738
0
  int status = 0;
739
740
0
  WINPR_ASSERT(context);
741
742
0
  const UINT32 timeout =
743
0
      freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
744
0
  const UINT64 start = GetTickCount64();
745
0
  for (;;)
746
0
  {
747
0
    ERR_clear_error();
748
0
    status = BIO_read(bufferedBio, buf, len);
749
750
0
    if (status > 0)
751
0
    {
752
0
      break;
753
0
    }
754
0
    else 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 %s reply from SOCKS proxy (Status %d)", reason, status);
764
0
      return -1;
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 %s reply from SOCKS proxy (BIO_read returned zero)",
774
0
                 reason);
775
0
        return status;
776
0
      }
777
0
      Sleep(10);
778
0
    }
779
0
    else // if (status == 0)
780
0
    {
781
      /* Error? */
782
0
      WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
783
0
               reason);
784
0
      return -1;
785
0
    }
786
0
  }
787
788
0
  if (status < 2)
789
0
  {
790
0
    WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
791
0
    return -1;
792
0
  }
793
794
0
  if (buf[0] != checkVer)
795
0
  {
796
0
    WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
797
0
    return -1;
798
0
  }
799
800
0
  return status;
801
0
}
802
803
static BOOL socks_proxy_userpass(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
804
                                 const char* proxyPassword)
805
0
{
806
0
  WINPR_ASSERT(context);
807
0
  WINPR_ASSERT(bufferedBio);
808
809
0
  if (!proxyUsername || !proxyPassword)
810
0
  {
811
0
    WLog_ERR(TAG, "%s invalid username (%p) or password (%p)", logprefix, proxyUsername,
812
0
             proxyPassword);
813
0
    return FALSE;
814
0
  }
815
816
0
  const size_t usernameLen = (BYTE)strnlen(proxyUsername, 256);
817
0
  if (usernameLen > 255)
818
0
  {
819
0
    WLog_ERR(TAG, "%s username too long (%" PRIuz ", max=255)", logprefix, usernameLen);
820
0
    return FALSE;
821
0
  }
822
823
0
  const size_t userpassLen = (BYTE)strnlen(proxyPassword, 256);
824
0
  if (userpassLen > 255)
825
0
  {
826
0
    WLog_ERR(TAG, "%s password too long (%" PRIuz ", max=255)", logprefix, userpassLen);
827
0
    return FALSE;
828
0
  }
829
830
  /* user/password v1 method */
831
0
  {
832
0
    BYTE buf[2 * 255 + 3] = { 0 };
833
0
    size_t offset = 0;
834
0
    buf[offset++] = 1;
835
836
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, usernameLen);
837
0
    memcpy(&buf[offset], proxyUsername, usernameLen);
838
0
    offset += usernameLen;
839
840
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, userpassLen);
841
0
    memcpy(&buf[offset], proxyPassword, userpassLen);
842
0
    offset += userpassLen;
843
844
0
    ERR_clear_error();
845
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
846
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
847
848
0
    if (status != ioffset)
849
0
    {
850
0
      WLog_ERR(TAG, "%s error writing user/password request", logprefix);
851
0
      return FALSE;
852
0
    }
853
0
  }
854
855
0
  BYTE buf[2] = { 0 };
856
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 1);
857
858
0
  if (status < 2)
859
0
    return FALSE;
860
861
0
  if (buf[1] != 0x00)
862
0
  {
863
0
    WLog_ERR(TAG, "%s invalid user/password", logprefix);
864
0
    return FALSE;
865
0
  }
866
0
  return TRUE;
867
0
}
868
869
static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
870
                                const char* proxyPassword, const char* hostname, UINT16 port)
871
0
{
872
0
  BYTE nauthMethods = 1;
873
0
  const size_t hostnlen = strnlen(hostname, 255);
874
875
0
  if (proxyUsername || proxyPassword)
876
0
    nauthMethods++;
877
878
  /* select auth. method */
879
0
  {
880
0
    const BYTE buf[] = { 5,            /* SOCKS version */
881
0
                       nauthMethods, /* #of methods offered */
882
0
                       AUTH_M_NO_AUTH, AUTH_M_USR_PASS };
883
884
0
    size_t writeLen = sizeof(buf);
885
0
    if (nauthMethods <= 1)
886
0
      writeLen--;
887
888
0
    ERR_clear_error();
889
0
    const int iwriteLen = WINPR_ASSERTING_INT_CAST(int, writeLen);
890
0
    const int status = BIO_write(bufferedBio, buf, iwriteLen);
891
892
0
    if (status != iwriteLen)
893
0
    {
894
0
      WLog_ERR(TAG, "SOCKS proxy: failed to write AUTH METHOD request", logprefix);
895
0
      return FALSE;
896
0
    }
897
0
  }
898
899
0
  {
900
0
    BYTE buf[2] = { 0 };
901
0
    const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 5);
902
903
0
    if (status <= 0)
904
0
      return FALSE;
905
906
0
    switch (buf[1])
907
0
    {
908
0
      case AUTH_M_NO_AUTH:
909
0
        WLog_DBG(TAG, "%s (NO AUTH) method was selected", logprefix);
910
0
        break;
911
912
0
      case AUTH_M_USR_PASS:
913
0
        if (nauthMethods < 2)
914
0
        {
915
0
          WLog_ERR(TAG, "%s USER/PASS method was not proposed to server", logprefix);
916
0
          return FALSE;
917
0
        }
918
0
        if (!socks_proxy_userpass(context, bufferedBio, proxyUsername, proxyPassword))
919
0
          return FALSE;
920
0
        break;
921
922
0
      default:
923
0
        WLog_ERR(TAG, "%s unknown method 0x%x was selected by proxy", logprefix, buf[1]);
924
0
        return FALSE;
925
0
    }
926
0
  }
927
  /* CONN request */
928
0
  {
929
0
    BYTE buf[262] = { 0 };
930
0
    size_t offset = 0;
931
0
    buf[offset++] = 5;                 /* SOCKS version */
932
0
    buf[offset++] = SOCKS_CMD_CONNECT; /* command */
933
0
    buf[offset++] = 0;                 /* 3rd octet is reserved x00 */
934
935
0
    if (inet_pton(AF_INET6, hostname, &buf[offset + 1]) == 1)
936
0
    {
937
0
      buf[offset++] = SOCKS_ADDR_IPV6;
938
0
      offset += 16;
939
0
    }
940
0
    else if (inet_pton(AF_INET, hostname, &buf[offset + 1]) == 1)
941
0
    {
942
0
      buf[offset++] = SOCKS_ADDR_IPV4;
943
0
      offset += 4;
944
0
    }
945
0
    else
946
0
    {
947
0
      buf[offset++] = SOCKS_ADDR_FQDN;
948
0
      buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, hostnlen);
949
0
      memcpy(&buf[offset], hostname, hostnlen);
950
0
      offset += hostnlen;
951
0
    }
952
953
0
    if (offset > sizeof(buf) - 2)
954
0
      return FALSE;
955
956
    /* follows DST.PORT in netw. format */
957
0
    buf[offset++] = (port >> 8) & 0xff;
958
0
    buf[offset++] = port & 0xff;
959
960
0
    ERR_clear_error();
961
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
962
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
963
964
0
    if ((status < 0) || (status != ioffset))
965
0
    {
966
0
      WLog_ERR(TAG, "SOCKS proxy: failed to write CONN REQ", logprefix);
967
0
      return FALSE;
968
0
    }
969
0
  }
970
971
0
  BYTE buf[255] = { 0 };
972
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
973
974
0
  if (status < 4)
975
0
    return FALSE;
976
977
0
  if (buf[1] == 0)
978
0
  {
979
0
    WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
980
0
    return TRUE;
981
0
  }
982
983
0
  if ((buf[1] > 0) && (buf[1] < 9))
984
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
985
0
  else
986
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
987
988
0
  return FALSE;
989
0
}