library: Add call to change reported OS name
[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         struct oc_auth_form *form = NULL;
796         int result, buflen;
797         char request_body[2048];
798         const char *request_body_type = NULL;
799         const char *method = "GET";
800
801         if (vpninfo->use_stoken) {
802                 result = prepare_stoken(vpninfo);
803                 if (result)
804                         return result;
805         }
806
807  retry:
808         if (form_buf) {
809                 free(form_buf);
810                 form_buf = NULL;
811         }
812         if (openconnect_open_https(vpninfo)) {
813                 vpn_progress(vpninfo, PRG_ERR,
814                              _("Failed to open HTTPS connection to %s\n"),
815                              vpninfo->hostname);
816                 return -EINVAL;
817         }
818
819         /*
820          * It would be nice to use cURL for this, but we really need to guarantee
821          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
822          * to have any way to let us provide our own socket read/write functions.
823          * We can only provide a socket _open_ function. Which would require having
824          * a socketpair() and servicing the "other" end of it.
825          *
826          * So we process the HTTP for ourselves...
827          */
828         buf = buf_alloc();
829         buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
830         add_common_headers(vpninfo, buf);
831
832         if (request_body_type) {
833                 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
834                 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
835         }
836         buf_append(buf, "\r\n");
837
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         result = parse_xml_response(vpninfo, form_buf, &form);
905         if (result) {
906                 free(form_buf);
907                 return -ENOMEM;
908         }
909         request_body[0] = 0;
910         result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
911                                   &method, &request_body_type);
912         free_auth_form(form);
913
914         if (!result)
915                 goto redirect;
916
917         free(form_buf);
918
919         if (result != 2)
920                 return result;
921
922         /* A return value of 2 means the XML form indicated
923            success. We _should_ have a cookie... */
924
925         for (opt = vpninfo->cookies; opt; opt = opt->next) {
926
927                 if (!strcmp(opt->option, "webvpn"))
928                         vpninfo->cookie = opt->value;
929                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
930                         char *tok = opt->value;
931                         char *bu = NULL, *fu = NULL, *sha = NULL;
932
933                         do {
934                                 if (tok != opt->value)
935                                         *(tok++) = 0;
936
937                                 if (!strncmp(tok, "bu:", 3))
938                                         bu = tok + 3;
939                                 else if (!strncmp(tok, "fu:", 3))
940                                         fu = tok + 3;
941                                 else if (!strncmp(tok, "fh:", 3)) {
942                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
943                                                          SHA1_SIZE * 2))
944                                                 break;
945                                         sha = tok + 3;
946                                 }
947                         } while ((tok = strchr(tok, '&')));
948
949                         if (bu && fu && sha)
950                                 fetch_config(vpninfo, bu, fu, sha);
951                 }
952         }
953         if (vpninfo->csd_scriptname) {
954                 unlink(vpninfo->csd_scriptname);
955                 free(vpninfo->csd_scriptname);
956                 vpninfo->csd_scriptname = NULL;
957         }
958         return 0;
959 }
960
961 char *openconnect_create_useragent(const char *base)
962 {
963         char *uagent;
964
965         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
966                 return NULL;
967
968         return uagent;
969 }
970
971 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
972                       char *buf, size_t len)
973 {
974         int i = 0;
975         int ret;
976
977         if (len < 2)
978                 return -EINVAL;
979
980         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
981                 if (buf[i] == '\n') {
982                         buf[i] = 0;
983                         if (i && buf[i-1] == '\r') {
984                                 buf[i-1] = 0;
985                                 i--;
986                         }
987                         return i;
988                 }
989                 i++;
990
991                 if (i >= len - 1) {
992                         buf[i] = 0;
993                         return i;
994                 }
995         }
996         buf[i] = 0;
997         return i ?: ret;
998 }
999
1000 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1001                        unsigned char *buf, size_t len)
1002 {
1003         size_t count;
1004
1005         for (count = 0; count < len; ) {
1006                 fd_set rd_set, wr_set;
1007                 int maxfd = fd;
1008                 int i;
1009
1010                 FD_ZERO(&wr_set);
1011                 FD_ZERO(&rd_set);
1012                 FD_SET(fd, &wr_set);
1013                 if (vpninfo->cancel_fd != -1) {
1014                         FD_SET(vpninfo->cancel_fd, &rd_set);
1015                         if (vpninfo->cancel_fd > fd)
1016                                 maxfd = vpninfo->cancel_fd;
1017                 }
1018
1019                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1020                 if (vpninfo->cancel_fd != -1 &&
1021                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1022                         return -EINTR;
1023
1024                 /* Not that this should ever be able to happen... */
1025                 if (!FD_ISSET(fd, &wr_set))
1026                         continue;
1027
1028                 i = write(fd, buf + count, len - count);
1029                 if (i < 0)
1030                         return -errno;
1031
1032                 count += i;
1033         }
1034         return 0;
1035 }
1036
1037 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1038                       unsigned char *buf, size_t len)
1039 {
1040         size_t count;
1041
1042         for (count = 0; count < len; ) {
1043                 fd_set rd_set;
1044                 int maxfd = fd;
1045                 int i;
1046
1047                 FD_ZERO(&rd_set);
1048                 FD_SET(fd, &rd_set);
1049                 if (vpninfo->cancel_fd != -1) {
1050                         FD_SET(vpninfo->cancel_fd, &rd_set);
1051                         if (vpninfo->cancel_fd > fd)
1052                                 maxfd = vpninfo->cancel_fd;
1053                 }
1054
1055                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1056                 if (vpninfo->cancel_fd != -1 &&
1057                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1058                         return -EINTR;
1059
1060                 /* Not that this should ever be able to happen... */
1061                 if (!FD_ISSET(fd, &rd_set))
1062                         continue;
1063
1064                 i = read(fd, buf + count, len - count);
1065                 if (i < 0)
1066                         return -errno;
1067
1068                 count += i;
1069         }
1070         return 0;
1071 }
1072
1073 static const char *socks_errors[] = {
1074         N_("request granted"),
1075         N_("general failure"),
1076         N_("connection not allowed by ruleset"),
1077         N_("network unreachable"),
1078         N_("host unreachable"),
1079         N_("connection refused by destination host"),
1080         N_("TTL expired"),
1081         N_("command not supported / protocol error"),
1082         N_("address type not supported")
1083 };
1084
1085 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1086 {
1087         unsigned char buf[1024];
1088         int i;
1089
1090         buf[0] = 5; /* SOCKS version */
1091         buf[1] = 1; /* # auth methods */
1092         buf[2] = 0; /* No auth supported */
1093
1094         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1095                 vpn_progress(vpninfo, PRG_ERR,
1096                              _("Error writing auth request to SOCKS proxy: %s\n"),
1097                              strerror(-i));
1098                 return i;
1099         }
1100         
1101         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1102                 vpn_progress(vpninfo, PRG_ERR,
1103                              _("Error reading auth response from SOCKS proxy: %s\n"),
1104                              strerror(-i));
1105                 return i;
1106         }
1107         if (buf[0] != 5) {
1108                 vpn_progress(vpninfo, PRG_ERR,
1109                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1110                              buf[0], buf[1]);
1111                 return -EIO;
1112         }
1113         if (buf[1]) {
1114         socks_err:
1115                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1116                         vpn_progress(vpninfo, PRG_ERR,
1117                                      _("SOCKS proxy error %02x: %s\n"),
1118                                      buf[1], _(socks_errors[buf[1]]));
1119                 else
1120                         vpn_progress(vpninfo, PRG_ERR,
1121                                      _("SOCKS proxy error %02x\n"),
1122                                      buf[1]);
1123                 return -EIO;
1124         }
1125
1126         vpn_progress(vpninfo, PRG_INFO,
1127                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1128                      vpninfo->hostname, vpninfo->port);
1129
1130         buf[0] = 5; /* SOCKS version */
1131         buf[1] = 1; /* CONNECT */
1132         buf[2] = 0; /* Reserved */
1133         buf[3] = 3; /* Address type is domain name */
1134         buf[4] = strlen(vpninfo->hostname);
1135         strcpy((char *)buf + 5, vpninfo->hostname);
1136         i = strlen(vpninfo->hostname) + 5;
1137         buf[i++] = vpninfo->port >> 8;
1138         buf[i++] = vpninfo->port & 0xff;
1139
1140         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1141                 vpn_progress(vpninfo, PRG_ERR,
1142                              _("Error writing connect request to SOCKS proxy: %s\n"),
1143                              strerror(-i));
1144                 return i;
1145         }
1146         /* Read 5 bytes -- up to and including the first byte of the returned
1147            address (which might be the length byte of a domain name) */
1148         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1149                 vpn_progress(vpninfo, PRG_ERR,
1150                              _("Error reading connect response from SOCKS proxy: %s\n"),
1151                              strerror(-i));
1152                 return i;
1153         }
1154         if (buf[0] != 5) {
1155                 vpn_progress(vpninfo, PRG_ERR,
1156                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1157                              buf[0], buf[1]);
1158                 return -EIO;
1159         }
1160         if (buf[1])
1161                 goto socks_err;
1162
1163         /* Connect responses contain an address */
1164         switch(buf[3]) {
1165         case 1: /* Legacy IP */
1166                 i = 5;
1167                 break;
1168         case 3: /* Domain name */
1169                 i = buf[4] + 2;
1170                 break;
1171         case 4: /* IPv6 */
1172                 i = 17;
1173                 break;
1174         default:
1175                 vpn_progress(vpninfo, PRG_ERR,
1176                              _("Unexpected address type %02x in SOCKS connect response\n"),
1177                              buf[3]);
1178                 return -EIO;
1179         }
1180
1181         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1182                 vpn_progress(vpninfo, PRG_ERR,
1183                              _("Error reading connect response from SOCKS proxy: %s\n"),
1184                              strerror(-i));
1185                 return i;
1186         }
1187         return 0;
1188 }
1189
1190 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1191 {
1192         char buf[MAX_BUF_LEN];
1193         struct oc_text_buf *reqbuf;
1194         int buflen, result;
1195
1196         reqbuf = buf_alloc();
1197         buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1198         buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1199         buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1200         buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1201         buf_append(reqbuf, "Connection: keep-alive\r\n");
1202         buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1203         buf_append(reqbuf, "\r\n");
1204
1205         if (buf_error(reqbuf))
1206                 return buf_free(reqbuf);
1207
1208         vpn_progress(vpninfo, PRG_INFO,
1209                      _("Requesting HTTP proxy connection to %s:%d\n"),
1210                      vpninfo->hostname, vpninfo->port);
1211
1212         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1213         buf_free(reqbuf);
1214
1215         if (result) {
1216                 vpn_progress(vpninfo, PRG_ERR,
1217                              _("Sending proxy request failed: %s\n"),
1218                              strerror(-result));
1219                 return result;
1220         }
1221
1222         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1223                 vpn_progress(vpninfo, PRG_ERR,
1224                              _("Error fetching proxy response\n"));
1225                 return -EIO;
1226         }
1227
1228         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1229             buf[8] != ' ' || !(result = atoi(buf+9))) {
1230                 vpn_progress(vpninfo, PRG_ERR,
1231                              _("Failed to parse proxy response '%s'\n"), buf);
1232                 return -EINVAL;
1233         }
1234
1235         if (result != 200) {
1236                 vpn_progress(vpninfo, PRG_ERR,
1237                              _("Proxy CONNECT request failed: %s\n"), buf);
1238                 return -EIO;
1239         }
1240
1241         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1242                 if (buflen < 0) {
1243                         vpn_progress(vpninfo, PRG_ERR,
1244                                      _("Failed to read proxy response\n"));
1245                         return -EIO;
1246                 }
1247                 vpn_progress(vpninfo, PRG_ERR,
1248                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1249                              buf);
1250         }
1251
1252         return 0;
1253 }
1254
1255 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1256 {
1257         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1258                 return process_http_proxy(vpninfo, ssl_sock);
1259         
1260         if (!strcmp(vpninfo->proxy_type, "socks") ||
1261             !strcmp(vpninfo->proxy_type, "socks5"))
1262                 return process_socks_proxy(vpninfo, ssl_sock);
1263
1264         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1265                      vpninfo->proxy_type);
1266         return -EIO;
1267 }
1268
1269 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1270 {
1271         char *url = proxy;
1272         int ret;
1273
1274         if (!url)
1275                 return -ENOMEM;
1276
1277         free(vpninfo->proxy_type);
1278         vpninfo->proxy_type = NULL;
1279         free(vpninfo->proxy);
1280         vpninfo->proxy = NULL;
1281
1282         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1283                                  &vpninfo->proxy_port, NULL, 80);
1284         if (ret)
1285                 goto out;
1286
1287         if (vpninfo->proxy_type &&
1288             strcmp(vpninfo->proxy_type, "http") &&
1289             strcmp(vpninfo->proxy_type, "socks") &&
1290             strcmp(vpninfo->proxy_type, "socks5")) {
1291                 vpn_progress(vpninfo, PRG_ERR,
1292                              _("Only http or socks(5) proxies supported\n"));
1293                 free(vpninfo->proxy_type);
1294                 vpninfo->proxy_type = NULL;
1295                 free(vpninfo->proxy);
1296                 vpninfo->proxy = NULL;
1297                 return -EINVAL;
1298         }
1299  out:
1300         free(url);
1301         return ret;
1302 }