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