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