http: Create add_common_headers() to simplify HTTP request code
[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 }
443
444 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
445                         char *server_sha1)
446 {
447         struct oc_text_buf *buf;
448         char *config_buf = NULL;
449         int result, buflen;
450         unsigned char local_sha1_bin[SHA1_SIZE];
451         char local_sha1_ascii[(SHA1_SIZE * 2)+1];
452         int i;
453
454         if (openconnect_open_https(vpninfo)) {
455                 vpn_progress(vpninfo, PRG_ERR,
456                              _("Failed to open HTTPS connection to %s\n"),
457                              vpninfo->hostname);
458                 return -EINVAL;
459         }
460
461         buf = buf_alloc();
462         buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
463         add_common_headers(vpninfo, buf);
464         buf_append(buf, "\r\n");
465
466         if (buf_error(buf))
467                 return buf_free(buf);
468
469         if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
470                 vpn_progress(vpninfo, PRG_ERR,
471                              _("Failed to send GET request for new config\n"));
472                 buf_free(buf);
473                 return -EIO;
474         }
475         buf_free(buf);
476
477         buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
478         if (buflen < 0) {
479                 /* We'll already have complained about whatever offended us */
480                 return -EINVAL;
481         }
482
483         if (result != 200) {
484                 free(config_buf);
485                 return -EINVAL;
486         }
487
488         openconnect_sha1(local_sha1_bin, config_buf, buflen);
489
490         for (i = 0; i < SHA1_SIZE; i++)
491                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
492
493         if (strcasecmp(server_sha1, local_sha1_ascii)) {
494                 vpn_progress(vpninfo, PRG_ERR,
495                              _("Downloaded config file did not match intended SHA1\n"));
496                 free(config_buf);
497                 return -EINVAL;
498         }
499
500         result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
501         free(config_buf);
502         return result;
503 }
504
505 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
506 {
507         char fname[16];
508         int fd, ret;
509
510         if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
511                 vpn_progress(vpninfo, PRG_ERR,
512                              _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
513                                "This facility is disabled by default for security reasons, so you may wish to enable it."));
514                 return -EPERM;
515         }
516
517 #ifndef __linux__
518         vpn_progress(vpninfo, PRG_INFO,
519                      _("Trying to run Linux CSD trojan script."));
520 #endif
521
522         sprintf(fname, "/tmp/csdXXXXXX");
523         fd = mkstemp(fname);
524         if (fd < 0) {
525                 int err = -errno;
526                 vpn_progress(vpninfo, PRG_ERR,
527                              _("Failed to open temporary CSD script file: %s\n"),
528                              strerror(errno));
529                 return err;
530         }
531
532         ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
533         if (ret) {
534                 vpn_progress(vpninfo, PRG_ERR,
535                              _("Failed to write temporary CSD script file: %s\n"),
536                              strerror(-ret));
537                 return ret;
538         }
539         fchmod(fd, 0755);
540         close(fd);
541
542         if (!fork()) {
543                 char scertbuf[MD5_SIZE * 2 + 1];
544                 char ccertbuf[MD5_SIZE * 2 + 1];
545                 char *csd_argv[32];
546                 int i = 0;
547
548                 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
549                         struct passwd *pw;
550
551                         if (setuid(vpninfo->uid_csd)) {
552                                 fprintf(stderr, _("Failed to set uid %ld\n"),
553                                         (long)vpninfo->uid_csd);
554                                 exit(1);
555                         }
556                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
557                                 fprintf(stderr, _("Invalid user uid=%ld\n"),
558                                         (long)vpninfo->uid_csd);
559                                 exit(1);
560                         }
561                         setenv("HOME", pw->pw_dir, 1);
562                         if (chdir(pw->pw_dir)) {
563                                 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
564                                         pw->pw_dir, strerror(errno));
565                                 exit(1);
566                         }
567                 }
568                 if (getuid() == 0 && !vpninfo->csd_wrapper) {
569                         fprintf(stderr, _("Warning: you are running insecure "
570                                           "CSD code with root privileges\n"
571                                           "\t Use command line option \"--csd-user\"\n"));
572                 }
573                 if (vpninfo->uid_csd_given == 2) {
574                         /* The NM tool really needs not to get spurious output
575                            on stdout, which the CSD trojan spews. */
576                         dup2(2, 1);
577                 }
578                 if (vpninfo->csd_wrapper)
579                         csd_argv[i++] = vpninfo->csd_wrapper;
580                 csd_argv[i++] = fname;
581                 csd_argv[i++]= (char *)"-ticket";
582                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
583                         return -ENOMEM;
584                 csd_argv[i++]= (char *)"-stub";
585                 csd_argv[i++]= (char *)"\"0\"";
586                 csd_argv[i++]= (char *)"-group";
587                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
588                         return -ENOMEM;
589
590                 openconnect_local_cert_md5(vpninfo, ccertbuf);
591                 scertbuf[0] = 0;
592                 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
593                 csd_argv[i++]= (char *)"-certhash";
594                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
595                         return -ENOMEM;
596
597                 csd_argv[i++]= (char *)"-url";
598                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
599                         return -ENOMEM;
600
601                 csd_argv[i++]= (char *)"-langselen";
602                 csd_argv[i++] = NULL;
603
604                 execv(csd_argv[0], csd_argv);
605                 vpn_progress(vpninfo, PRG_ERR,
606                              _("Failed to exec CSD script %s\n"), csd_argv[0]);
607                 exit(1);
608         }
609
610         free(vpninfo->csd_stuburl);
611         vpninfo->csd_stuburl = NULL;
612         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
613                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
614         vpninfo->csd_waiturl = NULL;
615         vpninfo->csd_scriptname = strdup(fname);
616
617         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
618
619         return 0;
620 }
621
622 int internal_parse_url(char *url, char **res_proto, char **res_host,
623                        int *res_port, char **res_path, int default_port)
624 {
625         char *proto = url;
626         char *host, *path, *port_str;
627         int port;
628
629         host = strstr(url, "://");
630         if (host) {
631                 *host = 0;
632                 host += 3;
633
634                 if (!strcasecmp(proto, "https"))
635                         port = 443;
636                 else if (!strcasecmp(proto, "http"))
637                         port = 80;
638                 else if (!strcasecmp(proto, "socks") ||
639                          !strcasecmp(proto, "socks4") ||
640                          !strcasecmp(proto, "socks5"))
641                         port = 1080;
642                 else
643                         return -EPROTONOSUPPORT;
644         } else {
645                 if (default_port) {
646                         proto = NULL;
647                         port = default_port;
648                         host = url;
649                 } else
650                         return -EINVAL;
651         }
652
653         path = strchr(host, '/');
654         if (path)
655                 *(path++) = 0;
656
657         port_str = strrchr(host, ':');
658         if (port_str) {
659                 char *end;
660                 int new_port = strtol(port_str + 1, &end, 10);
661
662                 if (!*end) {
663                         *port_str = 0;
664                         port = new_port;
665                 }
666         }
667
668         if (res_proto)
669                 *res_proto = proto ? strdup(proto) : NULL;
670         if (res_host)
671                 *res_host = strdup(host);
672         if (res_port)
673                 *res_port = port;
674         if (res_path)
675                 *res_path = (path && *path) ? strdup(path) : NULL;
676
677         /* Undo the damage we did to the original string */
678         if (port_str)
679                 *(port_str) = ':';
680         if (path)
681                 *(path - 1) = '/';
682         if (proto)
683                 *(host - 3) = ':';
684         return 0;
685 }
686
687 static void clear_cookies(struct openconnect_info *vpninfo)
688 {
689         struct vpn_option *opt, *next;
690
691         for (opt = vpninfo->cookies; opt; opt = next) {
692                 next = opt->next;
693
694                 free(opt->option);
695                 free(opt->value);
696                 free(opt);
697         }
698         vpninfo->cookies = NULL;
699 }
700
701 /* Return value:
702  *  < 0, on error
703  *  = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
704  */
705 static int handle_redirect(struct openconnect_info *vpninfo)
706 {
707         if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
708                 /* New host. Tear down the existing connection and make a new one */
709                 char *host;
710                 int port;
711                 int ret;
712
713                 free(vpninfo->urlpath);
714                 vpninfo->urlpath = NULL;
715
716                 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
717                 if (ret) {
718                         vpn_progress(vpninfo, PRG_ERR,
719                                      _("Failed to parse redirected URL '%s': %s\n"),
720                                      vpninfo->redirect_url, strerror(-ret));
721                         free(vpninfo->redirect_url);
722                         vpninfo->redirect_url = NULL;
723                         return ret;
724                 }
725
726                 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
727                         free(vpninfo->hostname);
728                         vpninfo->hostname = host;
729                         vpninfo->port = port;
730
731                         /* Kill the existing connection, and a new one will happen */
732                         free(vpninfo->peer_addr);
733                         vpninfo->peer_addr = NULL;
734                         openconnect_close_https(vpninfo, 0);
735                         clear_cookies(vpninfo);
736                 } else
737                         free(host);
738
739                 free(vpninfo->redirect_url);
740                 vpninfo->redirect_url = NULL;
741
742                 return 0;
743         } else if (strstr(vpninfo->redirect_url, "://")) {
744                 vpn_progress(vpninfo, PRG_ERR,
745                              _("Cannot follow redirection to non-https URL '%s'\n"),
746                              vpninfo->redirect_url);
747                 free(vpninfo->redirect_url);
748                 vpninfo->redirect_url = NULL;
749                 return -EINVAL;
750         } else if (vpninfo->redirect_url[0] == '/') {
751                 /* Absolute redirect within same host */
752                 free(vpninfo->urlpath);
753                 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
754                 free(vpninfo->redirect_url);
755                 vpninfo->redirect_url = NULL;
756                 return 0;
757         } else {
758                 char *lastslash = NULL;
759                 if (vpninfo->urlpath)
760                         lastslash = strrchr(vpninfo->urlpath, '/');
761                 if (!lastslash) {
762                         free(vpninfo->urlpath);
763                         vpninfo->urlpath = vpninfo->redirect_url;
764                         vpninfo->redirect_url = NULL;
765                 } else {
766                         char *oldurl = vpninfo->urlpath;
767                         *lastslash = 0;
768                         vpninfo->urlpath = NULL;
769                         if (asprintf(&vpninfo->urlpath, "%s/%s",
770                                      oldurl, vpninfo->redirect_url) == -1) {
771                                 int err = -errno;
772                                 vpn_progress(vpninfo, PRG_ERR,
773                                              _("Allocating new path for relative redirect failed: %s\n"),
774                                              strerror(-err));
775                                 return err;
776                         }
777                         free(oldurl);
778                         free(vpninfo->redirect_url);
779                         vpninfo->redirect_url = NULL;
780                 }
781                 return 0;
782         }
783 }
784
785 /* Return value:
786  *  < 0, on error
787  *  > 0, no cookie (user cancel)
788  *  = 0, obtained cookie
789  */
790 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
791 {
792         struct vpn_option *opt;
793         struct oc_text_buf *buf;
794         char *form_buf = NULL;
795         int result, buflen;
796         char request_body[2048];
797         const char *request_body_type = NULL;
798         const char *method = "GET";
799
800         if (vpninfo->use_stoken) {
801                 result = prepare_stoken(vpninfo);
802                 if (result)
803                         return result;
804         }
805
806  retry:
807         if (form_buf) {
808                 free(form_buf);
809                 form_buf = NULL;
810         }
811         if (openconnect_open_https(vpninfo)) {
812                 vpn_progress(vpninfo, PRG_ERR,
813                              _("Failed to open HTTPS connection to %s\n"),
814                              vpninfo->hostname);
815                 return -EINVAL;
816         }
817
818         /*
819          * It would be nice to use cURL for this, but we really need to guarantee
820          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
821          * to have any way to let us provide our own socket read/write functions.
822          * We can only provide a socket _open_ function. Which would require having
823          * a socketpair() and servicing the "other" end of it.
824          *
825          * So we process the HTTP for ourselves...
826          */
827         buf = buf_alloc();
828         buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
829         add_common_headers(vpninfo, buf);
830
831         if (request_body_type) {
832                 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
833                 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
834         }
835         buf_append(buf, "\r\n");
836
837         if (request_body_type)
838                 buf_append(buf, "%s", request_body);
839
840         if (vpninfo->port == 443)
841                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
842                              method, vpninfo->hostname,
843                              vpninfo->urlpath ?: "");
844         else
845                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
846                              method, vpninfo->hostname, vpninfo->port,
847                              vpninfo->urlpath ?: "");
848
849         if (buf_error(buf))
850                 return buf_free(buf);
851
852         result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
853         buf_free(buf);
854         if (result < 0)
855                 return result;
856
857         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
858         if (buflen < 0) {
859                 /* We'll already have complained about whatever offended us */
860                 return buflen;
861         }
862
863         if (result != 200 && vpninfo->redirect_url) {
864         redirect:
865                 result = handle_redirect(vpninfo);
866                 if (result == 0)
867                         goto retry;
868                 free(form_buf);
869                 return result;
870         }
871         if (!form_buf || result != 200) {
872                 vpn_progress(vpninfo, PRG_ERR,
873                              _("Unexpected %d result from server\n"),
874                              result);
875                 free(form_buf);
876                 return -EINVAL;
877         }
878         if (vpninfo->csd_stuburl) {
879                 /* This is the CSD stub script, which we now need to run */
880                 result = run_csd_script(vpninfo, form_buf, buflen);
881                 if (result) {
882                         free(form_buf);
883                         return result;
884                 }
885
886                 /* Now we'll be redirected to the waiturl */
887                 goto retry;
888         }
889         if (strncmp(form_buf, "<?xml", 5)) {
890                 /* Not XML? Perhaps it's HTML with a refresh... */
891                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
892                         vpn_progress(vpninfo, PRG_INFO,
893                                      _("Refreshing %s after 1 second...\n"),
894                                      vpninfo->urlpath);
895                         sleep(1);
896                         goto retry;
897                 }
898                 vpn_progress(vpninfo, PRG_ERR,
899                              _("Unknown response from server\n"));
900                 free(form_buf);
901                 return -EINVAL;
902         }
903         request_body[0] = 0;
904         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
905                                     &method, &request_body_type);
906
907         if (!result)
908                 goto redirect;
909
910         free(form_buf);
911
912         if (result != 2)
913                 return result;
914
915         /* A return value of 2 means the XML form indicated
916            success. We _should_ have a cookie... */
917
918         for (opt = vpninfo->cookies; opt; opt = opt->next) {
919
920                 if (!strcmp(opt->option, "webvpn"))
921                         vpninfo->cookie = opt->value;
922                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
923                         char *tok = opt->value;
924                         char *bu = NULL, *fu = NULL, *sha = NULL;
925
926                         do {
927                                 if (tok != opt->value)
928                                         *(tok++) = 0;
929
930                                 if (!strncmp(tok, "bu:", 3))
931                                         bu = tok + 3;
932                                 else if (!strncmp(tok, "fu:", 3))
933                                         fu = tok + 3;
934                                 else if (!strncmp(tok, "fh:", 3)) {
935                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
936                                                          SHA1_SIZE * 2))
937                                                 break;
938                                         sha = tok + 3;
939                                 }
940                         } while ((tok = strchr(tok, '&')));
941
942                         if (bu && fu && sha)
943                                 fetch_config(vpninfo, bu, fu, sha);
944                 }
945         }
946         if (vpninfo->csd_scriptname) {
947                 unlink(vpninfo->csd_scriptname);
948                 free(vpninfo->csd_scriptname);
949                 vpninfo->csd_scriptname = NULL;
950         }
951         return 0;
952 }
953
954 char *openconnect_create_useragent(const char *base)
955 {
956         char *uagent;
957
958         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
959                 return NULL;
960
961         return uagent;
962 }
963
964 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
965                       char *buf, size_t len)
966 {
967         int i = 0;
968         int ret;
969
970         if (len < 2)
971                 return -EINVAL;
972
973         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
974                 if (buf[i] == '\n') {
975                         buf[i] = 0;
976                         if (i && buf[i-1] == '\r') {
977                                 buf[i-1] = 0;
978                                 i--;
979                         }
980                         return i;
981                 }
982                 i++;
983
984                 if (i >= len - 1) {
985                         buf[i] = 0;
986                         return i;
987                 }
988         }
989         buf[i] = 0;
990         return i ?: ret;
991 }
992
993 static int proxy_write(struct openconnect_info *vpninfo, int fd,
994                        unsigned char *buf, size_t len)
995 {
996         size_t count;
997
998         for (count = 0; count < len; ) {
999                 fd_set rd_set, wr_set;
1000                 int maxfd = fd;
1001                 int i;
1002
1003                 FD_ZERO(&wr_set);
1004                 FD_ZERO(&rd_set);
1005                 FD_SET(fd, &wr_set);
1006                 if (vpninfo->cancel_fd != -1) {
1007                         FD_SET(vpninfo->cancel_fd, &rd_set);
1008                         if (vpninfo->cancel_fd > fd)
1009                                 maxfd = vpninfo->cancel_fd;
1010                 }
1011
1012                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1013                 if (vpninfo->cancel_fd != -1 &&
1014                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1015                         return -EINTR;
1016
1017                 /* Not that this should ever be able to happen... */
1018                 if (!FD_ISSET(fd, &wr_set))
1019                         continue;
1020
1021                 i = write(fd, buf + count, len - count);
1022                 if (i < 0)
1023                         return -errno;
1024
1025                 count += i;
1026         }
1027         return 0;
1028 }
1029
1030 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1031                       unsigned char *buf, size_t len)
1032 {
1033         size_t count;
1034
1035         for (count = 0; count < len; ) {
1036                 fd_set rd_set;
1037                 int maxfd = fd;
1038                 int i;
1039
1040                 FD_ZERO(&rd_set);
1041                 FD_SET(fd, &rd_set);
1042                 if (vpninfo->cancel_fd != -1) {
1043                         FD_SET(vpninfo->cancel_fd, &rd_set);
1044                         if (vpninfo->cancel_fd > fd)
1045                                 maxfd = vpninfo->cancel_fd;
1046                 }
1047
1048                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1049                 if (vpninfo->cancel_fd != -1 &&
1050                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1051                         return -EINTR;
1052
1053                 /* Not that this should ever be able to happen... */
1054                 if (!FD_ISSET(fd, &rd_set))
1055                         continue;
1056
1057                 i = read(fd, buf + count, len - count);
1058                 if (i < 0)
1059                         return -errno;
1060
1061                 count += i;
1062         }
1063         return 0;
1064 }
1065
1066 static const char *socks_errors[] = {
1067         N_("request granted"),
1068         N_("general failure"),
1069         N_("connection not allowed by ruleset"),
1070         N_("network unreachable"),
1071         N_("host unreachable"),
1072         N_("connection refused by destination host"),
1073         N_("TTL expired"),
1074         N_("command not supported / protocol error"),
1075         N_("address type not supported")
1076 };
1077
1078 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1079 {
1080         unsigned char buf[1024];
1081         int i;
1082
1083         buf[0] = 5; /* SOCKS version */
1084         buf[1] = 1; /* # auth methods */
1085         buf[2] = 0; /* No auth supported */
1086
1087         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1088                 vpn_progress(vpninfo, PRG_ERR,
1089                              _("Error writing auth request to SOCKS proxy: %s\n"),
1090                              strerror(-i));
1091                 return i;
1092         }
1093         
1094         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1095                 vpn_progress(vpninfo, PRG_ERR,
1096                              _("Error reading auth response from SOCKS proxy: %s\n"),
1097                              strerror(-i));
1098                 return i;
1099         }
1100         if (buf[0] != 5) {
1101                 vpn_progress(vpninfo, PRG_ERR,
1102                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1103                              buf[0], buf[1]);
1104                 return -EIO;
1105         }
1106         if (buf[1]) {
1107         socks_err:
1108                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1109                         vpn_progress(vpninfo, PRG_ERR,
1110                                      _("SOCKS proxy error %02x: %s\n"),
1111                                      buf[1], _(socks_errors[buf[1]]));
1112                 else
1113                         vpn_progress(vpninfo, PRG_ERR,
1114                                      _("SOCKS proxy error %02x\n"),
1115                                      buf[1]);
1116                 return -EIO;
1117         }
1118
1119         vpn_progress(vpninfo, PRG_INFO,
1120                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1121                      vpninfo->hostname, vpninfo->port);
1122
1123         buf[0] = 5; /* SOCKS version */
1124         buf[1] = 1; /* CONNECT */
1125         buf[2] = 0; /* Reserved */
1126         buf[3] = 3; /* Address type is domain name */
1127         buf[4] = strlen(vpninfo->hostname);
1128         strcpy((char *)buf + 5, vpninfo->hostname);
1129         i = strlen(vpninfo->hostname) + 5;
1130         buf[i++] = vpninfo->port >> 8;
1131         buf[i++] = vpninfo->port & 0xff;
1132
1133         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1134                 vpn_progress(vpninfo, PRG_ERR,
1135                              _("Error writing connect request to SOCKS proxy: %s\n"),
1136                              strerror(-i));
1137                 return i;
1138         }
1139         /* Read 5 bytes -- up to and including the first byte of the returned
1140            address (which might be the length byte of a domain name) */
1141         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1142                 vpn_progress(vpninfo, PRG_ERR,
1143                              _("Error reading connect response from SOCKS proxy: %s\n"),
1144                              strerror(-i));
1145                 return i;
1146         }
1147         if (buf[0] != 5) {
1148                 vpn_progress(vpninfo, PRG_ERR,
1149                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1150                              buf[0], buf[1]);
1151                 return -EIO;
1152         }
1153         if (buf[1])
1154                 goto socks_err;
1155
1156         /* Connect responses contain an address */
1157         switch(buf[3]) {
1158         case 1: /* Legacy IP */
1159                 i = 5;
1160                 break;
1161         case 3: /* Domain name */
1162                 i = buf[4] + 2;
1163                 break;
1164         case 4: /* IPv6 */
1165                 i = 17;
1166                 break;
1167         default:
1168                 vpn_progress(vpninfo, PRG_ERR,
1169                              _("Unexpected address type %02x in SOCKS connect response\n"),
1170                              buf[3]);
1171                 return -EIO;
1172         }
1173
1174         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1175                 vpn_progress(vpninfo, PRG_ERR,
1176                              _("Error reading connect response from SOCKS proxy: %s\n"),
1177                              strerror(-i));
1178                 return i;
1179         }
1180         return 0;
1181 }
1182
1183 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1184 {
1185         char buf[MAX_BUF_LEN];
1186         struct oc_text_buf *reqbuf;
1187         int buflen, result;
1188
1189         reqbuf = buf_alloc();
1190         buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1191         buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1192         buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1193         buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1194         buf_append(reqbuf, "Connection: keep-alive\r\n");
1195         buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1196         buf_append(reqbuf, "\r\n");
1197
1198         if (buf_error(reqbuf))
1199                 return buf_free(reqbuf);
1200
1201         vpn_progress(vpninfo, PRG_INFO,
1202                      _("Requesting HTTP proxy connection to %s:%d\n"),
1203                      vpninfo->hostname, vpninfo->port);
1204
1205         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1206         buf_free(reqbuf);
1207
1208         if (result) {
1209                 vpn_progress(vpninfo, PRG_ERR,
1210                              _("Sending proxy request failed: %s\n"),
1211                              strerror(-result));
1212                 return result;
1213         }
1214
1215         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1216                 vpn_progress(vpninfo, PRG_ERR,
1217                              _("Error fetching proxy response\n"));
1218                 return -EIO;
1219         }
1220
1221         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1222             buf[8] != ' ' || !(result = atoi(buf+9))) {
1223                 vpn_progress(vpninfo, PRG_ERR,
1224                              _("Failed to parse proxy response '%s'\n"), buf);
1225                 return -EINVAL;
1226         }
1227
1228         if (result != 200) {
1229                 vpn_progress(vpninfo, PRG_ERR,
1230                              _("Proxy CONNECT request failed: %s\n"), buf);
1231                 return -EIO;
1232         }
1233
1234         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1235                 if (buflen < 0) {
1236                         vpn_progress(vpninfo, PRG_ERR,
1237                                      _("Failed to read proxy response\n"));
1238                         return -EIO;
1239                 }
1240                 vpn_progress(vpninfo, PRG_ERR,
1241                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1242                              buf);
1243         }
1244
1245         return 0;
1246 }
1247
1248 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1249 {
1250         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1251                 return process_http_proxy(vpninfo, ssl_sock);
1252         
1253         if (!strcmp(vpninfo->proxy_type, "socks") ||
1254             !strcmp(vpninfo->proxy_type, "socks5"))
1255                 return process_socks_proxy(vpninfo, ssl_sock);
1256
1257         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1258                      vpninfo->proxy_type);
1259         return -EIO;
1260 }
1261
1262 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1263 {
1264         char *url = proxy;
1265         int ret;
1266
1267         if (!url)
1268                 return -ENOMEM;
1269
1270         free(vpninfo->proxy_type);
1271         vpninfo->proxy_type = NULL;
1272         free(vpninfo->proxy);
1273         vpninfo->proxy = NULL;
1274
1275         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1276                                  &vpninfo->proxy_port, NULL, 80);
1277         if (ret)
1278                 goto out;
1279
1280         if (vpninfo->proxy_type &&
1281             strcmp(vpninfo->proxy_type, "http") &&
1282             strcmp(vpninfo->proxy_type, "socks") &&
1283             strcmp(vpninfo->proxy_type, "socks5")) {
1284                 vpn_progress(vpninfo, PRG_ERR,
1285                              _("Only http or socks(5) proxies supported\n"));
1286                 free(vpninfo->proxy_type);
1287                 vpninfo->proxy_type = NULL;
1288                 free(vpninfo->proxy);
1289                 vpninfo->proxy = NULL;
1290                 return -EINVAL;
1291         }
1292  out:
1293         free(url);
1294         return ret;
1295 }