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