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