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