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