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