Handle IPv6 literal [] in connection, accept https:// URL for server
[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 #define MAX_BUF_LEN 131072
44 /*
45  * We didn't really want to have to do this for ourselves -- one might have
46  * thought that it would be available in a library somewhere. But neither
47  * cURL nor Neon have reliable cross-platform ways of either using a cert
48  * from the TPM, or just reading from / writing to a transport which is
49  * provided by their caller.
50  */
51
52 int http_add_cookie(struct openconnect_info *vpninfo, const char *option, const char *value)
53 {
54         struct vpn_option *new, **this;
55
56         if (*value) {
57                 new = malloc(sizeof(*new));
58                 if (!new) {
59                         vpninfo->progress(vpninfo, PRG_ERR, "No memory for allocating cookies\n");
60                         return -ENOMEM;
61                 }
62                 new->next = NULL;
63                 new->option = strdup(option);
64                 new->value = strdup(value);
65                 if (!new->option || !new->value) {
66                         free(new->option);
67                         free(new->value);
68                         free(new);
69                         return -ENOMEM;
70                 }
71         } else {
72                 /* Kill cookie; don't replace it */
73                 new = NULL;
74         }
75         for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
76                 if (!strcmp(option, (*this)->option)) {
77                         /* Replace existing cookie */
78                         if (new)
79                                 new->next = (*this)->next;
80                         else
81                                 new = (*this)->next;
82                         
83                         free((*this)->option);
84                         free((*this)->value);
85                         free(*this);
86                         *this = new;
87                         break;
88                 }
89         }
90         if (new && !*this) {
91                 *this = new;
92                 new->next = NULL;
93         }
94         return 0;
95 }
96
97 static int process_http_response(struct openconnect_info *vpninfo, int *result,
98                                  int (*header_cb)(struct openconnect_info *, char *, char *),
99                                  char *body, int buf_len)
100 {
101         char buf[MAX_BUF_LEN];
102         int bodylen = 0;
103         int done = 0;
104         int http10 = 0, closeconn = 0;
105         int i;
106
107         if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
108                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
109                 return -EINVAL;
110         }
111
112  cont:
113         if (!strncmp(buf, "HTTP/1.0 ", 9)) {
114                 http10 = 1;
115                 closeconn = 1;
116         }
117
118         if ((!http10 && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
119                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse HTTP response '%s'\n", buf);
120                 return -EINVAL;
121         }
122
123         vpninfo->progress(vpninfo, PRG_TRACE, "Got HTTP response: %s\n", buf);
124
125         /* Eat headers... */
126         while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
127                 char *colon;
128
129                 vpninfo->progress(vpninfo, PRG_TRACE, "%s\n", buf);
130
131                 if (i < 0) {
132                         vpninfo->progress(vpninfo, PRG_ERR, "Error processing HTTP response\n");
133                         return -EINVAL;
134                 }
135                 colon = strchr(buf, ':');
136                 if (!colon) {
137                         vpninfo->progress(vpninfo, PRG_ERR, "Ignoring unknown HTTP response line '%s'\n", buf);
138                         continue;
139                 }
140                 *(colon++) = 0;
141                 if (*colon == ' ')
142                         colon++;
143
144                 if (!strcmp(buf, "Connection") && !strcmp(colon, "Close"))
145                         closeconn = 1;
146
147                 if (!strcmp(buf, "Location")) {
148                         vpninfo->redirect_url = strdup(colon);
149                         if (!vpninfo->redirect_url)
150                                 return -ENOMEM;
151                 }
152                 if (!strcmp(buf, "Content-Length")) {
153                         bodylen = atoi(colon);
154                         if (bodylen < 0 || bodylen > buf_len) {
155                                 vpninfo->progress(vpninfo, PRG_ERR, "Response body too large for buffer (%d > %d)\n",
156                                         bodylen, buf_len);
157                                 return -EINVAL;
158                         }
159                 }
160                 if (!strcmp(buf, "Set-Cookie")) {
161                         char *semicolon = strchr(colon, ';');
162                         char *equals = strchr(colon, '=');
163                         int ret;
164
165                         if (semicolon)
166                                 *semicolon = 0;
167
168                         if (!equals) {
169                                 vpninfo->progress(vpninfo, PRG_ERR, "Invalid cookie offered: %s\n", buf);
170                                 return -EINVAL;
171                         }
172                         *(equals++) = 0;
173
174                         ret = http_add_cookie(vpninfo, colon, equals);
175                         if (ret)
176                                 return ret;
177                 }
178                 if (!strcmp(buf, "Transfer-Encoding")) {
179                         if (!strcmp(colon, "chunked"))
180                                 bodylen = -1;
181                         else {
182                                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown Transfer-Encoding: %s\n", colon);
183                                 return -EINVAL;
184                         }
185                 }
186                 if (header_cb && !strncmp(buf, "X-", 2))
187                         header_cb(vpninfo, buf, colon);
188         }
189
190         /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
191         if (*result == 100)
192                 goto cont;
193
194         /* Now the body, if there is one */
195         if (!bodylen)
196                 goto fin;
197
198         if (http10) {
199                 /* HTTP 1.0 response. Just eat all we can. */
200                 while (1) {
201                         i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
202                         if (i < 0)
203                                 goto fin;
204                         done += i;
205                 }
206         }
207         /* If we were given Content-Length, it's nice and easy... */
208         if (bodylen > 0) {
209                 while (done < bodylen) {
210                         i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
211                         if (i < 0) {
212                                 vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
213                                 return -EINVAL;
214                         }
215                         done += i;
216                 }
217                 goto fin;
218         }
219
220         /* ... else, chunked */
221         while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
222                 int chunklen, lastchunk = 0;
223
224                 if (i < 0) {
225                         vpninfo->progress(vpninfo, PRG_ERR, "Error fetching chunk header\n");
226                         exit(1);
227                 }
228                 chunklen = strtol(buf, NULL, 16);
229                 if (!chunklen) {
230                         lastchunk = 1;
231                         goto skip;
232                 }
233                 if (chunklen + done > buf_len) {
234                         vpninfo->progress(vpninfo, PRG_ERR, "Response body too large for buffer (%d > %d)\n",
235                                 chunklen + done, buf_len);
236                         return -EINVAL;
237                 }
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                                 return -EINVAL;
243                         }
244                         chunklen -= i;
245                         done += i;
246                 }
247         skip:
248                 if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
249                         if (i < 0) {
250                                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTP response body\n");
251                         } else {
252                                 vpninfo->progress(vpninfo, PRG_ERR, "Error in chunked decoding. Expected '', got: '%s'",
253                                         buf);
254                         }
255                         return -EINVAL;
256                 }
257
258                 if (lastchunk)
259                         break;
260         }
261  fin:
262         if (closeconn && vpninfo->https_ssl) {
263                 SSL_free(vpninfo->https_ssl);
264                 vpninfo->https_ssl = NULL;
265                 close(vpninfo->ssl_fd);
266                 vpninfo->ssl_fd = -1;
267         }
268         body[done] = 0;
269         return done;
270 }
271
272 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
273                         char *server_sha1)
274 {
275         struct vpn_option *opt;
276         char buf[MAX_BUF_LEN];
277         int result, buflen;
278         unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
279         char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
280         EVP_MD_CTX c;
281         int i;
282
283         sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
284         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
285         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
286         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
287         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
288
289         if (vpninfo->cookies) {
290                 sprintf(buf + strlen(buf),  "Cookie: ");
291                 for (opt = vpninfo->cookies; opt; opt = opt->next)
292                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
293                                       opt->value, opt->next ? "; " : "\r\n");
294         }
295         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
296
297         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
298
299         buflen = process_http_response(vpninfo, &result, NULL, buf, MAX_BUF_LEN);
300         if (buflen < 0) {
301                 /* We'll already have complained about whatever offended us */
302                 return -EINVAL;
303         }
304
305         if (result != 200)
306                 return -EINVAL;
307
308
309         EVP_MD_CTX_init(&c);
310         EVP_Digest(buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
311         EVP_MD_CTX_cleanup(&c);
312
313         for (i = 0; i < SHA_DIGEST_LENGTH; i++)
314                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
315
316         if (strcasecmp(server_sha1, local_sha1_ascii)) {
317                 vpninfo->progress(vpninfo, PRG_ERR, "Downloaded config file did not match intended SHA1\n");
318                 return -EINVAL;
319         }
320
321         return vpninfo->write_new_config(vpninfo, buf, buflen);
322 }
323
324 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
325 {
326         char fname[16];
327         int fd;
328
329         if (!vpninfo->uid_csd_given) {
330                 vpninfo->progress(vpninfo, PRG_ERR, "Error: You are trying to "
331                                   "run insecure CSD code without specifying the CSD user.\n"
332                                   "       Use command line option \"--csd-user\"\n");
333                 exit(1);
334         }
335
336 #ifndef __linux__
337         vpninfo->progress(vpninfo, PRG_INFO,
338                           "Trying to run Linux CSD trojan script.");
339 #endif
340
341         sprintf(fname, "/tmp/csdXXXXXX");
342         fd = mkstemp(fname);
343         if (fd < 0) {
344                 int err = -errno;
345                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open temporary CSD script file: %s\n",
346                                   strerror(errno));
347                 return err;
348         }
349         write(fd, buf, buflen);
350         fchmod(fd, 0755);
351         close(fd);
352
353         if (!fork()) {
354                 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
355                 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
356                 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
357                 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
358                 char *csd_argv[32];
359                 int i = 0;
360
361                 if (vpninfo->uid_csd != getuid()) {
362                         struct passwd *pw;
363
364                         if (setuid(vpninfo->uid_csd)) {
365                                 fprintf(stderr, "Failed to set uid %d\n",
366                                         vpninfo->uid_csd);
367                                 exit(1);
368                         }
369                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
370                                 fprintf(stderr, "Invalid user uid=%d\n",
371                                         vpninfo->uid_csd);
372                                 exit(1);
373                         }
374                         setenv("HOME", pw->pw_dir, 1);
375                         chdir(pw->pw_dir);
376                 }
377                 if (vpninfo->uid_csd == 0) {
378                         fprintf(stderr, "Warning: you are running insecure "
379                                 "CSD code with root privileges\n"
380                                 "\t Use command line option \"--csd-user\"\n");
381                 }
382
383                 csd_argv[i++] = fname;
384                 csd_argv[i++] = "-ticket";
385                 asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket);
386                 csd_argv[i++] = "-stub";
387                 csd_argv[i++] = "\"0\"";
388                 csd_argv[i++] = "-group";
389                 asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"");
390                 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
391                 if (ccert)
392                         get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
393                 else
394                         ccertbuf[0] = 0;
395
396                 csd_argv[i++] = "-certhash";
397                 asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf);
398                 csd_argv[i++] = "-url";
399                 asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl);
400                 /* WTF would it want to know this for? */
401                 csd_argv[i++] = "-vpnclient";
402                 csd_argv[i++] = "\"/opt/cisco/vpn/bin/vpnui";
403                 csd_argv[i++] = "-connect";
404                 asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl);
405                 csd_argv[i++] = "-connectparam";
406                 asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token);
407                 csd_argv[i++] = "-langselen";
408                 csd_argv[i++] = NULL;
409
410                 execv(fname, csd_argv);
411                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", fname);
412                 exit(1);
413         }
414
415         free(vpninfo->csd_stuburl);
416         vpninfo->csd_stuburl = NULL;
417         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
418                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
419         vpninfo->csd_waiturl = NULL;
420         vpninfo->csd_scriptname = strdup(fname);
421
422         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
423
424         return 0;
425 }
426
427 #ifdef __sun__
428 char *local_strcasestr(const char *haystack, const char *needle)
429 {
430         int hlen = strlen(haystack);
431         int nlen = strlen(needle);
432         int i, j;
433
434         for (i = 0; i < hlen - nlen + 1; i++) {
435                 for (j = 0; j < nlen; j++) {
436                         if (tolower(haystack[i + j]) != 
437                             tolower(needle[j]))
438                                 break;
439                 }
440                 if (j == nlen)
441                         return (char *)haystack + i;
442         }
443         return NULL;
444 }
445 #define strcasestr local_strcasestr
446 #endif
447
448 int parse_url(char *url, char **res_proto, char **res_host, int *res_port,
449               char **res_path, int default_port)
450 {
451         char *proto = url;
452         char *host, *path, *port_str;
453         int port;
454
455         host = strstr(url, "://");
456         if (host) {
457                 *host = 0;
458                 host += 3;
459
460                 if (!strcasecmp(proto, "https"))
461                         port = 443;
462                 else if (!strcasecmp(proto, "http"))
463                         port = 80;
464                 else if (!strcasecmp(proto, "socks") ||
465                          !strcasecmp(proto, "socks4") ||
466                          !strcasecmp(proto, "socks5"))
467                         port = 1080;
468                 else
469                         return -EPROTONOSUPPORT;
470         } else {
471                 if (default_port) {
472                         proto = NULL;
473                         port = default_port;
474                         host = url;
475                 } else
476                         return -EINVAL;
477         }
478
479         path = strchr(host, '/');
480         if (path) {
481                 *(path++) = 0;
482                 if (!*path)
483                         path = NULL;
484         }
485
486         port_str = strrchr(host, ':');
487         if (port_str) {
488                 char *end;
489                 int new_port = strtol(port_str + 1, &end, 10);
490
491                 if (!*end) {
492                         *port_str = 0;
493                         port = new_port;
494                 }
495         }
496
497         if (res_proto)
498                 *res_proto = proto ? strdup(proto) : NULL;
499         if (res_host)
500                 *res_host = strdup(host);
501         if (res_port)
502                 *res_port = port;
503         if (res_path)
504                 *res_path = path ? strdup(path) : NULL;
505         return 0;
506 }
507
508 /* Return value:
509  *  < 0, on error
510  *  = 0, no cookie (user cancel)
511  *  = 1, obtained cookie
512  */
513 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
514 {
515         struct vpn_option *opt, *next;
516         char buf[MAX_BUF_LEN];
517         int result, buflen;
518         char request_body[2048];
519         char *request_body_type = NULL;
520         char *method = "GET";
521
522  retry:
523         if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
524                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
525                         vpninfo->hostname);
526                 return -EINVAL;
527         }
528
529         /*
530          * It would be nice to use cURL for this, but we really need to guarantee
531          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
532          * to have any way to let us provide our own socket read/write functions.
533          * We can only provide a socket _open_ function. Which would require having
534          * a socketpair() and servicing the "other" end of it.
535          *
536          * So we process the HTTP for ourselves...
537          */
538         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
539         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
540         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
541         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
542         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
543
544         if (vpninfo->cookies) {
545                 sprintf(buf + strlen(buf),  "Cookie: ");
546                 for (opt = vpninfo->cookies; opt; opt = opt->next)
547                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
548                                       opt->value, opt->next ? "; " : "\r\n");
549         }
550         if (request_body_type) {
551                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
552                               request_body_type);
553                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
554                               strlen(request_body));
555         }
556         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
557         if (request_body_type)
558                 sprintf(buf + strlen(buf), "%s", request_body);
559
560         if (vpninfo->port == 443)
561                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
562                                   method, vpninfo->hostname,
563                                   vpninfo->urlpath ?: "");
564         else
565                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
566                                   method, vpninfo->hostname, vpninfo->port,
567                                   vpninfo->urlpath ?: "");
568
569         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
570
571         buflen = process_http_response(vpninfo, &result, NULL, buf, MAX_BUF_LEN);
572         if (buflen < 0) {
573                 /* We'll already have complained about whatever offended us */
574                 exit(1);
575         }
576
577         if (result != 200 && vpninfo->redirect_url) {
578         redirect:
579                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
580                         /* New host. Tear down the existing connection and make a new one */
581                         char *host;
582                         int port;
583                         int ret;
584
585                         free(vpninfo->urlpath);
586                         vpninfo->urlpath = NULL;
587
588                         ret = parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
589                         if (ret) {
590                                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse redirected URL '%s': %s\n",
591                                                   vpninfo->redirect_url, strerror(-ret));
592                                 free(vpninfo->redirect_url);
593                                 return ret;
594                         }
595
596                         if (strcmp(vpninfo->hostname, host) || port != vpninfo->port) {
597                                 free(vpninfo->hostname);
598                                 vpninfo->hostname = host;
599                                 vpninfo->port = port;
600
601                                 /* Kill the existing connection, and a new one will happen */
602                                 free(vpninfo->peer_addr);
603                                 vpninfo->peer_addr = NULL;
604                                 SSL_free(vpninfo->https_ssl);
605                                 vpninfo->https_ssl = NULL;
606                                 close(vpninfo->ssl_fd);
607                                 vpninfo->ssl_fd = -1;
608
609                                 for (opt = vpninfo->cookies; opt; opt = next) {
610                                         next = opt->next;
611
612                                         free(opt->option);
613                                         free(opt->value);
614                                         free(opt);
615                                 }
616                                 vpninfo->cookies = NULL;
617                         } else
618                                 free(host);
619
620                         free(vpninfo->redirect_url);
621                         vpninfo->redirect_url = NULL;
622
623                         goto retry;
624                 } else if (vpninfo->redirect_url[0] == '/') {
625                         /* Absolute redirect within same host */
626                         free(vpninfo->urlpath);
627                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
628                         free(vpninfo->redirect_url);
629                         vpninfo->redirect_url = NULL;
630                         goto retry;
631                 } else {
632                         vpninfo->progress(vpninfo, PRG_ERR, "Relative redirect (to '%s') not supported\n",
633                                 vpninfo->redirect_url);
634                         return -EINVAL;
635                 }
636         }
637
638         if (vpninfo->csd_stuburl) {
639                 /* This is the CSD stub script, which we now need to run */
640                 result = run_csd_script(vpninfo, buf, buflen);
641                 if (result)
642                         return result;
643
644                 /* Now we'll be redirected to the waiturl */
645                 goto retry;
646         }
647         if (strncmp(buf, "<?xml", 5)) {
648                 /* Not XML? Perhaps it's HTML with a refresh... */
649                 if (strcasestr(buf, "http-equiv=\"refresh\"")) {
650                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
651                                           vpninfo->urlpath);
652                         sleep(1);
653                         goto retry;
654                 }
655                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
656                 return -EINVAL;
657         }
658         request_body[0] = 0;
659         result = parse_xml_response(vpninfo, buf, request_body, sizeof(request_body),
660                                     &method, &request_body_type);
661         if (!result)
662                 goto redirect;
663
664         if (result != 2)
665                 return result;
666         /* A return value of 2 means the XML form indicated
667            success. We _should_ have a cookie... */
668
669         for (opt = vpninfo->cookies; opt; opt = opt->next) {
670
671                 if (!strcmp(opt->option, "webvpn"))
672                         vpninfo->cookie = opt->value;
673                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
674                         char *tok = opt->value;
675                         char *bu = NULL, *fu = NULL, *sha = NULL;
676
677                         do {
678                                 if (tok != opt->value)
679                                         *(tok++) = 0;
680
681                                 if (!strncmp(tok, "bu:", 3))
682                                         bu = tok + 3;
683                                 else if (!strncmp(tok, "fu:", 3))
684                                         fu = tok + 3;
685                                 else if (!strncmp(tok, "fh:", 3)) {
686                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
687                                                          SHA_DIGEST_LENGTH * 2))
688                                                 break;
689                                         sha = tok + 3;
690                                 }
691                         } while ((tok = strchr(tok, '&')));
692
693                         if (bu && fu && sha)
694                                 fetch_config(vpninfo, bu, fu, sha);
695                 }
696         }
697         if (vpninfo->csd_scriptname) {
698                 unlink(vpninfo->csd_scriptname);
699                 free(vpninfo->csd_scriptname);
700                 vpninfo->csd_scriptname = NULL;
701         }
702         return 0;
703 }
704
705 char *openconnect_create_useragent(char *base)
706 {
707         char *uagent;
708
709         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
710                 return NULL;
711
712         return uagent;
713 }
714
715 static int proxy_gets(int fd, char *buf, size_t len)
716 {
717         int i = 0;
718         int ret;
719
720         if (len < 2)
721                 return -EINVAL;
722
723         while ( (ret = read(fd, buf + i, 1)) == 1) {
724                 if (buf[i] == '\n') {
725                         buf[i] = 0;
726                         if (i && buf[i-1] == '\r') {
727                                 buf[i-1] = 0;
728                                 i--;
729                         }
730                         return i;
731                 }
732                 i++;
733
734                 if (i >= len - 1) {
735                         buf[i] = 0;
736                         return i;
737                 }
738         }
739         if (ret < 0)
740                 ret = -errno;
741
742         buf[i] = 0;
743         return i ?: ret;
744 }
745
746
747 int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
748 {
749         char buf[MAX_BUF_LEN];
750         int count, buflen, result;
751
752         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
753         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
754         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
755         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
756         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
757         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
758         sprintf(buf + strlen(buf), "\r\n");
759
760         vpninfo->progress(vpninfo, PRG_INFO, "Requesting proxy connection to %s:%d\n",
761                           vpninfo->hostname, vpninfo->port);
762
763         buflen = strlen(buf);
764         for (count = 0; count < buflen; ) {
765                 int i = write(ssl_sock, buf + count, buflen - count);
766                 if (i < 0) {
767                         i = -errno;
768                         vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
769                                           strerror(errno));
770                         return i;
771                 }
772                 count += i;
773         }
774
775         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
776                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
777                 return -EIO;
778         }
779
780         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
781             buf[8] != ' ' || !(result = atoi(buf+9))) {
782                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
783                                   buf);
784                 return -EINVAL;
785         }
786
787         if (result != 200) {
788                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
789                                   buf);
790                 return -EIO;
791         }
792
793         while ((count = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
794                 if (count < 0) {
795                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
796                         return -EIO;
797                 }
798                 vpninfo->progress(vpninfo, PRG_ERR,
799                                   "Unexpected continuation line after CONNECT response: '%s'\n",
800                                   buf);
801         }
802
803         return 0;
804 }