/
http.c
1468 lines (1279 loc) · 36.2 KB
1
/*
2
* OpenConnect (SSL + DTLS) VPN client
3
*
4
* Copyright © 2008-2012 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
17
18
19
20
21
22
23
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
*
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
24
25
26
27
28
29
*/
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
30
#include <string.h>
31
#include <ctype.h>
32
#include <pwd.h>
33
#include <sys/stat.h>
34
#include <sys/types.h>
35
36
37
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
38
#include <stdarg.h>
39
40
#include "openconnect-internal.h"
41
42
43
44
45
static int proxy_write(struct openconnect_info *vpninfo, int fd,
unsigned char *buf, size_t len);
static int proxy_read(struct openconnect_info *vpninfo, int fd,
unsigned char *buf, size_t len);
46
47
#define MAX_BUF_LEN 131072
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#define BUF_CHUNK_SIZE 4096
struct oc_text_buf {
char *data;
int pos;
int buf_len;
int error;
};
static struct oc_text_buf *buf_alloc(void)
{
return calloc(1, sizeof(struct oc_text_buf));
}
static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
{
va_list ap;
if (!buf || buf->error)
return;
if (!buf->data) {
buf->data = malloc(BUF_CHUNK_SIZE);
if (!buf->data) {
buf->error = -ENOMEM;
return;
}
buf->buf_len = BUF_CHUNK_SIZE;
}
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;
} else {
int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
if (new_buf_len > MAX_BUF_LEN) {
/* probably means somebody is messing with us */
buf->error = -E2BIG;
break;
}
buf->data = realloc(buf->data, new_buf_len);
if (!buf->data) {
buf->error = -ENOMEM;
break;
}
buf->buf_len = new_buf_len;
}
}
}
static int buf_error(struct oc_text_buf *buf)
{
return buf ? buf->error : -ENOMEM;
}
static int buf_free(struct oc_text_buf *buf)
{
int error = buf_error(buf);
if (buf) {
if (buf->data)
free(buf->data);
free(buf);
}
return error;
}
127
/*
128
* We didn't really want to have to do this for ourselves -- one might have
129
130
131
132
133
134
* 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.
*/
135
136
static int http_add_cookie(struct openconnect_info *vpninfo,
const char *option, const char *value)
137
138
139
140
141
142
{
struct vpn_option *new, **this;
if (*value) {
new = malloc(sizeof(*new));
if (!new) {
143
144
vpn_progress(vpninfo, PRG_ERR,
_("No memory for allocating cookies\n"));
145
146
147
148
149
return -ENOMEM;
}
new->next = NULL;
new->option = strdup(option);
new->value = strdup(value);
150
151
152
153
154
155
if (!new->option || !new->value) {
free(new->option);
free(new->value);
free(new);
return -ENOMEM;
}
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
} else {
/* Kill cookie; don't replace it */
new = NULL;
}
for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
if (!strcmp(option, (*this)->option)) {
/* Replace existing cookie */
if (new)
new->next = (*this)->next;
else
new = (*this)->next;
free((*this)->option);
free((*this)->value);
free(*this);
*this = new;
break;
}
}
if (new && !*this) {
*this = new;
new->next = NULL;
}
return 0;
}
182
183
184
#define BODY_HTTP10 -1
#define BODY_CHUNKED -2
185
186
static int process_http_response(struct openconnect_info *vpninfo, int *result,
int (*header_cb)(struct openconnect_info *, char *, char *),
187
char **body_ret)
188
{
189
char buf[MAX_BUF_LEN];
190
char *body = NULL;
191
int bodylen = BODY_HTTP10;
192
int done = 0;
193
int closeconn = 0;
194
195
int i;
196
cont:
197
if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
198
199
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTPS response\n"));
200
return -EINVAL;
201
202
}
203
if (!strncmp(buf, "HTTP/1.0 ", 9))
204
closeconn = 1;
205
206
if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
207
208
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse HTTP response '%s'\n"), buf);
209
210
211
return -EINVAL;
}
212
vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
213
_("Got HTTP response: %s\n"), buf);
214
215
/* Eat headers... */
216
while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
217
218
219
char *colon;
if (i < 0) {
220
221
vpn_progress(vpninfo, PRG_ERR,
_("Error processing HTTP response\n"));
222
223
224
225
return -EINVAL;
}
colon = strchr(buf, ':');
if (!colon) {
226
227
vpn_progress(vpninfo, PRG_ERR,
_("Ignoring unknown HTTP response line '%s'\n"), buf);
228
229
230
231
232
233
continue;
}
*(colon++) = 0;
if (*colon == ' ')
colon++;
234
235
236
237
/* 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, ';');
238
239
const char *print_equals;
char *equals = strchr(colon, '=');
240
241
242
243
244
245
int ret;
if (semicolon)
*semicolon = 0;
if (!equals) {
246
247
vpn_progress(vpninfo, PRG_ERR,
_("Invalid cookie offered: %s\n"), buf);
248
249
250
251
252
return -EINVAL;
}
*(equals++) = 0;
print_equals = equals;
253
254
255
/* 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)
256
print_equals = _("<elided>");
257
vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
258
259
buf, colon, print_equals, semicolon?";":"",
semicolon?(semicolon+1):"");
260
261
262
263
264
265
266
267
/* 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"));
268
269
270
271
ret = http_add_cookie(vpninfo, colon, equals);
if (ret)
return ret;
} else {
272
vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
273
274
}
275
276
277
if (!strcasecmp(buf, "Connection")) {
if (!strcasecmp(colon, "Close"))
closeconn = 1;
278
279
280
281
282
283
#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. */
284
285
else if (!strcasecmp(colon, "Keep-Alive"))
closeconn = 0;
286
#endif
287
}
288
if (!strcasecmp(buf, "Location")) {
289
290
291
292
vpninfo->redirect_url = strdup(colon);
if (!vpninfo->redirect_url)
return -ENOMEM;
}
293
if (!strcasecmp(buf, "Content-Length")) {
294
bodylen = atoi(colon);
295
if (bodylen < 0) {
296
297
298
vpn_progress(vpninfo, PRG_ERR,
_("Response body has negative size (%d)\n"),
bodylen);
299
300
301
return -EINVAL;
}
}
302
303
if (!strcasecmp(buf, "Transfer-Encoding")) {
if (!strcasecmp(colon, "chunked"))
304
bodylen = BODY_CHUNKED;
305
else {
306
307
308
vpn_progress(vpninfo, PRG_ERR,
_("Unknown Transfer-Encoding: %s\n"),
colon);
309
310
311
312
313
314
315
316
317
318
319
return -EINVAL;
}
}
if (header_cb && !strncmp(buf, "X-", 2))
header_cb(vpninfo, buf, colon);
}
/* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
if (*result == 100)
goto cont;
320
/* Now the body, if there is one */
321
322
323
324
vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
bodylen==BODY_HTTP10?"http 1.0" :
bodylen==BODY_CHUNKED?"chunked" : "length: ",
bodylen);
325
326
327
/* If we were given Content-Length, it's nice and easy... */
if (bodylen > 0) {
328
329
330
body = malloc(bodylen + 1);
if (!body)
return -ENOMEM;
331
while (done < bodylen) {
332
i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
333
if (i < 0) {
334
335
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
336
free(body);
337
338
339
340
return -EINVAL;
}
done += i;
}
341
342
} else if (bodylen == BODY_CHUNKED) {
/* ... else, chunked */
343
while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
344
int chunklen, lastchunk = 0;
345
346
if (i < 0) {
347
348
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching chunk header\n"));
349
return i;
350
351
352
353
354
355
}
chunklen = strtol(buf, NULL, 16);
if (!chunklen) {
lastchunk = 1;
goto skip;
}
356
357
358
body = realloc(body, done + chunklen + 1);
if (!body)
return -ENOMEM;
359
while (chunklen) {
360
i = openconnect_SSL_read(vpninfo, body + done, chunklen);
361
if (i < 0) {
362
363
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
364
free(body);
365
366
367
368
369
370
return -EINVAL;
}
chunklen -= i;
done += i;
}
skip:
371
if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
372
if (i < 0) {
373
374
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTP response body\n"));
375
} else {
376
377
378
vpn_progress(vpninfo, PRG_ERR,
_("Error in chunked decoding. Expected '', got: '%s'"),
buf);
379
}
380
free(body);
381
return -EINVAL;
382
}
383
384
385
386
387
388
if (lastchunk)
break;
}
} else if (bodylen == BODY_HTTP10) {
if (!closeconn) {
389
390
vpn_progress(vpninfo, PRG_ERR,
_("Cannot receive HTTP 1.0 body without closing connection\n"));
391
openconnect_close_https(vpninfo, 0);
392
393
394
return -EINVAL;
}
395
/* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
396
while (1) {
397
body = realloc(body, done + 16384);
398
399
if (!body)
return -ENOMEM;
400
401
402
403
404
405
406
407
408
409
i = openconnect_SSL_read(vpninfo, body + done, 16384);
if (i > 0) {
/* Got more data */
done += i;
} else if (i < 0) {
/* Error */
free(body);
return i;
} else {
/* Connection closed. Reduce allocation to just what we need */
410
body = realloc(body, done + 1);
411
412
if (!body)
return -ENOMEM;
413
break;
414
}
415
}
416
}
417
418
if (closeconn || vpninfo->no_http_keepalive)
419
openconnect_close_https(vpninfo, 0);
420
421
422
423
if (body)
body[done] = 0;
*body_ret = body;
424
425
426
return done;
}
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
{
struct vpn_option *opt;
buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
buf_append(buf, "Accept: */*\r\n");
buf_append(buf, "Accept-Encoding: identity\r\n");
if (vpninfo->cookies) {
buf_append(buf, "Cookie: ");
for (opt = vpninfo->cookies; opt; opt = opt->next)
buf_append(buf, "%s=%s%s", opt->option,
opt->value, opt->next ? "; " : "\r\n");
}
buf_append(buf, "X-Transcend-Version: 1\r\n");
443
444
buf_append(buf, "X-Aggregate-Auth: 1\r\n");
buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
445
446
}
447
static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
448
char *server_sha1)
449
{
450
struct oc_text_buf *buf;
451
char *config_buf = NULL;
452
int result, buflen;
453
454
unsigned char local_sha1_bin[SHA1_SIZE];
char local_sha1_ascii[(SHA1_SIZE * 2)+1];
455
456
int i;
457
458
459
460
461
462
463
if (openconnect_open_https(vpninfo)) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open HTTPS connection to %s\n"),
vpninfo->hostname);
return -EINVAL;
}
464
465
buf = buf_alloc();
buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
466
467
add_common_headers(vpninfo, buf);
buf_append(buf, "\r\n");
468
469
470
if (buf_error(buf))
return buf_free(buf);
471
472
if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
473
474
vpn_progress(vpninfo, PRG_ERR,
_("Failed to send GET request for new config\n"));
475
buf_free(buf);
476
477
return -EIO;
}
478
buf_free(buf);
479
480
buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
481
482
483
484
485
if (buflen < 0) {
/* We'll already have complained about whatever offended us */
return -EINVAL;
}
486
487
if (result != 200) {
free(config_buf);
488
return -EINVAL;
489
}
490
491
openconnect_sha1(local_sha1_bin, config_buf, buflen);
492
493
for (i = 0; i < SHA1_SIZE; i++)
494
495
496
sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
if (strcasecmp(server_sha1, local_sha1_ascii)) {
497
498
vpn_progress(vpninfo, PRG_ERR,
_("Downloaded config file did not match intended SHA1\n"));
499
free(config_buf);
500
501
502
return -EINVAL;
}
503
result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
504
505
free(config_buf);
return result;
506
}
507
508
static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
509
510
{
char fname[16];
511
int fd, ret;
512
513
if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
514
vpn_progress(vpninfo, PRG_ERR,
515
516
_("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
"This facility is disabled by default for security reasons, so you may wish to enable it."));
517
return -EPERM;
518
519
}
520
#ifndef __linux__
521
vpn_progress(vpninfo, PRG_INFO,
522
_("Trying to run Linux CSD trojan script."));
523
524
#endif
525
526
527
528
sprintf(fname, "/tmp/csdXXXXXX");
fd = mkstemp(fname);
if (fd < 0) {
int err = -errno;
529
530
531
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open temporary CSD script file: %s\n"),
strerror(errno));
532
533
return err;
}
534
535
ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
536
if (ret) {
537
538
vpn_progress(vpninfo, PRG_ERR,
_("Failed to write temporary CSD script file: %s\n"),
539
strerror(-ret));
540
541
return ret;
}
542
fchmod(fd, 0755);
543
close(fd);
544
545
if (!fork()) {
546
547
char scertbuf[MD5_SIZE * 2 + 1];
char ccertbuf[MD5_SIZE * 2 + 1];
548
549
550
char *csd_argv[32];
int i = 0;
551
if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
552
553
struct passwd *pw;
554
if (setuid(vpninfo->uid_csd)) {
555
556
fprintf(stderr, _("Failed to set uid %ld\n"),
(long)vpninfo->uid_csd);
557
558
exit(1);
}
559
if (!(pw = getpwuid(vpninfo->uid_csd))) {
560
561
fprintf(stderr, _("Invalid user uid=%ld\n"),
(long)vpninfo->uid_csd);
562
563
564
exit(1);
}
setenv("HOME", pw->pw_dir, 1);
565
if (chdir(pw->pw_dir)) {
566
fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
567
568
569
pw->pw_dir, strerror(errno));
exit(1);
}
570
}
571
if (getuid() == 0 && !vpninfo->csd_wrapper) {
572
573
574
fprintf(stderr, _("Warning: you are running insecure "
"CSD code with root privileges\n"
"\t Use command line option \"--csd-user\"\n"));
575
}
576
if (vpninfo->uid_csd_given == 2) {
577
578
579
580
/* The NM tool really needs not to get spurious output
on stdout, which the CSD trojan spews. */
dup2(2, 1);
}
581
582
if (vpninfo->csd_wrapper)
csd_argv[i++] = vpninfo->csd_wrapper;
583
csd_argv[i++] = fname;
584
csd_argv[i++]= (char *)"-ticket";
585
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
586
goto out;
587
588
589
csd_argv[i++]= (char *)"-stub";
csd_argv[i++]= (char *)"\"0\"";
csd_argv[i++]= (char *)"-group";
590
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
591
goto out;
592
593
594
595
openconnect_local_cert_md5(vpninfo, ccertbuf);
scertbuf[0] = 0;
get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
596
csd_argv[i++]= (char *)"-certhash";
597
if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
598
goto out;
599
600
csd_argv[i++]= (char *)"-url";
601
if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
602
goto out;
603
604
csd_argv[i++]= (char *)"-langselen";
605
606
csd_argv[i++] = NULL;
607
608
609
610
611
if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
goto out;
if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
goto out;
612
execv(csd_argv[0], csd_argv);
613
614
out:
615
616
vpn_progress(vpninfo, PRG_ERR,
_("Failed to exec CSD script %s\n"), csd_argv[0]);
617
exit(1);
618
619
620
621
}
free(vpninfo->csd_stuburl);
vpninfo->csd_stuburl = NULL;
622
free(vpninfo->urlpath);
623
624
vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
(vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
625
free(vpninfo->csd_waiturl);
626
vpninfo->csd_waiturl = NULL;
627
vpninfo->csd_scriptname = strdup(fname);
628
629
630
http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
631
632
633
return 0;
}
634
635
int internal_parse_url(char *url, char **res_proto, char **res_host,
int *res_port, char **res_path, int default_port)
636
637
638
639
640
641
{
char *proto = url;
char *host, *path, *port_str;
int port;
host = strstr(url, "://");
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
if (host) {
*host = 0;
host += 3;
if (!strcasecmp(proto, "https"))
port = 443;
else if (!strcasecmp(proto, "http"))
port = 80;
else if (!strcasecmp(proto, "socks") ||
!strcasecmp(proto, "socks4") ||
!strcasecmp(proto, "socks5"))
port = 1080;
else
return -EPROTONOSUPPORT;
} else {
if (default_port) {
proto = NULL;
port = default_port;
host = url;
} else
return -EINVAL;
}
664
665
path = strchr(host, '/');
666
if (path)
667
668
669
670
671
672
673
674
675
676
677
678
679
680
*(path++) = 0;
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)
681
*res_proto = proto ? strdup(proto) : NULL;
682
683
684
685
686
if (res_host)
*res_host = strdup(host);
if (res_port)
*res_port = port;
if (res_path)
687
688
689
*res_path = (path && *path) ? strdup(path) : NULL;
/* Undo the damage we did to the original string */
690
691
if (port_str)
*(port_str) = ':';
692
693
694
695
if (path)
*(path - 1) = '/';
if (proto)
*(host - 3) = ':';
696
697
698
return 0;
}
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
static void clear_cookies(struct openconnect_info *vpninfo)
{
struct vpn_option *opt, *next;
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,...})
*/
static int handle_redirect(struct openconnect_info *vpninfo)
{
719
720
vpninfo->redirect_type = REDIR_TYPE_LOCAL;
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
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) {
free(vpninfo->hostname);
vpninfo->hostname = host;
vpninfo->port = port;
/* Kill the existing connection, and a new one will happen */
free(vpninfo->peer_addr);
vpninfo->peer_addr = NULL;
openconnect_close_https(vpninfo, 0);
clear_cookies(vpninfo);
750
vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
} else
free(host);
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;
}
}
800
801
802
803
804
805
806
807
808
809
/* 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:
810
* < 0, on error
811
* >=0, on success, indicating the length of the data in *form_buf
812
*/
813
814
static int do_https_request(struct openconnect_info *vpninfo, const char *method,
const char *request_body_type, const char *request_body,
815
char **form_buf, int fetch_redirect)
816
{
817
struct oc_text_buf *buf;
818
int result, buflen;
819
820
retry:
821
822
vpninfo->redirect_type = REDIR_TYPE_NONE;
823
824
825
if (*form_buf) {
free(*form_buf);
*form_buf = NULL;
826
}
827
if (openconnect_open_https(vpninfo)) {
828
829
830
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open HTTPS connection to %s\n"),
vpninfo->hostname);
831
return -EINVAL;
832
833
834
}
/*
835
836
* It would be nice to use cURL for this, but we really need to guarantee
* that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
837
838
* to have any way to let us provide our own socket read/write functions.
* We can only provide a socket _open_ function. Which would require having
839
* a socketpair() and servicing the "other" end of it.
840
841
842
*
* So we process the HTTP for ourselves...
*/
843
844
buf = buf_alloc();
buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
845
add_common_headers(vpninfo, buf);
846
847
if (request_body_type) {
848
849
buf_append(buf, "Content-Type: %s\r\n", request_body_type);
buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
850
}
851
852
buf_append(buf, "\r\n");
853
if (request_body_type)
854
buf_append(buf, "%s", request_body);
855
856
if (vpninfo->port == 443)
857
vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
858
859
method, vpninfo->hostname,
vpninfo->urlpath ?: "");
860
else
861
vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
862
863
method, vpninfo->hostname, vpninfo->port,
vpninfo->urlpath ?: "");
864
865
866
867
868
869
if (buf_error(buf))
return buf_free(buf);
result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
buf_free(buf);
870
871
if (result < 0)
return result;
872
873
buflen = process_http_response(vpninfo, &result, NULL, form_buf);
874
875
if (buflen < 0) {
/* We'll already have complained about whatever offended us */
876
return buflen;
877
878
879
}
if (result != 200 && vpninfo->redirect_url) {
880
result = handle_redirect(vpninfo);
881
882
883
if (result == 0) {
if (!fetch_redirect)
return 0;
884
goto retry;
885
}
886
goto out;
887
}
888
if (!*form_buf || result != 200) {
889
vpn_progress(vpninfo, PRG_ERR,
890
891
_("Unexpected %d result from server\n"),
result);
892
893
894
895
896
897
898
899
900
901
902
903
result = -EINVAL;
goto out;
}
return buflen;
out:
free(*form_buf);
*form_buf = NULL;
return result;
}
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
/* Return value:
* < 0, if the data is unrecognized
* = 0, if the page contains an XML document
* = 1, if the page is a wait/refresh HTML page
*/
static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
{
if (strncmp(form_buf, "<?xml", 5)) {
/* Not XML? Perhaps it's HTML with a refresh... */
if (strcasestr(form_buf, "http-equiv=\"refresh\""))
return 1;
vpn_progress(vpninfo, PRG_ERR,
_("Unknown response from server\n"));
return -EINVAL;
}
return 0;
}
922
923
924
925
926
927
928
929
930
/* Return value:
* < 0, on error
* > 0, no cookie (user cancel)
* = 0, obtained cookie
*/
int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
{
struct vpn_option *opt;
char *form_buf = NULL;
931
932
struct oc_auth_form *form = NULL;
int result, buflen, tries;
933
char request_body[2048];
934
935
936
const char *request_body_type = "application/x-www-form-urlencoded";
const char *method = "POST";
int xmlpost = 0;
937
938
/* Step 1: Unlock software token (if applicable) */
939
940
941
942
if (vpninfo->use_stoken) {
result = prepare_stoken(vpninfo);
if (result)
return result;
943
}
944
945
946
947
948
949
950
951
952
953
954
955
956
/*
* Step 2: Probe for XML POST compatibility
*
* This can get stuck in a redirect loop, so give up after any of:
*
* a) HTTP error (e.g. 400 Bad Request)
* b) Same-host redirect (e.g. Location: /foo/bar)
* c) Three redirects without seeing a plausible login form
*/
result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
if (result < 0)
return result;
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
for (tries = 0; ; tries++) {
if (tries == 3)
break;
buflen = do_https_request(vpninfo, method, request_body_type, request_body,
&form_buf, 0);
if (buflen == -EINVAL)
break;
if (buflen < 0)
return buflen;
if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
break;
else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
continue;
result = parse_xml_response(vpninfo, form_buf, &form);
if (result < 0)
break;
xmlpost = 1;
vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
break;
}
/* Step 3: Fetch and parse the login form, if not using XML POST */
if (!xmlpost) {
buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
if (buflen < 0)
return buflen;
result = parse_xml_response(vpninfo, form_buf, &form);
if (result < 0) {
990
free(form_buf);
991
return result;
992
}
993
994
995
996
if (form->action) {
vpninfo->redirect_url = strdup(form->action);
handle_redirect(vpninfo);
}
997
}
998
999
1000
/* Step 4: Run the CSD trojan, if applicable */
if (vpninfo->csd_starturl) {