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