/
http.c
1455 lines (1263 loc) · 35.1 KB
1
/*
2
* OpenConnect (SSL + DTLS) VPN client
3
*
4
* Copyright © 2008-2015 Intel Corporation.
5
* Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
6
*
7
8
9
* Author: David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU Lesser General Public License
11
* version 2.1, as published by the Free Software Foundation.
12
*
13
* This program is distributed in the hope that it will be useful, but
14
15
16
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
17
18
*/
19
20
#include <config.h>
21
22
23
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
24
#include <string.h>
25
#include <ctype.h>
26
27
28
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
29
#include <stdarg.h>
30
31
#include "openconnect-internal.h"
32
33
34
static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len);
static int proxy_read(struct openconnect_info *vpninfo, char *buf, size_t len);
35
36
#define MAX_BUF_LEN 131072
37
38
#define BUF_CHUNK_SIZE 4096
39
struct oc_text_buf *buf_alloc(void)
40
41
42
43
{
return calloc(1, sizeof(struct oc_text_buf));
}
44
void buf_append_urlencoded(struct oc_text_buf *buf, const char *str)
45
46
{
while (str && *str) {
47
unsigned char c = *str;
48
if (c < 0x80 && (isalnum((int)(c)) || c=='-' || c=='_' || c=='.' || c=='~'))
49
50
buf_append_bytes(buf, str, 1);
else
51
52
buf_append(buf, "%%%02x", c);
53
54
55
56
str++;
}
}
57
58
59
60
61
62
63
64
65
void buf_append_hex(struct oc_text_buf *buf, const void *str, unsigned len)
{
const unsigned char *data = str;
unsigned i;
for (i = 0; i < len; i++)
buf_append(buf, "%02x", (unsigned)data[i]);
}
66
67
68
69
70
71
72
73
74
75
void buf_truncate(struct oc_text_buf *buf)
{
if (!buf)
return;
buf->pos = 0;
if (buf->data)
buf->data[0] = 0;
}
76
int buf_ensure_space(struct oc_text_buf *buf, int len)
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
{
int new_buf_len;
new_buf_len = (buf->pos + len + BUF_CHUNK_SIZE - 1) & ~(BUF_CHUNK_SIZE - 1);
if (new_buf_len <= buf->buf_len)
return 0;
if (new_buf_len > MAX_BUF_LEN) {
buf->error = -E2BIG;
return buf->error;
} else {
realloc_inplace(buf->data, new_buf_len);
if (!buf->data)
buf->error = -ENOMEM;
else
buf->buf_len = new_buf_len;
}
return buf->error;
}
98
99
void __attribute__ ((format (printf, 2, 3)))
buf_append(struct oc_text_buf *buf, const char *fmt, ...)
100
101
102
103
104
105
{
va_list ap;
if (!buf || buf->error)
return;
106
107
if (buf_ensure_space(buf, 1))
return;
108
109
110
111
112
113
114
115
116
117
118
119
120
while (1) {
int max_len = buf->buf_len - buf->pos, ret;
va_start(ap, fmt);
ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
va_end(ap);
if (ret < 0) {
buf->error = -EIO;
break;
} else if (ret < max_len) {
buf->pos += ret;
break;
121
122
} else if (buf_ensure_space(buf, ret))
break;
123
124
125
}
}
126
127
128
129
130
void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len)
{
if (!buf || buf->error)
return;
131
if (buf_ensure_space(buf, len + 1))
132
return;
133
134
135
memcpy(buf->data + buf->pos, bytes, len);
buf->pos += len;
136
buf->data[buf->pos] = 0;
137
138
}
139
140
141
142
143
144
145
146
147
148
149
void buf_append_from_utf16le(struct oc_text_buf *buf, const void *_utf16)
{
const unsigned char *utf16 = _utf16;
unsigned char utf8[4];
int c;
if (!utf16)
return;
while (utf16[0] || utf16[1]) {
if ((utf16[1] & 0xfc) == 0xd8 && (utf16[3] & 0xfc) == 0xdc) {
150
151
c = ((load_le16(utf16) & 0x3ff) << 10)|
(load_le16(utf16 + 2) & 0x3ff);
152
153
154
c += 0x10000;
utf16 += 4;
} else {
155
c = load_le16(utf16);
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
utf16 += 2;
}
if (c < 0x80) {
utf8[0] = c;
buf_append_bytes(buf, utf8, 1);
} else if (c < 0x800) {
utf8[0] = 0xc0 | (c >> 6);
utf8[1] = 0x80 | (c & 0x3f);
buf_append_bytes(buf, utf8, 2);
} else if (c < 0x10000) {
utf8[0] = 0xe0 | (c >> 12);
utf8[1] = 0x80 | ((c >> 6) & 0x3f);
utf8[2] = 0x80 | (c & 0x3f);
buf_append_bytes(buf, utf8, 3);
} else {
utf8[0] = 0xf0 | (c >> 18);
utf8[1] = 0x80 | ((c >> 12) & 0x3f);
utf8[2] = 0x80 | ((c >> 6) & 0x3f);
utf8[3] = 0x80 | (c & 0x3f);
buf_append_bytes(buf, utf8, 4);
}
}
utf8[0] = 0;
buf_append_bytes(buf, utf8, 1);
}
183
int get_utf8char(const char **p)
184
{
185
const char *utf8 = *p;
186
unsigned char c;
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
int utfchar, nr_extra, min;
c = *(utf8++);
if (c < 128) {
utfchar = c;
nr_extra = 0;
min = 0;
} else if ((c & 0xe0) == 0xc0) {
utfchar = c & 0x1f;
nr_extra = 1;
min = 0x80;
} else if ((c & 0xf0) == 0xe0) {
utfchar = c & 0x0f;
nr_extra = 2;
min = 0x800;
} else if ((c & 0xf8) == 0xf0) {
utfchar = c & 0x07;
nr_extra = 3;
min = 0x10000;
} else {
return -EILSEQ;
}
209
210
while (nr_extra--) {
211
c = *(utf8++);
212
if ((c & 0xc0) != 0x80)
213
return -EILSEQ;
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
utfchar <<= 6;
utfchar |= (c & 0x3f);
}
if (utfchar > 0x10ffff || utfchar < min)
return -EILSEQ;
*p = utf8;
return utfchar;
}
int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8)
{
int utfchar, len = 0;
/* Ick. Now I'm implementing my own UTF8 handling too. Perhaps it's
time to bite the bullet and start requiring something like glib? */
while (*utf8) {
utfchar = get_utf8char(&utf8);
if (utfchar < 0) {
234
if (buf)
235
236
buf->error = utfchar;
return utfchar;
237
238
239
}
if (!buf)
continue;
240
241
242
243
if (utfchar >= 0x10000) {
utfchar -= 0x10000;
if (buf_ensure_space(buf, 4))
244
return buf_error(buf);
245
246
247
store_le16(buf->data + buf->pos, (utfchar >> 10) | 0xd800);
store_le16(buf->data + buf->pos + 2, (utfchar & 0x3ff) | 0xdc00);
buf->pos += 4;
248
249
250
len += 4;
} else {
if (buf_ensure_space(buf, 2))
251
return buf_error(buf);
252
253
store_le16(buf->data + buf->pos, utfchar);
buf->pos += 2;
254
255
256
len += 2;
}
}
257
258
259
260
261
/* We were only being used for validation */
if (!buf)
return 0;
262
263
/* Ensure UTF16 is NUL-terminated */
if (buf_ensure_space(buf, 2))
264
return buf_error(buf);
265
266
buf->data[buf->pos] = buf->data[buf->pos + 1] = 0;
267
268
269
return len;
}
270
int buf_error(struct oc_text_buf *buf)
271
272
273
274
{
return buf ? buf->error : -ENOMEM;
}
275
int buf_free(struct oc_text_buf *buf)
276
277
278
279
280
281
282
283
284
285
286
287
{
int error = buf_error(buf);
if (buf) {
if (buf->data)
free(buf->data);
free(buf);
}
return error;
}
288
/*
289
* We didn't really want to have to do this for ourselves -- one might have
290
291
292
293
294
295
* thought that it would be available in a library somewhere. But neither
* cURL nor Neon have reliable cross-platform ways of either using a cert
* from the TPM, or just reading from / writing to a transport which is
* provided by their caller.
*/
296
297
int http_add_cookie(struct openconnect_info *vpninfo, const char *option,
const char *value, int replace)
298
{
299
struct oc_vpn_option *new, **this;
300
301
302
303
if (*value) {
new = malloc(sizeof(*new));
if (!new) {
304
305
vpn_progress(vpninfo, PRG_ERR,
_("No memory for allocating cookies\n"));
306
307
308
309
310
return -ENOMEM;
}
new->next = NULL;
new->option = strdup(option);
new->value = strdup(value);
311
312
313
314
315
316
if (!new->option || !new->value) {
free(new->option);
free(new->value);
free(new);
return -ENOMEM;
}
317
318
319
} else {
/* Kill cookie; don't replace it */
new = NULL;
320
321
322
/* This would be meaningless */
if (!replace)
return -EINVAL;
323
324
325
}
for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
if (!strcmp(option, (*this)->option)) {
326
327
328
329
330
331
if (!replace) {
free(new->value);
free(new->option);
free(new);
return 0;
}
332
333
334
335
336
/* Replace existing cookie */
if (new)
new->next = (*this)->next;
else
new = (*this)->next;
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
free((*this)->option);
free((*this)->value);
free(*this);
*this = new;
break;
}
}
if (new && !*this) {
*this = new;
new->next = NULL;
}
return 0;
}
352
353
354
#define BODY_HTTP10 -1
#define BODY_CHUNKED -2
355
356
357
int process_http_response(struct openconnect_info *vpninfo, int connect,
int (*header_cb)(struct openconnect_info *, char *, char *),
struct oc_text_buf *body)
358
{
359
char buf[MAX_BUF_LEN];
360
361
int bodylen = BODY_HTTP10;
int closeconn = 0;
362
int result;
363
364
int i;
365
366
buf_truncate(body);
367
cont:
368
if (vpninfo->ssl_gets(vpninfo, buf, sizeof(buf)) < 0) {
369
370
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTPS response\n"));
371
openconnect_close_https(vpninfo, 0);
372
return -EINVAL;
373
374
}
375
if (!strncmp(buf, "HTTP/1.0 ", 9))
376
closeconn = 1;
377
378
if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(result = atoi(buf+9))) {
379
380
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse HTTP response '%s'\n"), buf);
381
openconnect_close_https(vpninfo, 0);
382
383
384
return -EINVAL;
}
385
vpn_progress(vpninfo, (result == 200 || result == 407) ? PRG_DEBUG : PRG_INFO,
386
_("Got HTTP response: %s\n"), buf);
387
388
/* Eat headers... */
389
while ((i = vpninfo->ssl_gets(vpninfo, buf, sizeof(buf)))) {
390
391
392
char *colon;
if (i < 0) {
393
394
vpn_progress(vpninfo, PRG_ERR,
_("Error processing HTTP response\n"));
395
openconnect_close_https(vpninfo, 0);
396
397
398
399
return -EINVAL;
}
colon = strchr(buf, ':');
if (!colon) {
400
401
vpn_progress(vpninfo, PRG_ERR,
_("Ignoring unknown HTTP response line '%s'\n"), buf);
402
403
404
405
406
407
continue;
}
*(colon++) = 0;
if (*colon == ' ')
colon++;
408
409
410
411
/* Handle Set-Cookie first so that we can avoid printing the
webvpn cookie in the verbose debug output */
if (!strcasecmp(buf, "Set-Cookie")) {
char *semicolon = strchr(colon, ';');
412
413
const char *print_equals;
char *equals = strchr(colon, '=');
414
415
416
417
418
419
int ret;
if (semicolon)
*semicolon = 0;
if (!equals) {
420
421
vpn_progress(vpninfo, PRG_ERR,
_("Invalid cookie offered: %s\n"), buf);
422
423
424
425
426
return -EINVAL;
}
*(equals++) = 0;
print_equals = equals;
427
428
429
/* Don't print the webvpn cookie unless it's empty; we don't
want people posting it in public with debugging output */
if (!strcmp(colon, "webvpn") && *equals)
430
print_equals = _("<elided>");
431
vpn_progress(vpninfo, PRG_DEBUG, "%s: %s=%s%s%s\n",
432
433
buf, colon, print_equals, semicolon ? ";" : "",
semicolon ? (semicolon+1) : "");
434
435
436
437
438
439
440
441
/* The server tends to ask for the username and password as
usual, even if we've already failed because it didn't like
our cert. Thankfully it does give us this hint... */
if (!strcmp(colon, "ClientCertAuthFailed"))
vpn_progress(vpninfo, PRG_ERR,
_("SSL certificate authentication failed\n"));
442
ret = http_add_cookie(vpninfo, colon, equals, 1);
443
444
445
if (ret)
return ret;
} else {
446
vpn_progress(vpninfo, PRG_DEBUG, "%s: %s\n", buf, colon);
447
448
}
449
450
451
if (!strcasecmp(buf, "Connection")) {
if (!strcasecmp(colon, "Close"))
closeconn = 1;
452
453
454
455
456
457
#if 0
/* This might seem reasonable, but in fact it breaks
certificate authentication with some servers. If
they give an HTTP/1.0 response, even if they
explicitly give a Connection: Keep-Alive header,
just close the connection. */
458
459
else if (!strcasecmp(colon, "Keep-Alive"))
closeconn = 0;
460
#endif
461
}
462
if (!strcasecmp(buf, "Location")) {
463
464
465
466
vpninfo->redirect_url = strdup(colon);
if (!vpninfo->redirect_url)
return -ENOMEM;
}
467
if (!strcasecmp(buf, "Content-Length")) {
468
bodylen = atoi(colon);
469
if (bodylen < 0) {
470
471
472
vpn_progress(vpninfo, PRG_ERR,
_("Response body has negative size (%d)\n"),
bodylen);
473
openconnect_close_https(vpninfo, 0);
474
475
476
return -EINVAL;
}
}
477
478
if (!strcasecmp(buf, "Transfer-Encoding")) {
if (!strcasecmp(colon, "chunked"))
479
bodylen = BODY_CHUNKED;
480
else {
481
482
483
vpn_progress(vpninfo, PRG_ERR,
_("Unknown Transfer-Encoding: %s\n"),
colon);
484
openconnect_close_https(vpninfo, 0);
485
486
487
return -EINVAL;
}
}
488
if (header_cb)
489
490
491
492
header_cb(vpninfo, buf, colon);
}
/* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
493
if (result == 100)
494
495
goto cont;
496
/* On successful CONNECT, there is no body. Return success */
497
498
if (connect && result == 200)
return result;
499
500
/* Now the body, if there is one */
501
vpn_progress(vpninfo, PRG_DEBUG, _("HTTP body %s (%d)\n"),
502
503
bodylen == BODY_HTTP10 ? "http 1.0" :
bodylen == BODY_CHUNKED ? "chunked" : "length: ",
504
bodylen);
505
506
507
/* If we were given Content-Length, it's nice and easy... */
if (bodylen > 0) {
508
509
510
511
512
if (buf_ensure_space(body, bodylen + 1))
return buf_error(body);
while (body->pos < bodylen) {
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, bodylen - body->pos);
513
if (i < 0) {
514
515
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
516
openconnect_close_https(vpninfo, 0);
517
518
return -EINVAL;
}
519
body->pos += i;
520
}
521
522
} else if (bodylen == BODY_CHUNKED) {
/* ... else, chunked */
523
while ((i = vpninfo->ssl_gets(vpninfo, buf, sizeof(buf)))) {
524
int chunklen, lastchunk = 0;
525
526
if (i < 0) {
527
528
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching chunk header\n"));
529
return i;
530
531
532
533
534
535
}
chunklen = strtol(buf, NULL, 16);
if (!chunklen) {
lastchunk = 1;
goto skip;
}
536
537
if (buf_ensure_space(body, chunklen + 1))
return buf_error(body);
538
while (chunklen) {
539
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, chunklen);
540
if (i < 0) {
541
542
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
543
544
545
return -EINVAL;
}
chunklen -= i;
546
body->pos += i;
547
548
}
skip:
549
if ((i = vpninfo->ssl_gets(vpninfo, buf, sizeof(buf)))) {
550
if (i < 0) {
551
552
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTP response body\n"));
553
} else {
554
555
556
vpn_progress(vpninfo, PRG_ERR,
_("Error in chunked decoding. Expected '', got: '%s'"),
buf);
557
558
}
return -EINVAL;
559
}
560
561
562
563
564
565
if (lastchunk)
break;
}
} else if (bodylen == BODY_HTTP10) {
if (!closeconn) {
566
567
vpn_progress(vpninfo, PRG_ERR,
_("Cannot receive HTTP 1.0 body without closing connection\n"));
568
openconnect_close_https(vpninfo, 0);
569
570
571
return -EINVAL;
}
572
/* HTTP 1.0 response. Just eat all we can in 4KiB chunks */
573
while (1) {
574
575
576
577
if (buf_ensure_space(body, 4096 + 1))
return buf_error(body);
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, 4096);
if (i < 0) {
578
/* Error */
579
openconnect_close_https(vpninfo, 0);
580
return i;
581
} else if (!i)
582
break;
583
584
585
/* Got more data */
body->pos += i;
586
}
587
}
588
589
if (closeconn || vpninfo->no_http_keepalive)
590
openconnect_close_https(vpninfo, 0);
591
592
593
body->data[body->pos] = 0;
return result;
594
595
}
596
int internal_parse_url(const char *url, char **res_proto, char **res_host,
597
int *res_port, char **res_path, int default_port)
598
{
599
600
601
const char *orig_host, *orig_path;
char *host, *port_str;
int port, proto_len = 0;
602
603
604
605
606
orig_host = strstr(url, "://");
if (orig_host) {
proto_len = orig_host - url;
orig_host += 3;
607
608
if (strprefix_match(url, proto_len, "https"))
609
port = 443;
610
else if (strprefix_match(url, proto_len, "http"))
611
port = 80;
612
613
614
else if (strprefix_match(url, proto_len, "socks") ||
strprefix_match(url, proto_len, "socks4") ||
strprefix_match(url, proto_len, "socks5"))
615
616
617
618
619
620
port = 1080;
else
return -EPROTONOSUPPORT;
} else {
if (default_port) {
port = default_port;
621
orig_host = url;
622
623
624
} else
return -EINVAL;
}
625
626
627
628
629
630
631
632
633
orig_path = strchr(orig_host, '/');
if (orig_path) {
host = strndup(orig_host, orig_path - orig_host);
orig_path++;
} else
host = strdup(orig_host);
if (!host)
return -ENOMEM;
634
635
636
637
638
639
640
641
642
643
644
645
646
port_str = strrchr(host, ':');
if (port_str) {
char *end;
int new_port = strtol(port_str + 1, &end, 10);
if (!*end) {
*port_str = 0;
port = new_port;
}
}
if (res_proto)
647
*res_proto = proto_len ? strndup(url, proto_len) : NULL;
648
if (res_host)
649
650
651
*res_host = host;
else
free(host);
652
653
654
if (res_port)
*res_port = port;
if (res_path)
655
656
*res_path = (orig_path && *orig_path) ? strdup(orig_path) : NULL;
657
658
659
return 0;
}
660
void openconnect_clear_cookies(struct openconnect_info *vpninfo)
661
{
662
struct oc_vpn_option *opt, *next;
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
for (opt = vpninfo->cookies; opt; opt = next) {
next = opt->next;
free(opt->option);
free(opt->value);
free(opt);
}
vpninfo->cookies = NULL;
}
/* Return value:
* < 0, on error
* = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
*/
678
int handle_redirect(struct openconnect_info *vpninfo)
679
{
680
681
vpninfo->redirect_type = REDIR_TYPE_LOCAL;
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
/* New host. Tear down the existing connection and make a new one */
char *host;
int port;
int ret;
free(vpninfo->urlpath);
vpninfo->urlpath = NULL;
ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
if (ret) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse redirected URL '%s': %s\n"),
vpninfo->redirect_url, strerror(-ret));
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
return ret;
}
if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
702
openconnect_set_hostname(vpninfo, host);
703
704
705
706
vpninfo->port = port;
/* Kill the existing connection, and a new one will happen */
openconnect_close_https(vpninfo, 0);
707
openconnect_clear_cookies(vpninfo);
708
vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
709
710
}
free(host);
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
return 0;
} else if (strstr(vpninfo->redirect_url, "://")) {
vpn_progress(vpninfo, PRG_ERR,
_("Cannot follow redirection to non-https URL '%s'\n"),
vpninfo->redirect_url);
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
return -EINVAL;
} else if (vpninfo->redirect_url[0] == '/') {
/* Absolute redirect within same host */
free(vpninfo->urlpath);
vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
return 0;
} else {
char *lastslash = NULL;
if (vpninfo->urlpath)
lastslash = strrchr(vpninfo->urlpath, '/');
if (!lastslash) {
free(vpninfo->urlpath);
vpninfo->urlpath = vpninfo->redirect_url;
vpninfo->redirect_url = NULL;
} else {
char *oldurl = vpninfo->urlpath;
*lastslash = 0;
vpninfo->urlpath = NULL;
if (asprintf(&vpninfo->urlpath, "%s/%s",
oldurl, vpninfo->redirect_url) == -1) {
int err = -errno;
vpn_progress(vpninfo, PRG_ERR,
_("Allocating new path for relative redirect failed: %s\n"),
strerror(-err));
return err;
}
free(oldurl);
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
}
return 0;
}
}
758
void dump_buf(struct openconnect_info *vpninfo, char prefix, char *buf)
759
760
761
762
763
764
765
766
767
768
769
770
771
772
{
while (*buf) {
char *eol = buf;
char eol_char = 0;
while (*eol) {
if (*eol == '\r' || *eol == '\n') {
eol_char = *eol;
*eol = 0;
break;
}
eol++;
}
773
vpn_progress(vpninfo, PRG_DEBUG, "%c %s\n", prefix, buf);
774
775
776
777
778
779
780
781
782
783
if (!eol_char)
break;
*eol = eol_char;
buf = eol + 1;
if (eol_char == '\r' && *buf == '\n')
buf++;
}
}
784
785
786
787
788
789
790
791
792
793
/* Inputs:
* method: GET or POST
* vpninfo->hostname: Host DNS name
* vpninfo->port: TCP port, typically 443
* vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
* request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
* request_body: POST content
* form_buf: Callee-allocated buffer for server content
*
* Return value:
794
* < 0, on error
795
* >=0, on success, indicating the length of the data in *form_buf
796
*/
797
798
799
int do_https_request(struct openconnect_info *vpninfo, const char *method,
const char *request_body_type, struct oc_text_buf *request_body,
char **form_buf, int fetch_redirect)
800
{
801
struct oc_text_buf *buf = buf_alloc();
802
int result;
803
int rq_retry;
804
int rlen, pad;
805
int auth = 0;
806
int max_redirects = 10;
807
808
if (request_body_type && buf_error(request_body))
809
810
return buf_error(request_body);
811
redirected:
812
813
814
815
816
if (max_redirects-- <= 0) {
result = -EIO;
goto out;
}
817
818
vpninfo->redirect_type = REDIR_TYPE_NONE;
819
820
821
if (*form_buf) {
free(*form_buf);
*form_buf = NULL;
822
}
823
824
/*
825
826
827
828
829
830
* A long time ago, I *wanted* to use an HTTP client library like cURL
* for this. But we need a *lot* of control over the underlying SSL
* transport, and we also have to do horrid tricks like the Juniper NC
* 'GET' request that actaully behaves like a 'CONNECT'.
*
* So the world gained Yet Another HTTP Implementation. Sorry.
831
832
*
*/
833
buf_truncate(buf);
834
buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
835
836
837
838
839
840
841
842
if (auth) {
result = gen_authorization_hdr(vpninfo, 0, buf);
if (result)
goto out;
/* Forget existing challenges */
clear_auth_states(vpninfo, vpninfo->http_auth, 0);
}
843
844
if (vpninfo->proto->add_http_headers)
vpninfo->proto->add_http_headers(vpninfo, buf);
845
846
if (request_body_type) {
847
rlen = request_body->pos;
848
849
850
851
852
853
/* force body length to be a multiple of 64, to avoid leaking
* password length. */
pad = 64*(1+rlen/64) - rlen;
buf_append(buf, "X-Pad: %0*d\r\n", pad, 0);
854
buf_append(buf, "Content-Type: %s\r\n", request_body_type);
855
buf_append(buf, "Content-Length: %d\r\n", (int)rlen);
856
}
857
858
buf_append(buf, "\r\n");
859
if (request_body_type)
860
buf_append_bytes(buf, request_body->data, request_body->pos);
861
862
if (vpninfo->port == 443)
863
vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
864
865
method, vpninfo->hostname,
vpninfo->urlpath ?: "");
866
else
867
vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
868
869
method, vpninfo->hostname, vpninfo->port,
vpninfo->urlpath ?: "");
870
871
872
873
if (buf_error(buf))
return buf_free(buf);
874
875
vpninfo->retry_on_auth_fail = 0;
876
877
878
879
880
881
882
883
retry:
if (openconnect_https_connected(vpninfo)) {
/* The session is already connected. If we get a failure on
* *sending* the request, try it again immediately with a new
* connection. */
rq_retry = 1;
} else {
rq_retry = 0;
884
if ((result = openconnect_open_https(vpninfo))) {
885
886
887
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open HTTPS connection to %s\n"),
vpninfo->hostname);
888
889
890
891
/* We really don't want to return -EINVAL if we have
failed to even connect to the server, because if
we do that openconnect_obtain_cookie() might try
again without XMLPOST... with the same result. */
892
893
result = -EIO;
goto out;
894
895
896
}
}
897
898
899
if (vpninfo->dump_http_traffic)
dump_buf(vpninfo, '>', buf->data);
900
result = vpninfo->ssl_write(vpninfo, buf->data, buf->pos);
901
902
903
904
if (rq_retry && result < 0) {
openconnect_close_https(vpninfo, 0);
goto retry;
}
905
if (result < 0)
906
goto out;
907
908
result = process_http_response(vpninfo, 0, http_auth_hdrs, buf);
909
if (result < 0) {
910
/* We'll already have complained about whatever offended us */
911
goto out;
912
}
913
914
if (vpninfo->dump_http_traffic && buf->pos)
dump_buf(vpninfo, '<', buf->data);
915
916
if (result == 401 && vpninfo->try_http_auth) {
917
918
919
auth = 1;
goto redirected;
}
920
if (result != 200 && vpninfo->redirect_url) {
921
result = handle_redirect(vpninfo);
922
923
if (result == 0) {
if (!fetch_redirect)
924
goto out;
925
926
927
928
929
if (fetch_redirect == 2) {
/* Juniper requires we GET after a redirected POST */
method = "GET";
request_body_type = NULL;
}
930
931
if (vpninfo->redirect_type == REDIR_TYPE_NEWHOST)
clear_auth_states(vpninfo, vpninfo->http_auth, 1);
932
goto redirected;
933
}
934
goto out;
935
}
936
if (!buf->pos || result != 200) {
937
vpn_progress(vpninfo, PRG_ERR,
938
939
_("Unexpected %d result from server\n"),
result);
940
941
942
943
result = -EINVAL;
goto out;
}
944
945
946
*form_buf = buf->data;
buf->data = NULL;
result = buf->pos;
947
948
out:
949
buf_free(buf);
950
951
/* On success, clear out all authentication state for the next request */
clear_auth_states(vpninfo, vpninfo->http_auth, 1);
952
953
954
return result;
}
955
char *openconnect_create_useragent(const char *base)
956
{
957
958
char *uagent;
959
if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
960
961
return NULL;
962
963
return uagent;
}
964
965
static int proxy_gets(struct openconnect_info *vpninfo, char *buf, size_t len)
966
967
968
969
970
971
972
{
int i = 0;
int ret;
if (len < 2)
return -EINVAL;
973
while ((ret = proxy_read(vpninfo, (void *)(buf + i), 1)) == 1) {
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
if (buf[i] == '\n') {
buf[i] = 0;
if (i && buf[i-1] == '\r') {
buf[i-1] = 0;
i--;
}
return i;
}
i++;
if (i >= len - 1) {
buf[i] = 0;
return i;
}
}
buf[i] = 0;
return i ?: ret;
}
993
static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len)
994
995
{
size_t count;
996
997
998
999
int fd = vpninfo->proxy_fd;
if (fd == -1)
return -EINVAL;
1000