Handle allocation failure in HTTP 1.0 loop
[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                         vpninfo->progress(vpninfo, PRG_ERR, "Relative redirect (to '%s') not supported\n",
685                                 vpninfo->redirect_url);
686                         free(form_buf);
687                         return -EINVAL;
688                 }
689         }
690
691         if (vpninfo->csd_stuburl) {
692                 /* This is the CSD stub script, which we now need to run */
693                 result = run_csd_script(vpninfo, form_buf, buflen);
694                 if (result) {
695                         free(form_buf);
696                         return result;
697                 }
698
699                 /* Now we'll be redirected to the waiturl */
700                 goto retry;
701         }
702         if (strncmp(form_buf, "<?xml", 5)) {
703                 /* Not XML? Perhaps it's HTML with a refresh... */
704                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
705                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
706                                           vpninfo->urlpath);
707                         sleep(1);
708                         goto retry;
709                 }
710                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
711                 free(form_buf);
712                 return -EINVAL;
713         }
714         request_body[0] = 0;
715         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
716                                     &method, &request_body_type);
717
718         if (!result)
719                 goto redirect;
720
721         free(form_buf);
722
723         if (result != 2)
724                 return result;
725
726         /* A return value of 2 means the XML form indicated
727            success. We _should_ have a cookie... */
728
729         for (opt = vpninfo->cookies; opt; opt = opt->next) {
730
731                 if (!strcmp(opt->option, "webvpn"))
732                         vpninfo->cookie = opt->value;
733                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
734                         char *tok = opt->value;
735                         char *bu = NULL, *fu = NULL, *sha = NULL;
736
737                         do {
738                                 if (tok != opt->value)
739                                         *(tok++) = 0;
740
741                                 if (!strncmp(tok, "bu:", 3))
742                                         bu = tok + 3;
743                                 else if (!strncmp(tok, "fu:", 3))
744                                         fu = tok + 3;
745                                 else if (!strncmp(tok, "fh:", 3)) {
746                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
747                                                          SHA_DIGEST_LENGTH * 2))
748                                                 break;
749                                         sha = tok + 3;
750                                 }
751                         } while ((tok = strchr(tok, '&')));
752
753                         if (bu && fu && sha)
754                                 fetch_config(vpninfo, bu, fu, sha);
755                 }
756         }
757         if (vpninfo->csd_scriptname) {
758                 unlink(vpninfo->csd_scriptname);
759                 free(vpninfo->csd_scriptname);
760                 vpninfo->csd_scriptname = NULL;
761         }
762         return 0;
763 }
764
765 char *openconnect_create_useragent(char *base)
766 {
767         char *uagent;
768
769         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
770                 return NULL;
771
772         return uagent;
773 }
774
775 static int proxy_gets(int fd, char *buf, size_t len)
776 {
777         int i = 0;
778         int ret;
779
780         if (len < 2)
781                 return -EINVAL;
782
783         while ( (ret = read(fd, buf + i, 1)) == 1) {
784                 if (buf[i] == '\n') {
785                         buf[i] = 0;
786                         if (i && buf[i-1] == '\r') {
787                                 buf[i-1] = 0;
788                                 i--;
789                         }
790                         return i;
791                 }
792                 i++;
793
794                 if (i >= len - 1) {
795                         buf[i] = 0;
796                         return i;
797                 }
798         }
799         if (ret < 0)
800                 ret = -errno;
801
802         buf[i] = 0;
803         return i ?: ret;
804 }
805
806 static int proxy_write(int fd, unsigned char *buf, size_t len)
807 {
808         size_t count;
809         
810         for (count = 0; count < len; ) {
811                 int i = write(fd, buf + count, len - count);
812                 if (i < 0)
813                         return -errno;
814
815                 count += i;
816         }
817         return 0;
818 }
819
820 static int proxy_read(int fd, unsigned char *buf, size_t len)
821 {
822         size_t count;
823
824         for (count = 0; count < len; ) {
825                 int i = read(fd, buf + count, len - count);
826                 if (i < 0)
827                         return -errno;
828
829                 count += i;
830         }
831         return 0;
832 }
833
834 static const char *socks_errors[] = {
835         "request granted",
836         "general failure",
837         "connection not allowed by ruleset",
838         "network unreachable",
839         "host unreachable",
840         "connection refused by destination host",
841         "TTL expired",
842         "command not supported / protocol error",
843         "address type not supported"
844 };
845
846 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
847 {
848         unsigned char buf[1024];
849         int i;
850
851         buf[0] = 5; /* SOCKS version */
852         buf[1] = 1; /* # auth methods */
853         buf[2] = 0; /* No auth supported */
854
855         if ((i = proxy_write(ssl_sock, buf, 3))) {
856                 vpninfo->progress(vpninfo, PRG_ERR,
857                                   "Error writing auth request to SOCKS proxy: %s\n",
858                                   strerror(-i));
859                 return i;
860         }
861         
862         if ((i = proxy_read(ssl_sock, buf, 2))) {
863                 vpninfo->progress(vpninfo, PRG_ERR,
864                                   "Error reading auth response from SOCKS proxy: %s\n",
865                                   strerror(-i));
866                 return i;
867         }
868         if (buf[0] != 5) {
869                 vpninfo->progress(vpninfo, PRG_ERR,
870                                   "Unexpected auth response from SOCKS proxy: %02x %02x\n",
871                                   buf[0], buf[1]);
872                 return -EIO;
873         }
874         if (buf[1]) {
875         socks_err:
876                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
877                         vpninfo->progress(vpninfo, PRG_ERR,
878                                           "SOCKS proxy error %02x: %s\n",
879                                           buf[1], socks_errors[buf[1]]);
880                 else
881                         vpninfo->progress(vpninfo, PRG_ERR,
882                                           "SOCKS proxy error %02x\n",
883                                           buf[1]);
884                 return -EIO;
885         }
886
887         vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
888                           vpninfo->hostname, vpninfo->port);
889
890         buf[0] = 5; /* SOCKS version */
891         buf[1] = 1; /* CONNECT */
892         buf[2] = 0; /* Reserved */
893         buf[3] = 3; /* Address type is domain name */
894         buf[4] = strlen(vpninfo->hostname);
895         strcpy((char *)buf + 5, vpninfo->hostname);
896         i = strlen(vpninfo->hostname) + 5;
897         buf[i++] = vpninfo->port >> 8;
898         buf[i++] = vpninfo->port & 0xff;
899
900         if ((i = proxy_write(ssl_sock, buf, i))) {
901                 vpninfo->progress(vpninfo, PRG_ERR,
902                                   "Error writing connect request to SOCKS proxy: %s\n",
903                                   strerror(-i));
904                 return i;
905         }
906         /* Read 5 bytes -- up to and including the first byte of the returned
907            address (which might be the length byte of a domain name) */
908         if ((i = proxy_read(ssl_sock, buf, 5))) {
909                 vpninfo->progress(vpninfo, PRG_ERR,
910                                   "Error reading connect response from SOCKS proxy: %s\n",
911                                   strerror(-i));
912                 return i;
913         }
914         if (buf[0] != 5) {
915                 vpninfo->progress(vpninfo, PRG_ERR,
916                                   "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
917                                   buf[0], buf[1]);
918                 return -EIO;
919         }
920         if (buf[1])
921                 goto socks_err;
922
923         /* Connect responses contain an address */
924         switch(buf[3]) {
925         case 1: /* Legacy IP */
926                 i = 5;
927                 break;
928         case 3: /* Domain name */
929                 i = buf[4] + 2;
930                 break;
931         case 4: /* IPv6 */
932                 i = 17;
933                 break;
934         default:
935                 vpninfo->progress(vpninfo, PRG_ERR,
936                                   "Unexpected address type %02x in SOCKS connect response\n",
937                                   buf[3]);
938                 return -EIO;
939         }
940
941         if ((i = proxy_read(ssl_sock, buf, i))) {
942                 vpninfo->progress(vpninfo, PRG_ERR,
943                                   "Error reading connect response from SOCKS proxy: %s\n",
944                                   strerror(-i));
945                 return i;
946         }
947         return 0;
948 }
949
950 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
951 {
952         char buf[MAX_BUF_LEN];
953         int buflen, result;
954
955         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
956         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
957         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
958         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
959         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
960         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
961         sprintf(buf + strlen(buf), "\r\n");
962
963         vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
964                           vpninfo->hostname, vpninfo->port);
965
966         if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
967                 result = -errno;
968                 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
969                                   strerror(errno));
970                 return result;
971         }
972
973         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
974                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
975                 return -EIO;
976         }
977
978         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
979             buf[8] != ' ' || !(result = atoi(buf+9))) {
980                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
981                                   buf);
982                 return -EINVAL;
983         }
984
985         if (result != 200) {
986                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
987                                   buf);
988                 return -EIO;
989         }
990
991         while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
992                 if (buflen < 0) {
993                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
994                         return -EIO;
995                 }
996                 vpninfo->progress(vpninfo, PRG_ERR,
997                                   "Unexpected continuation line after CONNECT response: '%s'\n",
998                                   buf);
999         }
1000
1001         return 0;
1002 }
1003
1004 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1005 {
1006         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1007                 return process_http_proxy(vpninfo, ssl_sock);
1008         
1009         if (!strcmp(vpninfo->proxy_type, "socks") ||
1010             !strcmp(vpninfo->proxy_type, "socks5"))
1011                 return process_socks_proxy(vpninfo, ssl_sock);
1012
1013         vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
1014                                   vpninfo->proxy_type);
1015         return -EIO;
1016 }
1017
1018 int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1019 {
1020         char *url = strdup(proxy);
1021         int ret;
1022
1023         if (!url)
1024                 return -ENOMEM;
1025
1026         free(vpninfo->proxy_type);
1027         vpninfo->proxy_type = NULL;
1028         free(vpninfo->proxy);
1029         vpninfo->proxy = NULL;
1030
1031         ret = parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1032                         &vpninfo->proxy_port, NULL, 80);
1033         if (ret)
1034                 goto out;
1035
1036         if (vpninfo->proxy_type &&
1037             strcmp(vpninfo->proxy_type, "http") &&
1038             strcmp(vpninfo->proxy_type, "socks") &&
1039             strcmp(vpninfo->proxy_type, "socks5")) {
1040                 vpninfo->progress(vpninfo, PRG_ERR,
1041                                   "Only http or socks(5) proxies supported\n");
1042                 free(vpninfo->proxy_type);
1043                 vpninfo->proxy_type = NULL;
1044                 free(vpninfo->proxy);
1045                 vpninfo->proxy = NULL;
1046                 return -EINVAL;
1047         }
1048  out:
1049         free(url);
1050         return ret;
1051 }