/
http.c
1571 lines (1370 loc) · 38.5 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
32
#include <libxml/uri.h>
33
#include "openconnect-internal.h"
34
35
36
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);
37
38
39
#define BUF_CHUNK_SIZE 4096
40
struct oc_text_buf *buf_alloc(void)
41
42
43
44
{
return calloc(1, sizeof(struct oc_text_buf));
}
45
void buf_append_urlencoded(struct oc_text_buf *buf, const char *str)
46
47
{
while (str && *str) {
48
unsigned char c = *str;
49
if (c < 0x80 && (isalnum((int)(c)) || c=='-' || c=='_' || c=='.' || c=='~'))
50
51
buf_append_bytes(buf, str, 1);
else
52
53
buf_append(buf, "%%%02x", c);
54
55
56
57
str++;
}
}
58
59
60
61
62
63
64
65
66
67
68
69
70
void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str)
{
while (str && *str) {
unsigned char c = *str;
if (c=='<' || c=='>' || c=='&' || c=='"' || c=='\'')
buf_append(buf, "&#x%02x;", c);
else
buf_append_bytes(buf, str, 1);
str++;
}
}
71
72
73
74
75
76
77
78
79
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]);
}
80
81
82
83
84
85
void buf_truncate(struct oc_text_buf *buf)
{
if (!buf)
return;
if (buf->data)
86
87
88
memset(buf->data, 0, buf->pos);
buf->pos = 0;
89
90
}
91
int buf_ensure_space(struct oc_text_buf *buf, int len)
92
{
93
unsigned int new_buf_len;
94
95
96
97
if (!buf)
return -ENOMEM;
98
99
100
101
102
new_buf_len = (buf->pos + len + BUF_CHUNK_SIZE - 1) & ~(BUF_CHUNK_SIZE - 1);
if (new_buf_len <= buf->buf_len)
return 0;
103
if (new_buf_len > INT_MAX) {
104
105
106
107
108
109
110
111
112
113
114
115
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;
}
116
117
void __attribute__ ((format (printf, 2, 3)))
buf_append(struct oc_text_buf *buf, const char *fmt, ...)
118
119
120
121
122
123
{
va_list ap;
if (!buf || buf->error)
return;
124
125
if (buf_ensure_space(buf, 1))
return;
126
127
128
129
130
131
132
133
134
135
136
137
138
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;
139
140
} else if (buf_ensure_space(buf, ret))
break;
141
142
143
}
}
144
145
146
147
148
void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len)
{
if (!buf || buf->error)
return;
149
if (buf_ensure_space(buf, len + 1))
150
return;
151
152
153
memcpy(buf->data + buf->pos, bytes, len);
buf->pos += len;
154
buf->data[buf->pos] = 0;
155
156
}
157
158
159
160
161
162
163
164
165
166
167
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) {
168
169
c = ((load_le16(utf16) & 0x3ff) << 10)|
(load_le16(utf16 + 2) & 0x3ff);
170
171
172
c += 0x10000;
utf16 += 4;
} else {
173
c = load_le16(utf16);
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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);
}
201
int get_utf8char(const char **p)
202
{
203
const char *utf8 = *p;
204
unsigned char c;
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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;
}
227
228
while (nr_extra--) {
229
c = *(utf8++);
230
if ((c & 0xc0) != 0x80)
231
return -EILSEQ;
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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) {
252
if (buf)
253
254
buf->error = utfchar;
return utfchar;
255
256
257
}
if (!buf)
continue;
258
259
260
261
if (utfchar >= 0x10000) {
utfchar -= 0x10000;
if (buf_ensure_space(buf, 4))
262
return buf_error(buf);
263
264
265
store_le16(buf->data + buf->pos, (utfchar >> 10) | 0xd800);
store_le16(buf->data + buf->pos + 2, (utfchar & 0x3ff) | 0xdc00);
buf->pos += 4;
266
267
268
len += 4;
} else {
if (buf_ensure_space(buf, 2))
269
return buf_error(buf);
270
271
store_le16(buf->data + buf->pos, utfchar);
buf->pos += 2;
272
273
274
len += 2;
}
}
275
276
277
278
279
/* We were only being used for validation */
if (!buf)
return 0;
280
281
/* Ensure UTF16 is NUL-terminated */
if (buf_ensure_space(buf, 2))
282
return buf_error(buf);
283
284
buf->data[buf->pos] = buf->data[buf->pos + 1] = 0;
285
286
287
return len;
}
288
int buf_error(struct oc_text_buf *buf)
289
290
291
292
{
return buf ? buf->error : -ENOMEM;
}
293
int buf_free(struct oc_text_buf *buf)
294
295
296
297
{
int error = buf_error(buf);
if (buf) {
298
buf_truncate(buf);
299
300
301
302
303
304
305
306
if (buf->data)
free(buf->data);
free(buf);
}
return error;
}
307
/*
308
* We didn't really want to have to do this for ourselves -- one might have
309
310
311
312
313
314
* 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.
*/
315
316
int http_add_cookie(struct openconnect_info *vpninfo, const char *option,
const char *value, int replace)
317
{
318
struct oc_vpn_option *new, **this;
319
320
321
322
if (*value) {
new = malloc(sizeof(*new));
if (!new) {
323
324
vpn_progress(vpninfo, PRG_ERR,
_("No memory for allocating cookies\n"));
325
326
327
328
329
return -ENOMEM;
}
new->next = NULL;
new->option = strdup(option);
new->value = strdup(value);
330
331
332
333
334
335
if (!new->option || !new->value) {
free(new->option);
free(new->value);
free(new);
return -ENOMEM;
}
336
337
338
} else {
/* Kill cookie; don't replace it */
new = NULL;
339
340
341
/* This would be meaningless */
if (!replace)
return -EINVAL;
342
343
344
}
for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
if (!strcmp(option, (*this)->option)) {
345
346
347
348
349
350
if (!replace) {
free(new->value);
free(new->option);
free(new);
return 0;
}
351
352
353
354
355
/* Replace existing cookie */
if (new)
new->next = (*this)->next;
else
new = (*this)->next;
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
free((*this)->option);
free((*this)->value);
free(*this);
*this = new;
break;
}
}
if (new && !*this) {
*this = new;
new->next = NULL;
}
return 0;
}
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
/* Read one HTTP header line into hdrbuf, potentially allowing for
* continuation lines. Will never leave a character in 'nextchar' when
* an empty line (signifying end of headers) is received. Will only
* return success when hdrbuf is valid. */
static int read_http_header(struct openconnect_info *vpninfo, char *nextchar,
struct oc_text_buf *hdrbuf, int allow_cont)
{
int eol = 0;
int ret;
char c;
buf_truncate(hdrbuf);
c = *nextchar;
if (c) {
*nextchar = 0;
goto skip_first;
}
while (1) {
ret = vpninfo->ssl_read(vpninfo, &c, 1);
if (ret < 0)
return ret;
if (ret != 1)
return -EINVAL;
/* If we were looking for a continuation line and didn't get it,
* stash the character we *did* get into *nextchar for next time. */
if (eol && c != ' ' && c != '\t') {
*nextchar = c;
return buf_error(hdrbuf);
}
eol = 0;
skip_first:
if (c == '\n') {
if (!buf_error(hdrbuf) && hdrbuf->pos &&
hdrbuf->data[hdrbuf->pos - 1] == '\r') {
hdrbuf->pos--;
hdrbuf->data[hdrbuf->pos] = 0;
}
/* For a non-empty header line, see if there's a continuation */
if (allow_cont && hdrbuf->pos) {
eol = 1;
continue;
}
return buf_error(hdrbuf);
}
buf_append_bytes(hdrbuf, &c, 1);
}
return buf_error(hdrbuf);
}
427
428
429
#define BODY_HTTP10 -1
#define BODY_CHUNKED -2
430
431
432
int process_http_response(struct openconnect_info *vpninfo, int connect,
int (*header_cb)(struct openconnect_info *, char *, char *),
struct oc_text_buf *body)
433
{
434
435
struct oc_text_buf *hdrbuf = buf_alloc();
char nextchar = 0;
436
437
int bodylen = BODY_HTTP10;
int closeconn = 0;
438
int result;
439
int ret = -EINVAL;
440
441
int i;
442
443
buf_truncate(body);
444
445
446
447
/* Ensure it has *something* in it, so that we can dereference hdrbuf->data
* later without checking (for anything except buf_error(hdrbuf), which is
* what read_http_header() uses for its return code anyway). */
buf_append_bytes(hdrbuf, "\0", 1);
448
cont:
449
450
451
ret = read_http_header(vpninfo, &nextchar, hdrbuf, 0);
if (ret) {
vpn_progress(vpninfo, PRG_ERR, _("Error reading HTTP response: %s\n"),
452
strerror(-ret));
453
goto err;
454
455
}
456
if (!strncmp(hdrbuf->data, "HTTP/1.0 ", 9))
457
closeconn = 1;
458
459
460
if ((!closeconn && strncmp(hdrbuf->data, "HTTP/1.1 ", 9)) ||
!(result = atoi(hdrbuf->data + 9))) {
461
vpn_progress(vpninfo, PRG_ERR,
462
463
464
_("Failed to parse HTTP response '%s'\n"), hdrbuf->data);
ret = -EINVAL;
goto err;
465
466
}
467
vpn_progress(vpninfo, (result == 200 || result == 407) ? PRG_DEBUG : PRG_INFO,
468
_("Got HTTP response: %s\n"), hdrbuf->data);
469
470
/* Eat headers... */
471
while (1) {
472
char *colon;
473
char *hdrline;
474
475
476
477
ret = read_http_header(vpninfo, &nextchar, hdrbuf, 1);
if (ret) {
vpn_progress(vpninfo, PRG_ERR, _("Error reading HTTP response: %s\n"),
478
strerror(-ret));
479
goto err;
480
}
481
482
483
484
485
486
487
488
489
490
/* Default error case */
ret = -EINVAL;
/* Empty line ends headers */
if (!hdrbuf->pos)
break;
hdrline = hdrbuf->data;
colon = strchr(hdrline, ':');
491
if (!colon) {
492
vpn_progress(vpninfo, PRG_ERR,
493
_("Ignoring unknown HTTP response line '%s'\n"), hdrline);
494
495
496
497
498
499
continue;
}
*(colon++) = 0;
if (*colon == ' ')
colon++;
500
501
/* Handle Set-Cookie first so that we can avoid printing the
webvpn cookie in the verbose debug output */
502
if (!strcasecmp(hdrline, "Set-Cookie")) {
503
char *semicolon = strchr(colon, ';');
504
505
const char *print_equals;
char *equals = strchr(colon, '=');
506
507
508
509
510
if (semicolon)
*semicolon = 0;
if (!equals) {
511
vpn_progress(vpninfo, PRG_ERR,
512
513
514
_("Invalid cookie offered: %s\n"), hdrline);
ret = -EINVAL;
goto err;
515
516
517
518
}
*(equals++) = 0;
print_equals = equals;
519
520
521
/* 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)
522
print_equals = _("<elided>");
523
vpn_progress(vpninfo, PRG_DEBUG, "%s: %s=%s%s%s\n",
524
hdrline, colon, print_equals, semicolon ? ";" : "",
525
semicolon ? (semicolon+1) : "");
526
527
528
529
530
531
532
533
/* 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"));
534
ret = http_add_cookie(vpninfo, colon, equals, 1);
535
536
if (ret)
goto err;
537
} else {
538
vpn_progress(vpninfo, PRG_DEBUG, "%s: %s\n", hdrline, colon);
539
540
}
541
if (!strcasecmp(hdrline, "Connection")) {
542
543
if (!strcasecmp(colon, "Close"))
closeconn = 1;
544
545
546
547
548
549
#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. */
550
551
else if (!strcasecmp(colon, "Keep-Alive"))
closeconn = 0;
552
#endif
553
}
554
if (!strcasecmp(hdrline, "Location")) {
555
vpninfo->redirect_url = strdup(colon);
556
if (!vpninfo->redirect_url) {
557
558
ret = -ENOMEM;
goto err;
559
}
560
}
561
if (!strcasecmp(hdrline, "Content-Length")) {
562
bodylen = atoi(colon);
563
if (bodylen < 0) {
564
565
566
vpn_progress(vpninfo, PRG_ERR,
_("Response body has negative size (%d)\n"),
bodylen);
567
568
ret = -EINVAL;
goto err;
569
570
}
}
571
if (!strcasecmp(hdrline, "Transfer-Encoding")) {
572
if (!strcasecmp(colon, "chunked"))
573
bodylen = BODY_CHUNKED;
574
else {
575
576
577
vpn_progress(vpninfo, PRG_ERR,
_("Unknown Transfer-Encoding: %s\n"),
colon);
578
579
ret = -EINVAL;
goto err;
580
581
}
}
582
if (header_cb)
583
header_cb(vpninfo, hdrline, colon);
584
585
586
}
/* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
587
if (result == 100)
588
589
goto cont;
590
/* On successful CONNECT or upgrade, there is no body. Return success */
591
592
if (connect && (result == 200 || result == 101)) {
buf_free(hdrbuf);
593
return result;
594
}
595
596
/* Now the body, if there is one */
597
vpn_progress(vpninfo, PRG_DEBUG, _("HTTP body %s (%d)\n"),
598
599
bodylen == BODY_HTTP10 ? "http 1.0" :
bodylen == BODY_CHUNKED ? "chunked" : "length: ",
600
bodylen);
601
602
603
/* If we were given Content-Length, it's nice and easy... */
if (bodylen > 0) {
604
if (buf_ensure_space(body, bodylen + 1)) {
605
606
ret = buf_error(body);
goto err;
607
}
608
609
610
while (body->pos < bodylen) {
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, bodylen - body->pos);
611
if (i < 0) {
612
613
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
614
615
ret = i;
goto err;
616
}
617
body->pos += i;
618
}
619
} else if (bodylen == BODY_CHUNKED) {
620
char clen_buf[16];
621
/* ... else, chunked */
622
while ((i = vpninfo->ssl_gets(vpninfo, clen_buf, sizeof(clen_buf)))) {
623
624
int lastchunk = 0;
long chunklen;
625
626
if (i < 0) {
627
628
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching chunk header\n"));
629
630
ret = i;
goto err;
631
}
632
chunklen = strtol(clen_buf, NULL, 16);
633
634
635
636
if (!chunklen) {
lastchunk = 1;
goto skip;
}
637
638
639
if (chunklen < 0) {
vpn_progress(vpninfo, PRG_ERR,
_("HTTP chunk length is negative (%ld)\n"), chunklen);
640
641
ret = -EINVAL;
goto err;
642
643
644
645
}
if (chunklen >= INT_MAX) {
vpn_progress(vpninfo, PRG_ERR,
_("HTTP chunk length is too large (%ld)\n"), chunklen);
646
647
ret = -EINVAL;
goto err;
648
}
649
if (buf_ensure_space(body, chunklen + 1)) {
650
651
ret = buf_error(body);
goto err;
652
}
653
while (chunklen) {
654
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, chunklen);
655
if (i < 0) {
656
657
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
658
659
ret = i;
goto err;
660
661
}
chunklen -= i;
662
body->pos += i;
663
664
}
skip:
665
if ((i = vpninfo->ssl_gets(vpninfo, clen_buf, sizeof(clen_buf)))) {
666
if (i < 0) {
667
668
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTP response body\n"));
669
ret = i;
670
} else {
671
672
vpn_progress(vpninfo, PRG_ERR,
_("Error in chunked decoding. Expected '', got: '%s'"),
673
674
clen_buf);
ret = -EINVAL;
675
}
676
goto err;
677
}
678
679
680
681
682
683
if (lastchunk)
break;
}
} else if (bodylen == BODY_HTTP10) {
if (!closeconn) {
684
685
vpn_progress(vpninfo, PRG_ERR,
_("Cannot receive HTTP 1.0 body without closing connection\n"));
686
openconnect_close_https(vpninfo, 0);
687
688
689
return -EINVAL;
}
690
/* HTTP 1.0 response. Just eat all we can in 4KiB chunks */
691
while (1) {
692
if (buf_ensure_space(body, 4096 + 1)) {
693
694
ret = buf_error(body);
goto err;
695
}
696
697
i = vpninfo->ssl_read(vpninfo, body->data + body->pos, 4096);
if (i < 0) {
698
/* Error */
699
700
ret = i;
goto err;
701
} else if (!i)
702
break;
703
704
705
/* Got more data */
body->pos += i;
706
}
707
}
708
709
body->data[body->pos] = 0;
710
711
712
713
714
715
716
ret = result;
if (closeconn || vpninfo->no_http_keepalive) {
err:
openconnect_close_https(vpninfo, 0);
}
buf_free(hdrbuf);
717
return ret;
718
719
}
720
int internal_parse_url(const char *url, char **res_proto, char **res_host,
721
int *res_port, char **res_path, int default_port)
722
{
723
724
725
const char *orig_host, *orig_path;
char *host, *port_str;
int port, proto_len = 0;
726
727
728
729
730
orig_host = strstr(url, "://");
if (orig_host) {
proto_len = orig_host - url;
orig_host += 3;
731
732
if (strprefix_match(url, proto_len, "https"))
733
port = 443;
734
else if (strprefix_match(url, proto_len, "http"))
735
port = 80;
736
737
738
else if (strprefix_match(url, proto_len, "socks") ||
strprefix_match(url, proto_len, "socks4") ||
strprefix_match(url, proto_len, "socks5"))
739
740
741
742
743
744
port = 1080;
else
return -EPROTONOSUPPORT;
} else {
if (default_port) {
port = default_port;
745
orig_host = url;
746
747
748
} else
return -EINVAL;
}
749
750
751
752
753
754
755
756
757
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;
758
759
760
761
762
763
764
765
766
767
768
769
770
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)
771
*res_proto = proto_len ? strndup(url, proto_len) : NULL;
772
if (res_host)
773
774
775
*res_host = host;
else
free(host);
776
777
778
if (res_port)
*res_port = port;
if (res_path)
779
780
*res_path = (orig_path && *orig_path) ? strdup(orig_path) : NULL;
781
782
783
return 0;
}
784
void openconnect_clear_cookies(struct openconnect_info *vpninfo)
785
{
786
struct oc_vpn_option *opt, *next;
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
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,...})
*/
802
int handle_redirect(struct openconnect_info *vpninfo)
803
{
804
805
vpninfo->redirect_type = REDIR_TYPE_LOCAL;
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
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) {
826
openconnect_set_hostname(vpninfo, host);
827
828
829
830
vpninfo->port = port;
/* Kill the existing connection, and a new one will happen */
openconnect_close_https(vpninfo, 0);
831
openconnect_clear_cookies(vpninfo);
832
vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
833
834
}
free(host);
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
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;
}
}
882
void dump_buf(struct openconnect_info *vpninfo, char prefix, char *buf)
883
884
885
886
887
888
889
890
891
892
893
894
895
896
{
while (*buf) {
char *eol = buf;
char eol_char = 0;
while (*eol) {
if (*eol == '\r' || *eol == '\n') {
eol_char = *eol;
*eol = 0;
break;
}
eol++;
}
897
vpn_progress(vpninfo, PRG_DEBUG, "%c %s\n", prefix, buf);
898
899
900
901
902
903
904
905
906
907
if (!eol_char)
break;
*eol = eol_char;
buf = eol + 1;
if (eol_char == '\r' && *buf == '\n')
buf++;
}
}
908
909
void dump_buf_hex(struct openconnect_info *vpninfo, int loglevel, char prefix, unsigned char *buf, int len)
{
910
struct oc_text_buf *line = buf_alloc();
911
int i, j;
912
913
for (i = 0; i < len; i+=16) {
914
915
buf_truncate(line);
buf_append(line, "%04x:", i);
916
for (j = i; j < i+16; j++) {
917
918
919
if (!(j & 7))
buf_append(line, " ");
920
if (j < len)
921
buf_append(line, " %02x", buf[j]);
922
else
923
buf_append(line, " ");
924
}
925
buf_append(line, " |");
926
for (j = i; j < i+16 && j < len; j++)
927
928
929
930
931
buf_append(line, "%c", isprint(buf[j])? buf[j] : '.');
buf_append(line, "|");
if (buf_error(line))
break;
vpn_progress(vpninfo, loglevel, "%c %s\n", prefix, line->data);
932
}
933
buf_free(line);
934
935
}
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
static int https_socket_closed(struct openconnect_info *vpninfo)
{
char c;
if (!openconnect_https_connected(vpninfo))
return 1;
if (recv(vpninfo->ssl_fd, &c, 1, MSG_PEEK) == 0) {
vpn_progress(vpninfo, PRG_DEBUG,
_("HTTPS socket closed by peer; reopening\n"));
openconnect_close_https(vpninfo, 0);
return 1;
}
return 0;
}
954
955
956
957
958
959
960
961
962
963
/* 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:
964
* < 0, on error
965
* >=0, on success, indicating the length of the data in *form_buf
966
*/
967
968
969
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)
970
{
971
struct oc_text_buf *buf;
972
int result;
973
int rq_retry;
974
int rlen, pad;
975
int i, auth = 0;
976
int max_redirects = 10;
977
978
if (request_body_type && buf_error(request_body))
979
980
return buf_error(request_body);
981
982
buf = buf_alloc();
983
redirected:
984
985
986
987
988
if (max_redirects-- <= 0) {
result = -EIO;
goto out;
}
989
990
vpninfo->redirect_type = REDIR_TYPE_NONE;
991
992
993
if (*form_buf) {
free(*form_buf);
*form_buf = NULL;
994
}
995
996
/*
997
998
999
1000
* 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'.