/
http.c
1207 lines (1067 loc) · 31.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
39
#include "openconnect-internal.h"
40
41
42
43
44
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);
45
46
#define MAX_BUF_LEN 131072
47
/*
48
* We didn't really want to have to do this for ourselves -- one might have
49
50
51
52
53
54
* 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.
*/
55
56
static int http_add_cookie(struct openconnect_info *vpninfo,
const char *option, const char *value)
57
58
59
60
61
62
{
struct vpn_option *new, **this;
if (*value) {
new = malloc(sizeof(*new));
if (!new) {
63
64
vpn_progress(vpninfo, PRG_ERR,
_("No memory for allocating cookies\n"));
65
66
67
68
69
return -ENOMEM;
}
new->next = NULL;
new->option = strdup(option);
new->value = strdup(value);
70
71
72
73
74
75
if (!new->option || !new->value) {
free(new->option);
free(new->value);
free(new);
return -ENOMEM;
}
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
} 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;
}
102
103
104
#define BODY_HTTP10 -1
#define BODY_CHUNKED -2
105
106
static int process_http_response(struct openconnect_info *vpninfo, int *result,
int (*header_cb)(struct openconnect_info *, char *, char *),
107
char **body_ret)
108
{
109
char buf[MAX_BUF_LEN];
110
char *body = NULL;
111
int bodylen = BODY_HTTP10;
112
int done = 0;
113
int closeconn = 0;
114
115
int i;
116
cont:
117
if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
118
119
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTPS response\n"));
120
return -EINVAL;
121
122
}
123
if (!strncmp(buf, "HTTP/1.0 ", 9))
124
closeconn = 1;
125
126
if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
127
128
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse HTTP response '%s'\n"), buf);
129
130
131
return -EINVAL;
}
132
vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
133
_("Got HTTP response: %s\n"), buf);
134
135
/* Eat headers... */
136
while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
137
138
139
char *colon;
if (i < 0) {
140
141
vpn_progress(vpninfo, PRG_ERR,
_("Error processing HTTP response\n"));
142
143
144
145
return -EINVAL;
}
colon = strchr(buf, ':');
if (!colon) {
146
147
vpn_progress(vpninfo, PRG_ERR,
_("Ignoring unknown HTTP response line '%s'\n"), buf);
148
149
150
151
152
153
continue;
}
*(colon++) = 0;
if (*colon == ' ')
colon++;
154
155
156
157
/* 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, ';');
158
159
const char *print_equals;
char *equals = strchr(colon, '=');
160
161
162
163
164
165
int ret;
if (semicolon)
*semicolon = 0;
if (!equals) {
166
167
vpn_progress(vpninfo, PRG_ERR,
_("Invalid cookie offered: %s\n"), buf);
168
169
170
171
172
return -EINVAL;
}
*(equals++) = 0;
print_equals = equals;
173
174
175
/* 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)
176
print_equals = _("<elided>");
177
vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
178
179
buf, colon, print_equals, semicolon?";":"",
semicolon?(semicolon+1):"");
180
181
182
183
184
185
186
187
/* 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"));
188
189
190
191
ret = http_add_cookie(vpninfo, colon, equals);
if (ret)
return ret;
} else {
192
vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
193
194
}
195
196
197
if (!strcasecmp(buf, "Connection")) {
if (!strcasecmp(colon, "Close"))
closeconn = 1;
198
199
200
201
202
203
#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. */
204
205
else if (!strcasecmp(colon, "Keep-Alive"))
closeconn = 0;
206
#endif
207
}
208
if (!strcasecmp(buf, "Location")) {
209
210
211
212
vpninfo->redirect_url = strdup(colon);
if (!vpninfo->redirect_url)
return -ENOMEM;
}
213
if (!strcasecmp(buf, "Content-Length")) {
214
bodylen = atoi(colon);
215
if (bodylen < 0) {
216
217
218
vpn_progress(vpninfo, PRG_ERR,
_("Response body has negative size (%d)\n"),
bodylen);
219
220
221
return -EINVAL;
}
}
222
223
if (!strcasecmp(buf, "Transfer-Encoding")) {
if (!strcasecmp(colon, "chunked"))
224
bodylen = BODY_CHUNKED;
225
else {
226
227
228
vpn_progress(vpninfo, PRG_ERR,
_("Unknown Transfer-Encoding: %s\n"),
colon);
229
230
231
232
233
234
235
236
237
238
239
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;
240
/* Now the body, if there is one */
241
242
243
244
vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
bodylen==BODY_HTTP10?"http 1.0" :
bodylen==BODY_CHUNKED?"chunked" : "length: ",
bodylen);
245
246
247
/* If we were given Content-Length, it's nice and easy... */
if (bodylen > 0) {
248
249
250
body = malloc(bodylen + 1);
if (!body)
return -ENOMEM;
251
while (done < bodylen) {
252
i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
253
if (i < 0) {
254
255
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
256
free(body);
257
258
259
260
return -EINVAL;
}
done += i;
}
261
262
} else if (bodylen == BODY_CHUNKED) {
/* ... else, chunked */
263
while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
264
int chunklen, lastchunk = 0;
265
266
if (i < 0) {
267
268
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching chunk header\n"));
269
return i;
270
271
272
273
274
275
}
chunklen = strtol(buf, NULL, 16);
if (!chunklen) {
lastchunk = 1;
goto skip;
}
276
277
278
body = realloc(body, done + chunklen + 1);
if (!body)
return -ENOMEM;
279
while (chunklen) {
280
i = openconnect_SSL_read(vpninfo, body + done, chunklen);
281
if (i < 0) {
282
283
vpn_progress(vpninfo, PRG_ERR,
_("Error reading HTTP response body\n"));
284
free(body);
285
286
287
288
289
290
return -EINVAL;
}
chunklen -= i;
done += i;
}
skip:
291
if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
292
if (i < 0) {
293
294
vpn_progress(vpninfo, PRG_ERR,
_("Error fetching HTTP response body\n"));
295
} else {
296
297
298
vpn_progress(vpninfo, PRG_ERR,
_("Error in chunked decoding. Expected '', got: '%s'"),
buf);
299
}
300
free(body);
301
return -EINVAL;
302
}
303
304
305
306
307
308
if (lastchunk)
break;
}
} else if (bodylen == BODY_HTTP10) {
if (!closeconn) {
309
310
vpn_progress(vpninfo, PRG_ERR,
_("Cannot receive HTTP 1.0 body without closing connection\n"));
311
312
313
return -EINVAL;
}
314
/* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
315
while (1) {
316
body = realloc(body, done + 16384);
317
318
if (!body)
return -ENOMEM;
319
320
321
322
323
324
325
326
327
328
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 */
329
body = realloc(body, done + 1);
330
331
if (!body)
return -ENOMEM;
332
break;
333
}
334
}
335
}
336
337
if (closeconn || vpninfo->no_http_keepalive)
338
openconnect_close_https(vpninfo, 0);
339
340
341
342
if (body)
body[done] = 0;
*body_ret = body;
343
344
345
return done;
}
346
static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
347
char *server_sha1)
348
349
{
struct vpn_option *opt;
350
char buf[MAX_BUF_LEN];
351
char *config_buf = NULL;
352
int result, buflen;
353
354
unsigned char local_sha1_bin[SHA1_SIZE];
char local_sha1_ascii[(SHA1_SIZE * 2)+1];
355
356
int i;
357
358
359
360
361
362
363
if (openconnect_open_https(vpninfo)) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open HTTPS connection to %s\n"),
vpninfo->hostname);
return -EINVAL;
}
364
365
366
367
368
369
370
371
372
373
sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
sprintf(buf + strlen(buf), "Accept: */*\r\n");
sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
if (vpninfo->cookies) {
sprintf(buf + strlen(buf), "Cookie: ");
for (opt = vpninfo->cookies; opt; opt = opt->next)
sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
374
opt->value, opt->next ? "; " : "\r\n");
375
376
377
}
sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
378
if (openconnect_SSL_write(vpninfo, buf, strlen(buf)) != strlen(buf)) {
379
380
381
382
vpn_progress(vpninfo, PRG_ERR,
_("Failed to send GET request for new config\n"));
return -EIO;
}
383
384
buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
385
386
387
388
389
if (buflen < 0) {
/* We'll already have complained about whatever offended us */
return -EINVAL;
}
390
391
if (result != 200) {
free(config_buf);
392
return -EINVAL;
393
}
394
395
openconnect_sha1(local_sha1_bin, config_buf, buflen);
396
397
for (i = 0; i < SHA1_SIZE; i++)
398
399
400
sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
if (strcasecmp(server_sha1, local_sha1_ascii)) {
401
402
vpn_progress(vpninfo, PRG_ERR,
_("Downloaded config file did not match intended SHA1\n"));
403
free(config_buf);
404
405
406
return -EINVAL;
}
407
result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
408
409
free(config_buf);
return result;
410
}
411
412
static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
413
414
{
char fname[16];
415
int fd, ret;
416
417
if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
418
vpn_progress(vpninfo, PRG_ERR,
419
420
_("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."));
421
return -EPERM;
422
423
}
424
#ifndef __linux__
425
vpn_progress(vpninfo, PRG_INFO,
426
_("Trying to run Linux CSD trojan script."));
427
428
#endif
429
430
431
432
sprintf(fname, "/tmp/csdXXXXXX");
fd = mkstemp(fname);
if (fd < 0) {
int err = -errno;
433
434
435
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open temporary CSD script file: %s\n"),
strerror(errno));
436
437
return err;
}
438
439
ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
440
if (ret) {
441
442
443
vpn_progress(vpninfo, PRG_ERR,
_("Failed to write temporary CSD script file: %s\n"),
strerror(ret));
444
445
return ret;
}
446
fchmod(fd, 0755);
447
close(fd);
448
449
if (!fork()) {
450
451
char scertbuf[MD5_SIZE * 2 + 1];
char ccertbuf[MD5_SIZE * 2 + 1];
452
453
454
char *csd_argv[32];
int i = 0;
455
if (vpninfo->uid_csd != getuid()) {
456
457
struct passwd *pw;
458
if (setuid(vpninfo->uid_csd)) {
459
460
fprintf(stderr, _("Failed to set uid %ld\n"),
(long)vpninfo->uid_csd);
461
462
exit(1);
}
463
if (!(pw = getpwuid(vpninfo->uid_csd))) {
464
465
fprintf(stderr, _("Invalid user uid=%ld\n"),
(long)vpninfo->uid_csd);
466
467
468
exit(1);
}
setenv("HOME", pw->pw_dir, 1);
469
if (chdir(pw->pw_dir)) {
470
fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
471
472
473
pw->pw_dir, strerror(errno));
exit(1);
}
474
}
475
if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
476
477
478
fprintf(stderr, _("Warning: you are running insecure "
"CSD code with root privileges\n"
"\t Use command line option \"--csd-user\"\n"));
479
}
480
481
482
483
484
if (vpninfo->uid_csd_given == 2) {
/* The NM tool really needs not to get spurious output
on stdout, which the CSD trojan spews. */
dup2(2, 1);
}
485
486
if (vpninfo->csd_wrapper)
csd_argv[i++] = vpninfo->csd_wrapper;
487
csd_argv[i++] = fname;
488
csd_argv[i++]= (char *)"-ticket";
489
490
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
return -ENOMEM;
491
492
493
csd_argv[i++]= (char *)"-stub";
csd_argv[i++]= (char *)"\"0\"";
csd_argv[i++]= (char *)"-group";
494
495
496
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
return -ENOMEM;
497
498
499
openconnect_local_cert_md5(vpninfo, ccertbuf);
scertbuf[0] = 0;
get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
500
csd_argv[i++]= (char *)"-certhash";
501
502
if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
return -ENOMEM;
503
504
csd_argv[i++]= (char *)"-url";
505
506
if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
return -ENOMEM;
507
/* WTF would it want to know this for? */
508
509
510
csd_argv[i++]= (char *)"-vpnclient";
csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
csd_argv[i++]= (char *)"-connect";
511
512
if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
return -ENOMEM;
513
csd_argv[i++]= (char *)"-connectparam";
514
515
if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
return -ENOMEM;
516
csd_argv[i++]= (char *)"-langselen";
517
518
csd_argv[i++] = NULL;
519
execv(csd_argv[0], csd_argv);
520
521
vpn_progress(vpninfo, PRG_ERR,
_("Failed to exec CSD script %s\n"), csd_argv[0]);
522
exit(1);
523
524
525
526
}
free(vpninfo->csd_stuburl);
vpninfo->csd_stuburl = NULL;
527
528
vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
(vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
529
vpninfo->csd_waiturl = NULL;
530
vpninfo->csd_scriptname = strdup(fname);
531
532
533
http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
534
535
536
return 0;
}
537
538
#ifndef HAVE_STRCASESTR
static char *openconnect__strcasestr(const char *haystack, const char *needle)
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
{
int hlen = strlen(haystack);
int nlen = strlen(needle);
int i, j;
for (i = 0; i < hlen - nlen + 1; i++) {
for (j = 0; j < nlen; j++) {
if (tolower(haystack[i + j]) !=
tolower(needle[j]))
break;
}
if (j == nlen)
return (char *)haystack + i;
}
return NULL;
}
555
556
557
#define strcasestr openconnect__strcasestr
#endif
558
559
560
int internal_parse_url(char *url, char **res_proto, char **res_host,
int *res_port, char **res_path, int default_port)
561
562
563
564
565
566
{
char *proto = url;
char *host, *path, *port_str;
int port;
host = strstr(url, "://");
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
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;
}
589
590
path = strchr(host, '/');
591
if (path)
592
593
594
595
596
597
598
599
600
601
602
603
604
605
*(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)
606
*res_proto = proto ? strdup(proto) : NULL;
607
608
609
610
611
if (res_host)
*res_host = strdup(host);
if (res_port)
*res_port = port;
if (res_path)
612
613
614
*res_path = (path && *path) ? strdup(path) : NULL;
/* Undo the damage we did to the original string */
615
616
if (port_str)
*(port_str) = ':';
617
618
619
620
if (path)
*(path - 1) = '/';
if (proto)
*(host - 3) = ':';
621
622
623
return 0;
}
624
625
626
627
628
/* Return value:
* < 0, on error
* = 0, no cookie (user cancel)
* = 1, obtained cookie
*/
629
int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
630
631
{
struct vpn_option *opt, *next;
632
char buf[MAX_BUF_LEN];
633
char *form_buf = NULL;
634
int result, buflen;
635
char request_body[2048];
636
637
const char *request_body_type = NULL;
const char *method = "GET";
638
639
retry:
640
641
642
643
if (form_buf) {
free(form_buf);
form_buf = NULL;
}
644
if (openconnect_open_https(vpninfo)) {
645
646
647
vpn_progress(vpninfo, PRG_ERR,
_("Failed to open HTTPS connection to %s\n"),
vpninfo->hostname);
648
return -EINVAL;
649
650
651
}
/*
652
653
* 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
654
655
* 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
656
* a socketpair() and servicing the "other" end of it.
657
658
659
*
* So we process the HTTP for ourselves...
*/
660
sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
661
662
663
664
665
sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
sprintf(buf + strlen(buf), "Accept: */*\r\n");
sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
666
if (vpninfo->cookies) {
667
sprintf(buf + strlen(buf), "Cookie: ");
668
for (opt = vpninfo->cookies; opt; opt = opt->next)
669
sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
670
opt->value, opt->next ? "; " : "\r\n");
671
}
672
if (request_body_type) {
673
sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
674
request_body_type);
675
sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
676
677
strlen(request_body));
}
678
679
680
681
sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
if (request_body_type)
sprintf(buf + strlen(buf), "%s", request_body);
682
if (vpninfo->port == 443)
683
vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
684
685
method, vpninfo->hostname,
vpninfo->urlpath ?: "");
686
else
687
vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
688
689
method, vpninfo->hostname, vpninfo->port,
vpninfo->urlpath ?: "");
690
691
692
693
result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
if (result < 0)
return result;
694
695
buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
696
697
if (buflen < 0) {
/* We'll already have complained about whatever offended us */
698
return buflen;
699
700
701
}
if (result != 200 && vpninfo->redirect_url) {
702
redirect:
703
704
if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
/* New host. Tear down the existing connection and make a new one */
705
706
707
char *host;
int port;
int ret;
708
709
free(vpninfo->urlpath);
710
vpninfo->urlpath = NULL;
711
712
ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
713
if (ret) {
714
715
716
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse redirected URL '%s': %s\n"),
vpninfo->redirect_url, strerror(-ret));
717
free(vpninfo->redirect_url);
718
vpninfo->redirect_url = NULL;
719
free(form_buf);
720
return ret;
721
}
722
723
if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
724
free(vpninfo->hostname);
725
vpninfo->hostname = host;
726
vpninfo->port = port;
727
728
/* Kill the existing connection, and a new one will happen */
729
730
free(vpninfo->peer_addr);
vpninfo->peer_addr = NULL;
731
openconnect_close_https(vpninfo, 0);
732
733
734
735
736
737
738
739
740
for (opt = vpninfo->cookies; opt; opt = next) {
next = opt->next;
free(opt->option);
free(opt->value);
free(opt);
}
vpninfo->cookies = NULL;
741
742
743
} else
free(host);
744
745
746
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
747
goto retry;
748
749
750
751
752
753
754
755
} 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;
free(form_buf);
return -EINVAL;
756
757
758
} else if (vpninfo->redirect_url[0] == '/') {
/* Absolute redirect within same host */
free(vpninfo->urlpath);
759
760
vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
free(vpninfo->redirect_url);
761
762
763
vpninfo->redirect_url = NULL;
goto retry;
} else {
764
765
766
char *lastslash = NULL;
if (vpninfo->urlpath)
lastslash = strrchr(vpninfo->urlpath, '/');
767
768
769
770
771
772
773
774
775
776
777
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;
778
vpn_progress(vpninfo, PRG_ERR,
779
780
_("Allocating new path for relative redirect failed: %s\n"),
strerror(-err));
781
782
783
784
785
786
787
return err;
}
free(oldurl);
free(vpninfo->redirect_url);
vpninfo->redirect_url = NULL;
}
goto retry;
788
789
}
}
790
if (!form_buf || result != 200) {
791
vpn_progress(vpninfo, PRG_ERR,
792
793
_("Unexpected %d result from server\n"),
result);
794
free(form_buf);
795
796
return -EINVAL;
}
797
798
if (vpninfo->csd_stuburl) {
/* This is the CSD stub script, which we now need to run */
799
800
801
result = run_csd_script(vpninfo, form_buf, buflen);
if (result) {
free(form_buf);
802
return result;
803
}
804
805
806
807
/* Now we'll be redirected to the waiturl */
goto retry;
}
808
if (strncmp(form_buf, "<?xml", 5)) {
809
/* Not XML? Perhaps it's HTML with a refresh... */
810
if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
811
812
813
vpn_progress(vpninfo, PRG_INFO,
_("Refreshing %s after 1 second...\n"),
vpninfo->urlpath);
814
815
816
sleep(1);
goto retry;
}
817
818
vpn_progress(vpninfo, PRG_ERR,
_("Unknown response from server\n"));
819
free(form_buf);
820
821
return -EINVAL;
}
822
request_body[0] = 0;
823
result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
824
&method, &request_body_type);
825
826
if (!result)
827
goto redirect;
828
829
830
free(form_buf);
831
if (result != 2)
832
return result;
833
834
835
/* A return value of 2 means the XML form indicated
success. We _should_ have a cookie... */
836
837
838
839
840
for (opt = vpninfo->cookies; opt; opt = opt->next) {
if (!strcmp(opt->option, "webvpn"))
vpninfo->cookie = opt->value;
841
else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
842
843
844
845
846
847
848
849
850
851
852
853
char *tok = opt->value;
char *bu = NULL, *fu = NULL, *sha = NULL;
do {
if (tok != opt->value)
*(tok++) = 0;
if (!strncmp(tok, "bu:", 3))
bu = tok + 3;
else if (!strncmp(tok, "fu:", 3))
fu = tok + 3;
else if (!strncmp(tok, "fh:", 3)) {
854
if (!strncasecmp(tok+3, vpninfo->xmlsha1,
855
SHA1_SIZE * 2))
856
857
break;
sha = tok + 3;
858
}
859
860
861
862
} while ((tok = strchr(tok, '&')));
if (bu && fu && sha)
fetch_config(vpninfo, bu, fu, sha);
863
864
}
}
865
866
867
868
869
if (vpninfo->csd_scriptname) {
unlink(vpninfo->csd_scriptname);
free(vpninfo->csd_scriptname);
vpninfo->csd_scriptname = NULL;
}
870
return 0;
871
}
872
873
char *openconnect_create_useragent(const char *base)
874
{
875
876
char *uagent;
877
if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
878
879
return NULL;
880
881
return uagent;
}
882
883
884
static int proxy_gets(struct openconnect_info *vpninfo, int fd,
char *buf, size_t len)
885
886
887
888
889
890
891
{
int i = 0;
int ret;
if (len < 2)
return -EINVAL;
892
while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
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;
}
912
913
static int proxy_write(struct openconnect_info *vpninfo, int fd,
unsigned char *buf, size_t len)
914
915
{
size_t count;
916
917
for (count = 0; count < len; ) {
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
fd_set rd_set, wr_set;
int maxfd = fd;
int i;
FD_ZERO(&wr_set);
FD_ZERO(&rd_set);
FD_SET(fd, &wr_set);
if (vpninfo->cancel_fd != -1) {
FD_SET(vpninfo->cancel_fd, &rd_set);
if (vpninfo->cancel_fd > fd)
maxfd = vpninfo->cancel_fd;
}
select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
if (vpninfo->cancel_fd != -1 &&
FD_ISSET(vpninfo->cancel_fd, &rd_set))
return -EINTR;
/* Not that this should ever be able to happen... */
if (!FD_ISSET(fd, &wr_set))
continue;
i = write(fd, buf + count, len - count);
941
942
943
944
945
946
947
if (i < 0)
return -errno;
count += i;
}
return 0;
}
948
949
950
static int proxy_read(struct openconnect_info *vpninfo, int fd,
unsigned char *buf, size_t len)
951
952
953
954
{
size_t count;
for (count = 0; count < len; ) {
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
fd_set rd_set;
int maxfd = fd;
int i;
FD_ZERO(&rd_set);
FD_SET(fd, &rd_set);
if (vpninfo->cancel_fd != -1) {
FD_SET(vpninfo->cancel_fd, &rd_set);
if (vpninfo->cancel_fd > fd)
maxfd = vpninfo->cancel_fd;
}
select(maxfd + 1, &rd_set, NULL, NULL, NULL);
if (vpninfo->cancel_fd != -1 &&
FD_ISSET(vpninfo->cancel_fd, &rd_set))
return -EINTR;
/* Not that this should ever be able to happen... */
if (!FD_ISSET(fd, &rd_set))
continue;
i = read(fd, buf + count, len - count);
977
978
979
980
981
982
983
984
985
if (i < 0)
return -errno;
count += i;
}
return 0;
}
static const char *socks_errors[] = {
986
987
988
989
990
991
992
993
994
N_("request granted"),
N_("general failure"),
N_("connection not allowed by ruleset"),
N_("network unreachable"),
N_("host unreachable"),
N_("connection refused by destination host"),
N_("TTL expired"),
N_("command not supported / protocol error"),
N_("address type not supported")
995
996
997
998
999
1000
};
static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
{
unsigned char buf[1024];
int i;