Fix handling of error from proxy_write() in process_http_proxy()
[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->https_ssl, 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->https_ssl, 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->https_ssl, 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->https_ssl, 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         /*
644          * It would be nice to use cURL for this, but we really need to guarantee
645          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
646          * to have any way to let us provide our own socket read/write functions.
647          * We can only provide a socket _open_ function. Which would require having
648          * a socketpair() and servicing the "other" end of it.
649          *
650          * So we process the HTTP for ourselves...
651          */
652         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
653         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
654         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
655         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
656         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
657
658         if (vpninfo->cookies) {
659                 sprintf(buf + strlen(buf),  "Cookie: ");
660                 for (opt = vpninfo->cookies; opt; opt = opt->next)
661                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
662                                       opt->value, opt->next ? "; " : "\r\n");
663         }
664         if (request_body_type) {
665                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
666                               request_body_type);
667                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
668                               strlen(request_body));
669         }
670         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
671         if (request_body_type)
672                 sprintf(buf + strlen(buf), "%s", request_body);
673
674         if (vpninfo->port == 443)
675                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
676                              method, vpninfo->hostname,
677                              vpninfo->urlpath ?: "");
678         else
679                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
680                              method, vpninfo->hostname, vpninfo->port,
681                              vpninfo->urlpath ?: "");
682
683         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
684
685         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
686         if (buflen < 0) {
687                 /* We'll already have complained about whatever offended us */
688                 exit(1);
689         }
690
691         if (result != 200 && vpninfo->redirect_url) {
692         redirect:
693                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
694                         /* New host. Tear down the existing connection and make a new one */
695                         char *host;
696                         int port;
697                         int ret;
698
699                         free(vpninfo->urlpath);
700                         vpninfo->urlpath = NULL;
701
702                         ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
703                         if (ret) {
704                                 vpn_progress(vpninfo, PRG_ERR,
705                                              _("Failed to parse redirected URL '%s': %s\n"),
706                                              vpninfo->redirect_url, strerror(-ret));
707                                 free(vpninfo->redirect_url);
708                                 free(form_buf);
709                                 return ret;
710                         }
711
712                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
713                                 free(vpninfo->hostname);
714                                 vpninfo->hostname = host;
715                                 vpninfo->port = port;
716
717                                 /* Kill the existing connection, and a new one will happen */
718                                 free(vpninfo->peer_addr);
719                                 vpninfo->peer_addr = NULL;
720                                 if (vpninfo->https_ssl) {
721                                         SSL_free(vpninfo->https_ssl);
722                                         vpninfo->https_ssl = NULL;
723                                         close(vpninfo->ssl_fd);
724                                         vpninfo->ssl_fd = -1;
725                                 }
726
727                                 for (opt = vpninfo->cookies; opt; opt = next) {
728                                         next = opt->next;
729
730                                         free(opt->option);
731                                         free(opt->value);
732                                         free(opt);
733                                 }
734                                 vpninfo->cookies = NULL;
735                         } else
736                                 free(host);
737
738                         free(vpninfo->redirect_url);
739                         vpninfo->redirect_url = NULL;
740
741                         goto retry;
742                 } else if (vpninfo->redirect_url[0] == '/') {
743                         /* Absolute redirect within same host */
744                         free(vpninfo->urlpath);
745                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
746                         free(vpninfo->redirect_url);
747                         vpninfo->redirect_url = NULL;
748                         goto retry;
749                 } else {
750                         char *lastslash = NULL;
751                         if (vpninfo->urlpath)
752                                 lastslash = strrchr(vpninfo->urlpath, '/');
753                         if (!lastslash) {
754                                 free(vpninfo->urlpath);
755                                 vpninfo->urlpath = vpninfo->redirect_url;
756                                 vpninfo->redirect_url = NULL;
757                         } else {
758                                 char *oldurl = vpninfo->urlpath;
759                                 *lastslash = 0;
760                                 vpninfo->urlpath = NULL;
761                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
762                                              oldurl, vpninfo->redirect_url) == -1) {
763                                         int err = -errno;
764                                         vpn_progress(vpninfo, PRG_ERR,
765                                                      _("Allocating new path for relative redirect failed: %s\n"),
766                                                      strerror(-err));
767                                         return err;
768                                 }
769                                 free(oldurl);
770                                 free(vpninfo->redirect_url);
771                                 vpninfo->redirect_url = NULL;
772                         }
773                         goto retry;
774                 }
775         }
776         if (!form_buf || result != 200) {
777                 vpn_progress(vpninfo, PRG_ERR,
778                              _("Unexpected %d result from server\n"),
779                              result);
780                 free(form_buf);
781                 return -EINVAL;
782         }
783         if (vpninfo->csd_stuburl) {
784                 /* This is the CSD stub script, which we now need to run */
785                 result = run_csd_script(vpninfo, form_buf, buflen);
786                 if (result) {
787                         free(form_buf);
788                         return result;
789                 }
790
791                 /* Now we'll be redirected to the waiturl */
792                 goto retry;
793         }
794         if (strncmp(form_buf, "<?xml", 5)) {
795                 /* Not XML? Perhaps it's HTML with a refresh... */
796                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
797                         vpn_progress(vpninfo, PRG_INFO,
798                                      _("Refreshing %s after 1 second...\n"),
799                                      vpninfo->urlpath);
800                         sleep(1);
801                         goto retry;
802                 }
803                 vpn_progress(vpninfo, PRG_ERR,
804                              _("Unknown response from server\n"));
805                 free(form_buf);
806                 return -EINVAL;
807         }
808         request_body[0] = 0;
809         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
810                                     &method, &request_body_type);
811
812         if (!result)
813                 goto redirect;
814
815         free(form_buf);
816
817         if (result != 2)
818                 return result;
819
820         /* A return value of 2 means the XML form indicated
821            success. We _should_ have a cookie... */
822
823         for (opt = vpninfo->cookies; opt; opt = opt->next) {
824
825                 if (!strcmp(opt->option, "webvpn"))
826                         vpninfo->cookie = opt->value;
827                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
828                         char *tok = opt->value;
829                         char *bu = NULL, *fu = NULL, *sha = NULL;
830
831                         do {
832                                 if (tok != opt->value)
833                                         *(tok++) = 0;
834
835                                 if (!strncmp(tok, "bu:", 3))
836                                         bu = tok + 3;
837                                 else if (!strncmp(tok, "fu:", 3))
838                                         fu = tok + 3;
839                                 else if (!strncmp(tok, "fh:", 3)) {
840                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
841                                                          SHA_DIGEST_LENGTH * 2))
842                                                 break;
843                                         sha = tok + 3;
844                                 }
845                         } while ((tok = strchr(tok, '&')));
846
847                         if (bu && fu && sha)
848                                 fetch_config(vpninfo, bu, fu, sha);
849                 }
850         }
851         if (vpninfo->csd_scriptname) {
852                 unlink(vpninfo->csd_scriptname);
853                 free(vpninfo->csd_scriptname);
854                 vpninfo->csd_scriptname = NULL;
855         }
856         return 0;
857 }
858
859 char *openconnect_create_useragent(const char *base)
860 {
861         char *uagent;
862
863         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
864                 return NULL;
865
866         return uagent;
867 }
868
869 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
870                       char *buf, size_t len)
871 {
872         int i = 0;
873         int ret;
874
875         if (len < 2)
876                 return -EINVAL;
877
878         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
879                 if (buf[i] == '\n') {
880                         buf[i] = 0;
881                         if (i && buf[i-1] == '\r') {
882                                 buf[i-1] = 0;
883                                 i--;
884                         }
885                         return i;
886                 }
887                 i++;
888
889                 if (i >= len - 1) {
890                         buf[i] = 0;
891                         return i;
892                 }
893         }
894         buf[i] = 0;
895         return i ?: ret;
896 }
897
898 static int proxy_write(struct openconnect_info *vpninfo, int fd,
899                        unsigned char *buf, size_t len)
900 {
901         size_t count;
902         
903         for (count = 0; count < len; ) {
904                 int i = write(fd, buf + count, len - count);
905                 if (i < 0)
906                         return -errno;
907
908                 count += i;
909         }
910         return 0;
911 }
912
913 static int proxy_read(struct openconnect_info *vpninfo, int fd,
914                       unsigned char *buf, size_t len)
915 {
916         size_t count;
917
918         for (count = 0; count < len; ) {
919                 int i = read(fd, buf + count, len - count);
920                 if (i < 0)
921                         return -errno;
922
923                 count += i;
924         }
925         return 0;
926 }
927
928 static const char *socks_errors[] = {
929         N_("request granted"),
930         N_("general failure"),
931         N_("connection not allowed by ruleset"),
932         N_("network unreachable"),
933         N_("host unreachable"),
934         N_("connection refused by destination host"),
935         N_("TTL expired"),
936         N_("command not supported / protocol error"),
937         N_("address type not supported")
938 };
939
940 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
941 {
942         unsigned char buf[1024];
943         int i;
944
945         buf[0] = 5; /* SOCKS version */
946         buf[1] = 1; /* # auth methods */
947         buf[2] = 0; /* No auth supported */
948
949         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
950                 vpn_progress(vpninfo, PRG_ERR,
951                              _("Error writing auth request to SOCKS proxy: %s\n"),
952                              strerror(-i));
953                 return i;
954         }
955         
956         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
957                 vpn_progress(vpninfo, PRG_ERR,
958                              _("Error reading auth response from SOCKS proxy: %s\n"),
959                              strerror(-i));
960                 return i;
961         }
962         if (buf[0] != 5) {
963                 vpn_progress(vpninfo, PRG_ERR,
964                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
965                              buf[0], buf[1]);
966                 return -EIO;
967         }
968         if (buf[1]) {
969         socks_err:
970                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
971                         vpn_progress(vpninfo, PRG_ERR,
972                                      _("SOCKS proxy error %02x: %s\n"),
973                                      buf[1], _(socks_errors[buf[1]]));
974                 else
975                         vpn_progress(vpninfo, PRG_ERR,
976                                      _("SOCKS proxy error %02x\n"),
977                                      buf[1]);
978                 return -EIO;
979         }
980
981         vpn_progress(vpninfo, PRG_INFO,
982                      _("Requesting SOCKS proxy connection to %s:%d\n"),
983                      vpninfo->hostname, vpninfo->port);
984
985         buf[0] = 5; /* SOCKS version */
986         buf[1] = 1; /* CONNECT */
987         buf[2] = 0; /* Reserved */
988         buf[3] = 3; /* Address type is domain name */
989         buf[4] = strlen(vpninfo->hostname);
990         strcpy((char *)buf + 5, vpninfo->hostname);
991         i = strlen(vpninfo->hostname) + 5;
992         buf[i++] = vpninfo->port >> 8;
993         buf[i++] = vpninfo->port & 0xff;
994
995         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
996                 vpn_progress(vpninfo, PRG_ERR,
997                              _("Error writing connect request to SOCKS proxy: %s\n"),
998                              strerror(-i));
999                 return i;
1000         }
1001         /* Read 5 bytes -- up to and including the first byte of the returned
1002            address (which might be the length byte of a domain name) */
1003         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1004                 vpn_progress(vpninfo, PRG_ERR,
1005                              _("Error reading connect response from SOCKS proxy: %s\n"),
1006                              strerror(-i));
1007                 return i;
1008         }
1009         if (buf[0] != 5) {
1010                 vpn_progress(vpninfo, PRG_ERR,
1011                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1012                              buf[0], buf[1]);
1013                 return -EIO;
1014         }
1015         if (buf[1])
1016                 goto socks_err;
1017
1018         /* Connect responses contain an address */
1019         switch(buf[3]) {
1020         case 1: /* Legacy IP */
1021                 i = 5;
1022                 break;
1023         case 3: /* Domain name */
1024                 i = buf[4] + 2;
1025                 break;
1026         case 4: /* IPv6 */
1027                 i = 17;
1028                 break;
1029         default:
1030                 vpn_progress(vpninfo, PRG_ERR,
1031                              _("Unexpected address type %02x in SOCKS connect response\n"),
1032                              buf[3]);
1033                 return -EIO;
1034         }
1035
1036         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1037                 vpn_progress(vpninfo, PRG_ERR,
1038                              _("Error reading connect response from SOCKS proxy: %s\n"),
1039                              strerror(-i));
1040                 return i;
1041         }
1042         return 0;
1043 }
1044
1045 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1046 {
1047         char buf[MAX_BUF_LEN];
1048         int buflen, result;
1049
1050         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1051         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1052         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1053         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1054         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1055         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1056         sprintf(buf + strlen(buf), "\r\n");
1057
1058         vpn_progress(vpninfo, PRG_INFO,
1059                      _("Requesting HTTP proxy connection to %s:%d\n"),
1060                      vpninfo->hostname, vpninfo->port);
1061
1062         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1063         if (result) {
1064                 vpn_progress(vpninfo, PRG_ERR,
1065                              _("Sending proxy request failed: %s\n"),
1066                              strerror(-result));
1067                 return result;
1068         }
1069
1070         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1071                 vpn_progress(vpninfo, PRG_ERR,
1072                              _("Error fetching proxy response\n"));
1073                 return -EIO;
1074         }
1075
1076         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1077             buf[8] != ' ' || !(result = atoi(buf+9))) {
1078                 vpn_progress(vpninfo, PRG_ERR,
1079                              _("Failed to parse proxy response '%s'\n"), buf);
1080                 return -EINVAL;
1081         }
1082
1083         if (result != 200) {
1084                 vpn_progress(vpninfo, PRG_ERR,
1085                              _("Proxy CONNECT request failed: %s\n"), buf);
1086                 return -EIO;
1087         }
1088
1089         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1090                 if (buflen < 0) {
1091                         vpn_progress(vpninfo, PRG_ERR,
1092                                      _("Failed to read proxy response\n"));
1093                         return -EIO;
1094                 }
1095                 vpn_progress(vpninfo, PRG_ERR,
1096                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1097                              buf);
1098         }
1099
1100         return 0;
1101 }
1102
1103 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1104 {
1105         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1106                 return process_http_proxy(vpninfo, ssl_sock);
1107         
1108         if (!strcmp(vpninfo->proxy_type, "socks") ||
1109             !strcmp(vpninfo->proxy_type, "socks5"))
1110                 return process_socks_proxy(vpninfo, ssl_sock);
1111
1112         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1113                      vpninfo->proxy_type);
1114         return -EIO;
1115 }
1116
1117 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1118 {
1119         char *url = proxy;
1120         int ret;
1121
1122         if (!url)
1123                 return -ENOMEM;
1124
1125         free(vpninfo->proxy_type);
1126         vpninfo->proxy_type = NULL;
1127         free(vpninfo->proxy);
1128         vpninfo->proxy = NULL;
1129
1130         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1131                                  &vpninfo->proxy_port, NULL, 80);
1132         if (ret)
1133                 goto out;
1134
1135         if (vpninfo->proxy_type &&
1136             strcmp(vpninfo->proxy_type, "http") &&
1137             strcmp(vpninfo->proxy_type, "socks") &&
1138             strcmp(vpninfo->proxy_type, "socks5")) {
1139                 vpn_progress(vpninfo, PRG_ERR,
1140                              _("Only http or socks(5) proxies supported\n"));
1141                 free(vpninfo->proxy_type);
1142                 vpninfo->proxy_type = NULL;
1143                 free(vpninfo->proxy);
1144                 vpninfo->proxy = NULL;
1145                 return -EINVAL;
1146         }
1147  out:
1148         free(url);
1149         return ret;
1150 }