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