Coverage Report

Created: 2026-03-04 06:17

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, nullptr, 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 = WINPR_C_ARRAY_INIT;
218
0
  struct sockaddr_in6 sa6 = WINPR_C_ARRAY_INIT;
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 = WINPR_C_ARRAY_INIT;
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, nullptr, 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] = WINPR_C_ARRAY_INIT;
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 = WINPR_C_ARRAY_INIT;
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, nullptr, 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 = nullptr;
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 (%" PRIuz ")", 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(nullptr, 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, nullptr, 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 = nullptr;
580
0
  char port_str[10] = WINPR_C_ARRAY_INIT;
581
0
  char recv_buf[256] = WINPR_C_ARRAY_INIT;
582
0
  char* eol = nullptr;
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
  if (_itoa_s(port, port_str, sizeof(port_str), 10) < 0)
597
0
    return FALSE;
598
599
0
  hostLen = strlen(hostname);
600
0
  portLen = strnlen(port_str, sizeof(port_str));
601
0
  reserveSize = strlen(connect) + (hostLen + 1ull + portLen) * 2ull + strlen(httpheader);
602
0
  s = Stream_New(nullptr, reserveSize);
603
0
  if (!s)
604
0
    goto fail;
605
606
0
  Stream_Write(s, connect, strlen(connect));
607
0
  Stream_Write(s, hostname, hostLen);
608
0
  Stream_Write_UINT8(s, ':');
609
0
  Stream_Write(s, port_str, portLen);
610
0
  Stream_Write(s, httpheader, strlen(httpheader));
611
0
  Stream_Write(s, hostname, hostLen);
612
0
  Stream_Write_UINT8(s, ':');
613
0
  Stream_Write(s, port_str, portLen);
614
615
0
  if (proxyUsername && proxyPassword)
616
0
  {
617
0
    const int length = _scprintf("%s:%s", proxyUsername, proxyPassword);
618
0
    if (length > 0)
619
0
    {
620
0
      const size_t size = (size_t)length + 1ull;
621
0
      char* creds = (char*)malloc(size);
622
623
0
      if (!creds)
624
0
        goto fail;
625
0
      else
626
0
      {
627
0
        const char basic[] = CRLF "Proxy-Authorization: Basic ";
628
0
        char* base64 = nullptr;
629
630
0
        (void)sprintf_s(creds, size, "%s:%s", proxyUsername, proxyPassword);
631
0
        base64 = crypto_base64_encode((const BYTE*)creds, size - 1);
632
633
0
        if (!base64 || !Stream_EnsureRemainingCapacity(s, strlen(basic) + strlen(base64)))
634
0
        {
635
0
          free(base64);
636
0
          free(creds);
637
0
          goto fail;
638
0
        }
639
0
        Stream_Write(s, basic, strlen(basic));
640
0
        Stream_Write(s, base64, strlen(base64));
641
642
0
        free(base64);
643
0
      }
644
0
      free(creds);
645
0
    }
646
0
  }
647
648
0
  if (!Stream_EnsureRemainingCapacity(s, 4))
649
0
    goto fail;
650
651
0
  Stream_Write(s, CRLF CRLF, 4);
652
0
  ERR_clear_error();
653
654
0
  {
655
0
    const size_t pos = Stream_GetPosition(s);
656
0
    if (pos > INT32_MAX)
657
0
      goto fail;
658
659
0
    status = BIO_write(bufferedBio, Stream_Buffer(s), WINPR_ASSERTING_INT_CAST(int, pos));
660
0
  }
661
662
0
  if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
663
0
  {
664
0
    WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
665
0
    goto fail;
666
0
  }
667
668
  /* Read result until CR-LF-CR-LF.
669
   * Keep recv_buf a null-terminated string. */
670
0
  {
671
0
    const UINT64 start = GetTickCount64();
672
0
    while (strstr(recv_buf, CRLF CRLF) == nullptr)
673
0
    {
674
0
      if (resultsize >= sizeof(recv_buf) - 1)
675
0
      {
676
0
        WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
677
0
        goto fail;
678
0
      }
679
0
      const size_t rdsize = sizeof(recv_buf) - resultsize - 1ULL;
680
681
0
      ERR_clear_error();
682
683
0
      WINPR_ASSERT(rdsize <= INT32_MAX);
684
0
      status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (int)rdsize);
685
686
0
      if (status < 0)
687
0
      {
688
        /* Error? */
689
0
        if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
690
0
        {
691
0
          USleep(100);
692
0
          continue;
693
0
        }
694
695
0
        WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
696
0
        goto fail;
697
0
      }
698
0
      else if (status == 0)
699
0
      {
700
0
        const UINT64 now = GetTickCount64();
701
0
        const UINT64 diff = now - start;
702
0
        if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
703
0
        {
704
          /* Error? */
705
0
          WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
706
0
          goto fail;
707
0
        }
708
0
        Sleep(10);
709
0
      }
710
711
0
      resultsize += WINPR_ASSERTING_INT_CAST(size_t, status);
712
0
    }
713
0
  }
714
715
  /* Extract HTTP status line */
716
0
  eol = strchr(recv_buf, '\r');
717
718
0
  if (!eol)
719
0
  {
720
    /* should never happen */
721
0
    goto fail;
722
0
  }
723
724
0
  *eol = '\0';
725
0
  WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
726
727
0
  if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
728
0
    goto fail;
729
730
0
  recv_buf[7] = 'X';
731
732
0
  if (strncmp(recv_buf, "HTTP/1.X 200", 12) != 0)
733
0
    goto fail;
734
735
0
  rc = TRUE;
736
0
fail:
737
0
  Stream_Free(s, TRUE);
738
0
  return rc;
739
0
}
740
741
static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf, int len, char* reason,
742
                            BYTE checkVer)
743
0
{
744
0
  int status = 0;
745
746
0
  WINPR_ASSERT(context);
747
748
0
  const UINT32 timeout =
749
0
      freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
750
0
  const UINT64 start = GetTickCount64();
751
0
  for (;;)
752
0
  {
753
0
    ERR_clear_error();
754
0
    status = BIO_read(bufferedBio, buf, len);
755
756
0
    if (status > 0)
757
0
    {
758
0
      break;
759
0
    }
760
0
    else if (status < 0)
761
0
    {
762
      /* Error? */
763
0
      if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
764
0
      {
765
0
        USleep(100);
766
0
        continue;
767
0
      }
768
769
0
      WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
770
0
      return -1;
771
0
    }
772
0
    else if (status == 0)
773
0
    {
774
0
      const UINT64 now = GetTickCount64();
775
0
      const UINT64 diff = now - start;
776
0
      if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
777
0
      {
778
        /* Error? */
779
0
        WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
780
0
                 reason);
781
0
        return status;
782
0
      }
783
0
      Sleep(10);
784
0
    }
785
0
    else // if (status == 0)
786
0
    {
787
      /* Error? */
788
0
      WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
789
0
               reason);
790
0
      return -1;
791
0
    }
792
0
  }
793
794
0
  if (status < 2)
795
0
  {
796
0
    WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
797
0
    return -1;
798
0
  }
799
800
0
  if (buf[0] != checkVer)
801
0
  {
802
0
    WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
803
0
    return -1;
804
0
  }
805
806
0
  return status;
807
0
}
808
809
static BOOL socks_proxy_userpass(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
810
                                 const char* proxyPassword)
811
0
{
812
0
  WINPR_ASSERT(context);
813
0
  WINPR_ASSERT(bufferedBio);
814
815
0
  if (!proxyUsername || !proxyPassword)
816
0
  {
817
0
    WLog_ERR(TAG, "%s invalid username (%p) or password (%p)", logprefix,
818
0
             WINPR_CXX_COMPAT_CAST(const void*, proxyUsername),
819
0
             WINPR_CXX_COMPAT_CAST(const void*, proxyPassword));
820
0
    return FALSE;
821
0
  }
822
823
0
  const size_t usernameLen = (BYTE)strnlen(proxyUsername, 256);
824
0
  if (usernameLen > 255)
825
0
  {
826
0
    WLog_ERR(TAG, "%s username too long (%" PRIuz ", max=255)", logprefix, usernameLen);
827
0
    return FALSE;
828
0
  }
829
830
0
  const size_t userpassLen = (BYTE)strnlen(proxyPassword, 256);
831
0
  if (userpassLen > 255)
832
0
  {
833
0
    WLog_ERR(TAG, "%s password too long (%" PRIuz ", max=255)", logprefix, userpassLen);
834
0
    return FALSE;
835
0
  }
836
837
  /* user/password v1 method */
838
0
  {
839
0
    BYTE buf[2 * 255 + 3] = WINPR_C_ARRAY_INIT;
840
0
    size_t offset = 0;
841
0
    buf[offset++] = 1;
842
843
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, usernameLen);
844
0
    memcpy(&buf[offset], proxyUsername, usernameLen);
845
0
    offset += usernameLen;
846
847
0
    buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, userpassLen);
848
0
    memcpy(&buf[offset], proxyPassword, userpassLen);
849
0
    offset += userpassLen;
850
851
0
    ERR_clear_error();
852
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
853
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
854
855
0
    if (status != ioffset)
856
0
    {
857
0
      WLog_ERR(TAG, "%s error writing user/password request", logprefix);
858
0
      return FALSE;
859
0
    }
860
0
  }
861
862
0
  BYTE buf[2] = WINPR_C_ARRAY_INIT;
863
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 1);
864
865
0
  if (status < 2)
866
0
    return FALSE;
867
868
0
  if (buf[1] != 0x00)
869
0
  {
870
0
    WLog_ERR(TAG, "%s invalid user/password", logprefix);
871
0
    return FALSE;
872
0
  }
873
0
  return TRUE;
874
0
}
875
876
static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
877
                                const char* proxyPassword, const char* hostname, UINT16 port)
878
0
{
879
0
  BYTE nauthMethods = 1;
880
0
  const size_t hostnlen = strnlen(hostname, 255);
881
882
0
  if (proxyUsername || proxyPassword)
883
0
    nauthMethods++;
884
885
  /* select auth. method */
886
0
  {
887
0
    const BYTE buf[] = { 5,            /* SOCKS version */
888
0
                       nauthMethods, /* #of methods offered */
889
0
                       AUTH_M_NO_AUTH, AUTH_M_USR_PASS };
890
891
0
    size_t writeLen = sizeof(buf);
892
0
    if (nauthMethods <= 1)
893
0
      writeLen--;
894
895
0
    ERR_clear_error();
896
0
    const int iwriteLen = WINPR_ASSERTING_INT_CAST(int, writeLen);
897
0
    const int status = BIO_write(bufferedBio, buf, iwriteLen);
898
899
0
    if (status != iwriteLen)
900
0
    {
901
0
      WLog_ERR(TAG, "%s SOCKS proxy: failed to write AUTH METHOD request", logprefix);
902
0
      return FALSE;
903
0
    }
904
0
  }
905
906
0
  {
907
0
    BYTE buf[2] = WINPR_C_ARRAY_INIT;
908
0
    const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 5);
909
910
0
    if (status <= 0)
911
0
      return FALSE;
912
913
0
    switch (buf[1])
914
0
    {
915
0
      case AUTH_M_NO_AUTH:
916
0
        WLog_DBG(TAG, "%s (NO AUTH) method was selected", logprefix);
917
0
        break;
918
919
0
      case AUTH_M_USR_PASS:
920
0
        if (nauthMethods < 2)
921
0
        {
922
0
          WLog_ERR(TAG, "%s USER/PASS method was not proposed to server", logprefix);
923
0
          return FALSE;
924
0
        }
925
0
        if (!socks_proxy_userpass(context, bufferedBio, proxyUsername, proxyPassword))
926
0
          return FALSE;
927
0
        break;
928
929
0
      default:
930
0
        WLog_ERR(TAG, "%s unknown method 0x%x was selected by proxy", logprefix, buf[1]);
931
0
        return FALSE;
932
0
    }
933
0
  }
934
  /* CONN request */
935
0
  {
936
0
    BYTE buf[262] = WINPR_C_ARRAY_INIT;
937
0
    size_t offset = 0;
938
0
    buf[offset++] = 5;                 /* SOCKS version */
939
0
    buf[offset++] = SOCKS_CMD_CONNECT; /* command */
940
0
    buf[offset++] = 0;                 /* 3rd octet is reserved x00 */
941
942
0
    if (inet_pton(AF_INET6, hostname, &buf[offset + 1]) == 1)
943
0
    {
944
0
      buf[offset++] = SOCKS_ADDR_IPV6;
945
0
      offset += 16;
946
0
    }
947
0
    else if (inet_pton(AF_INET, hostname, &buf[offset + 1]) == 1)
948
0
    {
949
0
      buf[offset++] = SOCKS_ADDR_IPV4;
950
0
      offset += 4;
951
0
    }
952
0
    else
953
0
    {
954
0
      buf[offset++] = SOCKS_ADDR_FQDN;
955
0
      buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, hostnlen);
956
0
      memcpy(&buf[offset], hostname, hostnlen);
957
0
      offset += hostnlen;
958
0
    }
959
960
0
    if (offset > sizeof(buf) - 2)
961
0
      return FALSE;
962
963
    /* follows DST.PORT in netw. format */
964
0
    buf[offset++] = (port >> 8) & 0xff;
965
0
    buf[offset++] = port & 0xff;
966
967
0
    ERR_clear_error();
968
0
    const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
969
0
    const int status = BIO_write(bufferedBio, buf, ioffset);
970
971
0
    if ((status < 0) || (status != ioffset))
972
0
    {
973
0
      WLog_ERR(TAG, "%s SOCKS proxy: failed to write CONN REQ", logprefix);
974
0
      return FALSE;
975
0
    }
976
0
  }
977
978
0
  BYTE buf[255] = WINPR_C_ARRAY_INIT;
979
0
  const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
980
981
0
  if (status < 4)
982
0
    return FALSE;
983
984
0
  if (buf[1] == 0)
985
0
  {
986
0
    WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
987
0
    return TRUE;
988
0
  }
989
990
0
  if ((buf[1] > 0) && (buf[1] < 9))
991
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
992
0
  else
993
0
    WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
994
995
0
  return FALSE;
996
0
}