Refuse to redirect to a non-https URL
[platform/upstream/openconnect.git] / http.c
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  * Author: David Woodhouse <dwmw2@infradead.org>
8  *
9  * 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  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to:
20  *
21  *   Free Software Foundation, Inc.
22  *   51 Franklin Street, Fifth Floor,
23  *   Boston, MA 02110-1301 USA
24  */
25
26 #include <netdb.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #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 #include <openssl/ssl.h>
37 #include <openssl/err.h>
38 #include <openssl/engine.h>
39
40 #include "openconnect-internal.h"
41
42 static int proxy_write(struct openconnect_info *vpninfo, int fd,
43                        unsigned char *buf, size_t len);
44 static int proxy_read(struct openconnect_info *vpninfo, int fd,
45                       unsigned char *buf, size_t len);
46
47 #define MAX_BUF_LEN 131072
48 /*
49  * We didn't really want to have to do this for ourselves -- one might have
50  * thought that it would be available in a library somewhere. But neither
51  * cURL nor Neon have reliable cross-platform ways of either using a cert
52  * from the TPM, or just reading from / writing to a transport which is
53  * provided by their caller.
54  */
55
56 static int http_add_cookie(struct openconnect_info *vpninfo,
57                            const char *option, const char *value)
58 {
59         struct vpn_option *new, **this;
60
61         if (*value) {
62                 new = malloc(sizeof(*new));
63                 if (!new) {
64                         vpn_progress(vpninfo, PRG_ERR,
65                                      _("No memory for allocating cookies\n"));
66                         return -ENOMEM;
67                 }
68                 new->next = NULL;
69                 new->option = strdup(option);
70                 new->value = strdup(value);
71                 if (!new->option || !new->value) {
72                         free(new->option);
73                         free(new->value);
74                         free(new);
75                         return -ENOMEM;
76                 }
77         } else {
78                 /* Kill cookie; don't replace it */
79                 new = NULL;
80         }
81         for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
82                 if (!strcmp(option, (*this)->option)) {
83                         /* Replace existing cookie */
84                         if (new)
85                                 new->next = (*this)->next;
86                         else
87                                 new = (*this)->next;
88                         
89                         free((*this)->option);
90                         free((*this)->value);
91                         free(*this);
92                         *this = new;
93                         break;
94                 }
95         }
96         if (new && !*this) {
97                 *this = new;
98                 new->next = NULL;
99         }
100         return 0;
101 }
102
103 #define BODY_HTTP10 -1
104 #define BODY_CHUNKED -2
105
106 static int process_http_response(struct openconnect_info *vpninfo, int *result,
107                                  int (*header_cb)(struct openconnect_info *, char *, char *),
108                                  char **body_ret)
109 {
110         char buf[MAX_BUF_LEN];
111         char *body = NULL;
112         int bodylen = BODY_HTTP10;
113         int done = 0;
114         int closeconn = 0;
115         int i;
116
117  cont:
118         if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
119                 vpn_progress(vpninfo, PRG_ERR,
120                              _("Error fetching HTTPS response\n"));
121                 return -EINVAL;
122         }
123
124         if (!strncmp(buf, "HTTP/1.0 ", 9))
125                 closeconn = 1;
126         
127         if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
128                 vpn_progress(vpninfo, PRG_ERR,
129                              _("Failed to parse HTTP response '%s'\n"), buf);
130                 return -EINVAL;
131         }
132
133         vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
134                      _("Got HTTP response: %s\n"), buf);
135
136         /* Eat headers... */
137         while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
138                 char *colon;
139
140                 if (i < 0) {
141                         vpn_progress(vpninfo, PRG_ERR,
142                                      _("Error processing HTTP response\n"));
143                         return -EINVAL;
144                 }
145                 colon = strchr(buf, ':');
146                 if (!colon) {
147                         vpn_progress(vpninfo, PRG_ERR,
148                                      _("Ignoring unknown HTTP response line '%s'\n"), buf);
149                         continue;
150                 }
151                 *(colon++) = 0;
152                 if (*colon == ' ')
153                         colon++;
154
155                 /* Handle Set-Cookie first so that we can avoid printing the
156                    webvpn cookie in the verbose debug output */
157                 if (!strcasecmp(buf, "Set-Cookie")) {
158                         char *semicolon = strchr(colon, ';');
159                         const char *print_equals;
160                         char *equals = strchr(colon, '=');
161                         int ret;
162
163                         if (semicolon)
164                                 *semicolon = 0;
165
166                         if (!equals) {
167                                 vpn_progress(vpninfo, PRG_ERR,
168                                              _("Invalid cookie offered: %s\n"), buf);
169                                 return -EINVAL;
170                         }
171                         *(equals++) = 0;
172
173                         print_equals = equals;
174                         /* Don't print the webvpn cookie unless it's empty; we don't
175                            want people posting it in public with debugging output */
176                         if (!strcmp(colon, "webvpn") && *equals)
177                                 print_equals = _("<elided>");
178                         vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
179                                      buf, colon, print_equals, semicolon?";":"",
180                                      semicolon?(semicolon+1):"");
181
182                         /* The server tends to ask for the username and password as
183                            usual, even if we've already failed because it didn't like
184                            our cert. Thankfully it does give us this hint... */
185                         if (!strcmp(colon, "ClientCertAuthFailed"))
186                                 vpn_progress(vpninfo, PRG_ERR,
187                                              _("SSL certificate authentication failed\n"));
188
189                         ret = http_add_cookie(vpninfo, colon, equals);
190                         if (ret)
191                                 return ret;
192                 } else {
193                         vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
194                 }
195
196                 if (!strcasecmp(buf, "Connection")) {
197                         if (!strcasecmp(colon, "Close"))
198                                 closeconn = 1;
199 #if 0
200                         /* This might seem reasonable, but in fact it breaks
201                            certificate authentication with some servers. If
202                            they give an HTTP/1.0 response, even if they
203                            explicitly give a Connection: Keep-Alive header,
204                            just close the connection. */
205                         else if (!strcasecmp(colon, "Keep-Alive"))
206                                 closeconn = 0;
207 #endif
208                 }
209                 if (!strcasecmp(buf, "Location")) {
210                         vpninfo->redirect_url = strdup(colon);
211                         if (!vpninfo->redirect_url)
212                                 return -ENOMEM;
213                 }
214                 if (!strcasecmp(buf, "Content-Length")) {
215                         bodylen = atoi(colon);
216                         if (bodylen < 0) {
217                                 vpn_progress(vpninfo, PRG_ERR,
218                                              _("Response body has negative size (%d)\n"),
219                                              bodylen);
220                                 return -EINVAL;
221                         }
222                 }
223                 if (!strcasecmp(buf, "Transfer-Encoding")) {
224                         if (!strcasecmp(colon, "chunked"))
225                                 bodylen = BODY_CHUNKED;
226                         else {
227                                 vpn_progress(vpninfo, PRG_ERR,
228                                              _("Unknown Transfer-Encoding: %s\n"),
229                                              colon);
230                                 return -EINVAL;
231                         }
232                 }
233                 if (header_cb && !strncmp(buf, "X-", 2))
234                         header_cb(vpninfo, buf, colon);
235         }
236
237         /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
238         if (*result == 100)
239                 goto cont;
240
241         /* Now the body, if there is one */
242         vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"), 
243                      bodylen==BODY_HTTP10?"http 1.0" :
244                      bodylen==BODY_CHUNKED?"chunked" : "length: ",
245                      bodylen);
246
247         /* If we were given Content-Length, it's nice and easy... */
248         if (bodylen > 0) {
249                 body = malloc(bodylen + 1);
250                 if (!body)
251                         return -ENOMEM;
252                 while (done < bodylen) {
253                         i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
254                         if (i < 0) {
255                                 vpn_progress(vpninfo, PRG_ERR,
256                                              _("Error reading HTTP response body\n"));
257                                 free(body);
258                                 return -EINVAL;
259                         }
260                         done += i;
261                 }
262         } else if (bodylen == BODY_CHUNKED) {
263                 /* ... else, chunked */
264                 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
265                         int chunklen, lastchunk = 0;
266
267                         if (i < 0) {
268                                 vpn_progress(vpninfo, PRG_ERR,
269                                              _("Error fetching chunk header\n"));
270                                 return i;
271                         }
272                         chunklen = strtol(buf, NULL, 16);
273                         if (!chunklen) {
274                                 lastchunk = 1;
275                                 goto skip;
276                         }
277                         body = realloc(body, done + chunklen + 1);
278                         if (!body)
279                                 return -ENOMEM;
280                         while (chunklen) {
281                                 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
282                                 if (i < 0) {
283                                         vpn_progress(vpninfo, PRG_ERR,
284                                                      _("Error reading HTTP response body\n"));
285                                         free(body);
286                                         return -EINVAL;
287                                 }
288                                 chunklen -= i;
289                                 done += i;
290                         }
291                 skip:
292                         if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
293                                 if (i < 0) {
294                                         vpn_progress(vpninfo, PRG_ERR,
295                                                      _("Error fetching HTTP response body\n"));
296                                 } else {
297                                         vpn_progress(vpninfo, PRG_ERR,
298                                                      _("Error in chunked decoding. Expected '', got: '%s'"),
299                                                      buf);
300                                 }
301                                 free(body);
302                                 return -EINVAL;
303                         }
304
305                         if (lastchunk)
306                                 break;
307                 }
308         } else if (bodylen == BODY_HTTP10) {
309                 if (!closeconn) {
310                         vpn_progress(vpninfo, PRG_ERR,
311                                      _("Cannot receive HTTP 1.0 body without closing connection\n"));
312                         return -EINVAL;
313                 }
314
315                 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
316                 while (1) {
317                         body = realloc(body, done + 16384);
318                         if (!body)
319                                 return -ENOMEM;
320                         i = openconnect_SSL_read(vpninfo, body + done, 16384);
321                         if (i > 0) {
322                                 /* Got more data */
323                                 done += i;
324                         } else if (i < 0) {
325                                 /* Error */
326                                 free(body);
327                                 return i;
328                         } else {
329                                 /* Connection closed. Reduce allocation to just what we need */
330                                 body = realloc(body, done + 1);
331                                 if (!body)
332                                         return -ENOMEM;
333                                 break;
334                         }
335                 }
336         }
337
338         if (closeconn || vpninfo->no_http_keepalive) {
339                 SSL_free(vpninfo->https_ssl);
340                 vpninfo->https_ssl = NULL;
341                 close(vpninfo->ssl_fd);
342                 vpninfo->ssl_fd = -1;
343         }
344
345         if (body)
346                 body[done] = 0;
347         *body_ret = body;
348         return done;
349 }
350
351 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
352                         char *server_sha1)
353 {
354         struct vpn_option *opt;
355         char buf[MAX_BUF_LEN];
356         char *config_buf = NULL;
357         int result, buflen;
358         unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
359         char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
360         EVP_MD_CTX c;
361         int i;
362
363         sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
364         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
365         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
366         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
367         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
368
369         if (vpninfo->cookies) {
370                 sprintf(buf + strlen(buf),  "Cookie: ");
371                 for (opt = vpninfo->cookies; opt; opt = opt->next)
372                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
373                                       opt->value, opt->next ? "; " : "\r\n");
374         }
375         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
376
377         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
378
379         buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
380         if (buflen < 0) {
381                 /* We'll already have complained about whatever offended us */
382                 return -EINVAL;
383         }
384
385         if (result != 200) {
386                 free(config_buf);
387                 return -EINVAL;
388         }
389
390         EVP_MD_CTX_init(&c);
391         EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
392         EVP_MD_CTX_cleanup(&c);
393
394         for (i = 0; i < SHA_DIGEST_LENGTH; i++)
395                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
396
397         if (strcasecmp(server_sha1, local_sha1_ascii)) {
398                 vpn_progress(vpninfo, PRG_ERR,
399                              _("Downloaded config file did not match intended SHA1\n"));
400                 free(config_buf);
401                 return -EINVAL;
402         }
403
404         result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
405         free(config_buf);
406         return result;
407 }
408
409 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
410 {
411         char fname[16];
412         int fd, ret;
413
414         if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
415                 vpn_progress(vpninfo, PRG_ERR,
416                              _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
417                                "This facility is disabled by default for security reasons, so you may wish to enable it."));
418                 return -EPERM;
419         }
420
421 #ifndef __linux__
422         vpn_progress(vpninfo, PRG_INFO,
423                      _("Trying to run Linux CSD trojan script."));
424 #endif
425
426         sprintf(fname, "/tmp/csdXXXXXX");
427         fd = mkstemp(fname);
428         if (fd < 0) {
429                 int err = -errno;
430                 vpn_progress(vpninfo, PRG_ERR,
431                              _("Failed to open temporary CSD script file: %s\n"),
432                              strerror(errno));
433                 return err;
434         }
435
436         ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
437         if (ret) {
438                 vpn_progress(vpninfo, PRG_ERR,
439                              _("Failed to write temporary CSD script file: %s\n"),
440                              strerror(ret));
441                 return ret;
442         }
443         fchmod(fd, 0755);
444         close(fd);
445
446         if (!fork()) {
447                 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
448                 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
449                 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
450                 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
451                 char *csd_argv[32];
452                 int i = 0;
453
454                 if (vpninfo->uid_csd != getuid()) {
455                         struct passwd *pw;
456
457                         if (setuid(vpninfo->uid_csd)) {
458                                 fprintf(stderr, _("Failed to set uid %ld\n"),
459                                         (long)vpninfo->uid_csd);
460                                 exit(1);
461                         }
462                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
463                                 fprintf(stderr, _("Invalid user uid=%ld\n"),
464                                         (long)vpninfo->uid_csd);
465                                 exit(1);
466                         }
467                         setenv("HOME", pw->pw_dir, 1);
468                         if (chdir(pw->pw_dir)) {
469                                 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
470                                         pw->pw_dir, strerror(errno));
471                                 exit(1);
472                         }
473                 }
474                 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
475                         fprintf(stderr, _("Warning: you are running insecure "
476                                           "CSD code with root privileges\n"
477                                           "\t Use command line option \"--csd-user\"\n"));
478                 }
479                 if (vpninfo->uid_csd_given == 2) {             
480                         /* The NM tool really needs not to get spurious output
481                            on stdout, which the CSD trojan spews. */
482                         dup2(2, 1);
483                 }
484                 if (vpninfo->csd_wrapper)
485                         csd_argv[i++] = vpninfo->csd_wrapper;
486                 csd_argv[i++] = fname;
487                 csd_argv[i++]= (char *)"-ticket";
488                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
489                         return -ENOMEM;
490                 csd_argv[i++]= (char *)"-stub";
491                 csd_argv[i++]= (char *)"\"0\"";
492                 csd_argv[i++]= (char *)"-group";
493                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
494                         return -ENOMEM;
495
496                 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
497                 if (ccert)
498                         get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
499                 else
500                         ccertbuf[0] = 0;
501
502                 csd_argv[i++]= (char *)"-certhash";
503                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
504                         return -ENOMEM;
505                 csd_argv[i++]= (char *)"-url";
506                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
507                         return -ENOMEM;
508                 /* WTF would it want to know this for? */
509                 csd_argv[i++]= (char *)"-vpnclient";
510                 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
511                 csd_argv[i++]= (char *)"-connect";
512                 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
513                         return -ENOMEM;
514                 csd_argv[i++]= (char *)"-connectparam";
515                 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
516                         return -ENOMEM;
517                 csd_argv[i++]= (char *)"-langselen";
518                 csd_argv[i++] = NULL;
519
520                 execv(csd_argv[0], csd_argv);
521                 vpn_progress(vpninfo, PRG_ERR,
522                              _("Failed to exec CSD script %s\n"), csd_argv[0]);
523                 exit(1);
524         }
525
526         free(vpninfo->csd_stuburl);
527         vpninfo->csd_stuburl = NULL;
528         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
529                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
530         vpninfo->csd_waiturl = NULL;
531         vpninfo->csd_scriptname = strdup(fname);
532
533         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
534
535         return 0;
536 }
537
538 #ifndef HAVE_STRCASESTR
539 static char *openconnect__strcasestr(const char *haystack, const char *needle)
540 {
541         int hlen = strlen(haystack);
542         int nlen = strlen(needle);
543         int i, j;
544
545         for (i = 0; i < hlen - nlen + 1; i++) {
546                 for (j = 0; j < nlen; j++) {
547                         if (tolower(haystack[i + j]) != 
548                             tolower(needle[j]))
549                                 break;
550                 }
551                 if (j == nlen)
552                         return (char *)haystack + i;
553         }
554         return NULL;
555 }
556 #define strcasestr openconnect__strcasestr
557 #endif
558
559
560 int internal_parse_url(char *url, char **res_proto, char **res_host,
561                        int *res_port, char **res_path, int default_port)
562 {
563         char *proto = url;
564         char *host, *path, *port_str;
565         int port;
566
567         host = strstr(url, "://");
568         if (host) {
569                 *host = 0;
570                 host += 3;
571
572                 if (!strcasecmp(proto, "https"))
573                         port = 443;
574                 else if (!strcasecmp(proto, "http"))
575                         port = 80;
576                 else if (!strcasecmp(proto, "socks") ||
577                          !strcasecmp(proto, "socks4") ||
578                          !strcasecmp(proto, "socks5"))
579                         port = 1080;
580                 else
581                         return -EPROTONOSUPPORT;
582         } else {
583                 if (default_port) {
584                         proto = NULL;
585                         port = default_port;
586                         host = url;
587                 } else
588                         return -EINVAL;
589         }
590
591         path = strchr(host, '/');
592         if (path)
593                 *(path++) = 0;
594
595         port_str = strrchr(host, ':');
596         if (port_str) {
597                 char *end;
598                 int new_port = strtol(port_str + 1, &end, 10);
599
600                 if (!*end) {
601                         *port_str = 0;
602                         port = new_port;
603                 }
604         }
605
606         if (res_proto)
607                 *res_proto = proto ? strdup(proto) : NULL;
608         if (res_host)
609                 *res_host = strdup(host);
610         if (res_port)
611                 *res_port = port;
612         if (res_path)
613                 *res_path = (path && *path) ? strdup(path) : NULL;
614
615         /* Undo the damage we did to the original string */
616         if (port_str)
617                 *(port_str) = ':';
618         if (path)
619                 *(path - 1) = '/';
620         if (proto)
621                 *(host - 3) = ':';
622         return 0;
623 }
624
625 /* Return value:
626  *  < 0, on error
627  *  = 0, no cookie (user cancel)
628  *  = 1, obtained cookie
629  */
630 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
631 {
632         struct vpn_option *opt, *next;
633         char buf[MAX_BUF_LEN];
634         char *form_buf = NULL;
635         int result, buflen;
636         char request_body[2048];
637         const char *request_body_type = NULL;
638         const char *method = "GET";
639
640  retry:
641         if (form_buf) {
642                 free(form_buf);
643                 form_buf = NULL;
644         }
645         if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
646                 vpn_progress(vpninfo, PRG_ERR,
647                              _("Failed to open HTTPS connection to %s\n"),
648                              vpninfo->hostname);
649                 return -EINVAL;
650         }
651
652         /*
653          * It would be nice to use cURL for this, but we really need to guarantee
654          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
655          * to have any way to let us provide our own socket read/write functions.
656          * We can only provide a socket _open_ function. Which would require having
657          * a socketpair() and servicing the "other" end of it.
658          *
659          * So we process the HTTP for ourselves...
660          */
661         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
662         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
663         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
664         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
665         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
666
667         if (vpninfo->cookies) {
668                 sprintf(buf + strlen(buf),  "Cookie: ");
669                 for (opt = vpninfo->cookies; opt; opt = opt->next)
670                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
671                                       opt->value, opt->next ? "; " : "\r\n");
672         }
673         if (request_body_type) {
674                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
675                               request_body_type);
676                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
677                               strlen(request_body));
678         }
679         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
680         if (request_body_type)
681                 sprintf(buf + strlen(buf), "%s", request_body);
682
683         if (vpninfo->port == 443)
684                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
685                              method, vpninfo->hostname,
686                              vpninfo->urlpath ?: "");
687         else
688                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
689                              method, vpninfo->hostname, vpninfo->port,
690                              vpninfo->urlpath ?: "");
691
692         result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
693         if (result < 0)
694                 return result;
695
696         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
697         if (buflen < 0) {
698                 /* We'll already have complained about whatever offended us */
699                 return buflen;
700         }
701
702         if (result != 200 && vpninfo->redirect_url) {
703         redirect:
704                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
705                         /* New host. Tear down the existing connection and make a new one */
706                         char *host;
707                         int port;
708                         int ret;
709
710                         free(vpninfo->urlpath);
711                         vpninfo->urlpath = NULL;
712
713                         ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
714                         if (ret) {
715                                 vpn_progress(vpninfo, PRG_ERR,
716                                              _("Failed to parse redirected URL '%s': %s\n"),
717                                              vpninfo->redirect_url, strerror(-ret));
718                                 free(vpninfo->redirect_url);
719                                 vpninfo->redirect_url = NULL;
720                                 free(form_buf);
721                                 return ret;
722                         }
723
724                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
725                                 free(vpninfo->hostname);
726                                 vpninfo->hostname = host;
727                                 vpninfo->port = port;
728
729                                 /* Kill the existing connection, and a new one will happen */
730                                 free(vpninfo->peer_addr);
731                                 vpninfo->peer_addr = NULL;
732                                 if (vpninfo->https_ssl) {
733                                         SSL_free(vpninfo->https_ssl);
734                                         vpninfo->https_ssl = NULL;
735                                         close(vpninfo->ssl_fd);
736                                         vpninfo->ssl_fd = -1;
737                                 }
738
739                                 for (opt = vpninfo->cookies; opt; opt = next) {
740                                         next = opt->next;
741
742                                         free(opt->option);
743                                         free(opt->value);
744                                         free(opt);
745                                 }
746                                 vpninfo->cookies = NULL;
747                         } else
748                                 free(host);
749
750                         free(vpninfo->redirect_url);
751                         vpninfo->redirect_url = NULL;
752
753                         goto retry;
754                 } else if (strstr(vpninfo->redirect_url, "://")) {
755                         vpn_progress(vpninfo, PRG_ERR,
756                                      _("Cannot follow redirection to non-https URL '%s'\n"),
757                                      vpninfo->redirect_url);
758                         free(vpninfo->redirect_url);
759                         vpninfo->redirect_url = NULL;
760                         free(form_buf);
761                         return -EINVAL;
762                 } else if (vpninfo->redirect_url[0] == '/') {
763                         /* Absolute redirect within same host */
764                         free(vpninfo->urlpath);
765                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
766                         free(vpninfo->redirect_url);
767                         vpninfo->redirect_url = NULL;
768                         goto retry;
769                 } else {
770                         char *lastslash = NULL;
771                         if (vpninfo->urlpath)
772                                 lastslash = strrchr(vpninfo->urlpath, '/');
773                         if (!lastslash) {
774                                 free(vpninfo->urlpath);
775                                 vpninfo->urlpath = vpninfo->redirect_url;
776                                 vpninfo->redirect_url = NULL;
777                         } else {
778                                 char *oldurl = vpninfo->urlpath;
779                                 *lastslash = 0;
780                                 vpninfo->urlpath = NULL;
781                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
782                                              oldurl, vpninfo->redirect_url) == -1) {
783                                         int err = -errno;
784                                         vpn_progress(vpninfo, PRG_ERR,
785                                                      _("Allocating new path for relative redirect failed: %s\n"),
786                                                      strerror(-err));
787                                         return err;
788                                 }
789                                 free(oldurl);
790                                 free(vpninfo->redirect_url);
791                                 vpninfo->redirect_url = NULL;
792                         }
793                         goto retry;
794                 }
795         }
796         if (!form_buf || result != 200) {
797                 vpn_progress(vpninfo, PRG_ERR,
798                              _("Unexpected %d result from server\n"),
799                              result);
800                 free(form_buf);
801                 return -EINVAL;
802         }
803         if (vpninfo->csd_stuburl) {
804                 /* This is the CSD stub script, which we now need to run */
805                 result = run_csd_script(vpninfo, form_buf, buflen);
806                 if (result) {
807                         free(form_buf);
808                         return result;
809                 }
810
811                 /* Now we'll be redirected to the waiturl */
812                 goto retry;
813         }
814         if (strncmp(form_buf, "<?xml", 5)) {
815                 /* Not XML? Perhaps it's HTML with a refresh... */
816                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
817                         vpn_progress(vpninfo, PRG_INFO,
818                                      _("Refreshing %s after 1 second...\n"),
819                                      vpninfo->urlpath);
820                         sleep(1);
821                         goto retry;
822                 }
823                 vpn_progress(vpninfo, PRG_ERR,
824                              _("Unknown response from server\n"));
825                 free(form_buf);
826                 return -EINVAL;
827         }
828         request_body[0] = 0;
829         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
830                                     &method, &request_body_type);
831
832         if (!result)
833                 goto redirect;
834
835         free(form_buf);
836
837         if (result != 2)
838                 return result;
839
840         /* A return value of 2 means the XML form indicated
841            success. We _should_ have a cookie... */
842
843         for (opt = vpninfo->cookies; opt; opt = opt->next) {
844
845                 if (!strcmp(opt->option, "webvpn"))
846                         vpninfo->cookie = opt->value;
847                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
848                         char *tok = opt->value;
849                         char *bu = NULL, *fu = NULL, *sha = NULL;
850
851                         do {
852                                 if (tok != opt->value)
853                                         *(tok++) = 0;
854
855                                 if (!strncmp(tok, "bu:", 3))
856                                         bu = tok + 3;
857                                 else if (!strncmp(tok, "fu:", 3))
858                                         fu = tok + 3;
859                                 else if (!strncmp(tok, "fh:", 3)) {
860                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
861                                                          SHA_DIGEST_LENGTH * 2))
862                                                 break;
863                                         sha = tok + 3;
864                                 }
865                         } while ((tok = strchr(tok, '&')));
866
867                         if (bu && fu && sha)
868                                 fetch_config(vpninfo, bu, fu, sha);
869                 }
870         }
871         if (vpninfo->csd_scriptname) {
872                 unlink(vpninfo->csd_scriptname);
873                 free(vpninfo->csd_scriptname);
874                 vpninfo->csd_scriptname = NULL;
875         }
876         return 0;
877 }
878
879 char *openconnect_create_useragent(const char *base)
880 {
881         char *uagent;
882
883         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
884                 return NULL;
885
886         return uagent;
887 }
888
889 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
890                       char *buf, size_t len)
891 {
892         int i = 0;
893         int ret;
894
895         if (len < 2)
896                 return -EINVAL;
897
898         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
899                 if (buf[i] == '\n') {
900                         buf[i] = 0;
901                         if (i && buf[i-1] == '\r') {
902                                 buf[i-1] = 0;
903                                 i--;
904                         }
905                         return i;
906                 }
907                 i++;
908
909                 if (i >= len - 1) {
910                         buf[i] = 0;
911                         return i;
912                 }
913         }
914         buf[i] = 0;
915         return i ?: ret;
916 }
917
918 static int proxy_write(struct openconnect_info *vpninfo, int fd,
919                        unsigned char *buf, size_t len)
920 {
921         size_t count;
922
923         for (count = 0; count < len; ) {
924                 fd_set rd_set, wr_set;
925                 int maxfd = fd;
926                 int i;
927
928                 FD_ZERO(&wr_set);
929                 FD_ZERO(&rd_set);
930                 FD_SET(fd, &wr_set);
931                 if (vpninfo->cancel_fd != -1) {
932                         FD_SET(vpninfo->cancel_fd, &rd_set);
933                         if (vpninfo->cancel_fd > fd)
934                                 maxfd = vpninfo->cancel_fd;
935                 }
936
937                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
938                 if (vpninfo->cancel_fd != -1 &&
939                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
940                         return -EINTR;
941
942                 /* Not that this should ever be able to happen... */
943                 if (!FD_ISSET(fd, &wr_set))
944                         continue;
945
946                 i = write(fd, buf + count, len - count);
947                 if (i < 0)
948                         return -errno;
949
950                 count += i;
951         }
952         return 0;
953 }
954
955 static int proxy_read(struct openconnect_info *vpninfo, int fd,
956                       unsigned char *buf, size_t len)
957 {
958         size_t count;
959
960         for (count = 0; count < len; ) {
961                 fd_set rd_set;
962                 int maxfd = fd;
963                 int i;
964
965                 FD_ZERO(&rd_set);
966                 FD_SET(fd, &rd_set);
967                 if (vpninfo->cancel_fd != -1) {
968                         FD_SET(vpninfo->cancel_fd, &rd_set);
969                         if (vpninfo->cancel_fd > fd)
970                                 maxfd = vpninfo->cancel_fd;
971                 }
972
973                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
974                 if (vpninfo->cancel_fd != -1 &&
975                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
976                         return -EINTR;
977
978                 /* Not that this should ever be able to happen... */
979                 if (!FD_ISSET(fd, &rd_set))
980                         continue;
981
982                 i = read(fd, buf + count, len - count);
983                 if (i < 0)
984                         return -errno;
985
986                 count += i;
987         }
988         return 0;
989 }
990
991 static const char *socks_errors[] = {
992         N_("request granted"),
993         N_("general failure"),
994         N_("connection not allowed by ruleset"),
995         N_("network unreachable"),
996         N_("host unreachable"),
997         N_("connection refused by destination host"),
998         N_("TTL expired"),
999         N_("command not supported / protocol error"),
1000         N_("address type not supported")
1001 };
1002
1003 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1004 {
1005         unsigned char buf[1024];
1006         int i;
1007
1008         buf[0] = 5; /* SOCKS version */
1009         buf[1] = 1; /* # auth methods */
1010         buf[2] = 0; /* No auth supported */
1011
1012         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1013                 vpn_progress(vpninfo, PRG_ERR,
1014                              _("Error writing auth request to SOCKS proxy: %s\n"),
1015                              strerror(-i));
1016                 return i;
1017         }
1018         
1019         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1020                 vpn_progress(vpninfo, PRG_ERR,
1021                              _("Error reading auth response from SOCKS proxy: %s\n"),
1022                              strerror(-i));
1023                 return i;
1024         }
1025         if (buf[0] != 5) {
1026                 vpn_progress(vpninfo, PRG_ERR,
1027                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1028                              buf[0], buf[1]);
1029                 return -EIO;
1030         }
1031         if (buf[1]) {
1032         socks_err:
1033                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1034                         vpn_progress(vpninfo, PRG_ERR,
1035                                      _("SOCKS proxy error %02x: %s\n"),
1036                                      buf[1], _(socks_errors[buf[1]]));
1037                 else
1038                         vpn_progress(vpninfo, PRG_ERR,
1039                                      _("SOCKS proxy error %02x\n"),
1040                                      buf[1]);
1041                 return -EIO;
1042         }
1043
1044         vpn_progress(vpninfo, PRG_INFO,
1045                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1046                      vpninfo->hostname, vpninfo->port);
1047
1048         buf[0] = 5; /* SOCKS version */
1049         buf[1] = 1; /* CONNECT */
1050         buf[2] = 0; /* Reserved */
1051         buf[3] = 3; /* Address type is domain name */
1052         buf[4] = strlen(vpninfo->hostname);
1053         strcpy((char *)buf + 5, vpninfo->hostname);
1054         i = strlen(vpninfo->hostname) + 5;
1055         buf[i++] = vpninfo->port >> 8;
1056         buf[i++] = vpninfo->port & 0xff;
1057
1058         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1059                 vpn_progress(vpninfo, PRG_ERR,
1060                              _("Error writing connect request to SOCKS proxy: %s\n"),
1061                              strerror(-i));
1062                 return i;
1063         }
1064         /* Read 5 bytes -- up to and including the first byte of the returned
1065            address (which might be the length byte of a domain name) */
1066         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1067                 vpn_progress(vpninfo, PRG_ERR,
1068                              _("Error reading connect response from SOCKS proxy: %s\n"),
1069                              strerror(-i));
1070                 return i;
1071         }
1072         if (buf[0] != 5) {
1073                 vpn_progress(vpninfo, PRG_ERR,
1074                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1075                              buf[0], buf[1]);
1076                 return -EIO;
1077         }
1078         if (buf[1])
1079                 goto socks_err;
1080
1081         /* Connect responses contain an address */
1082         switch(buf[3]) {
1083         case 1: /* Legacy IP */
1084                 i = 5;
1085                 break;
1086         case 3: /* Domain name */
1087                 i = buf[4] + 2;
1088                 break;
1089         case 4: /* IPv6 */
1090                 i = 17;
1091                 break;
1092         default:
1093                 vpn_progress(vpninfo, PRG_ERR,
1094                              _("Unexpected address type %02x in SOCKS connect response\n"),
1095                              buf[3]);
1096                 return -EIO;
1097         }
1098
1099         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1100                 vpn_progress(vpninfo, PRG_ERR,
1101                              _("Error reading connect response from SOCKS proxy: %s\n"),
1102                              strerror(-i));
1103                 return i;
1104         }
1105         return 0;
1106 }
1107
1108 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1109 {
1110         char buf[MAX_BUF_LEN];
1111         int buflen, result;
1112
1113         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1114         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1115         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1116         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1117         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1118         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1119         sprintf(buf + strlen(buf), "\r\n");
1120
1121         vpn_progress(vpninfo, PRG_INFO,
1122                      _("Requesting HTTP proxy connection to %s:%d\n"),
1123                      vpninfo->hostname, vpninfo->port);
1124
1125         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1126         if (result) {
1127                 vpn_progress(vpninfo, PRG_ERR,
1128                              _("Sending proxy request failed: %s\n"),
1129                              strerror(-result));
1130                 return result;
1131         }
1132
1133         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1134                 vpn_progress(vpninfo, PRG_ERR,
1135                              _("Error fetching proxy response\n"));
1136                 return -EIO;
1137         }
1138
1139         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1140             buf[8] != ' ' || !(result = atoi(buf+9))) {
1141                 vpn_progress(vpninfo, PRG_ERR,
1142                              _("Failed to parse proxy response '%s'\n"), buf);
1143                 return -EINVAL;
1144         }
1145
1146         if (result != 200) {
1147                 vpn_progress(vpninfo, PRG_ERR,
1148                              _("Proxy CONNECT request failed: %s\n"), buf);
1149                 return -EIO;
1150         }
1151
1152         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1153                 if (buflen < 0) {
1154                         vpn_progress(vpninfo, PRG_ERR,
1155                                      _("Failed to read proxy response\n"));
1156                         return -EIO;
1157                 }
1158                 vpn_progress(vpninfo, PRG_ERR,
1159                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1160                              buf);
1161         }
1162
1163         return 0;
1164 }
1165
1166 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1167 {
1168         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1169                 return process_http_proxy(vpninfo, ssl_sock);
1170         
1171         if (!strcmp(vpninfo->proxy_type, "socks") ||
1172             !strcmp(vpninfo->proxy_type, "socks5"))
1173                 return process_socks_proxy(vpninfo, ssl_sock);
1174
1175         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1176                      vpninfo->proxy_type);
1177         return -EIO;
1178 }
1179
1180 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1181 {
1182         char *url = proxy;
1183         int ret;
1184
1185         if (!url)
1186                 return -ENOMEM;
1187
1188         free(vpninfo->proxy_type);
1189         vpninfo->proxy_type = NULL;
1190         free(vpninfo->proxy);
1191         vpninfo->proxy = NULL;
1192
1193         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1194                                  &vpninfo->proxy_port, NULL, 80);
1195         if (ret)
1196                 goto out;
1197
1198         if (vpninfo->proxy_type &&
1199             strcmp(vpninfo->proxy_type, "http") &&
1200             strcmp(vpninfo->proxy_type, "socks") &&
1201             strcmp(vpninfo->proxy_type, "socks5")) {
1202                 vpn_progress(vpninfo, PRG_ERR,
1203                              _("Only http or socks(5) proxies supported\n"));
1204                 free(vpninfo->proxy_type);
1205                 vpninfo->proxy_type = NULL;
1206                 free(vpninfo->proxy);
1207                 vpninfo->proxy = NULL;
1208                 return -EINVAL;
1209         }
1210  out:
1211         free(url);
1212         return ret;
1213 }