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