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