Coverage Report

Created: 2026-04-12 07:03

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