Fix handling of 'HTTP/1.1 100 Continue' response
[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         /* 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) {
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                                 if (vpninfo->https_ssl) {
605                                         SSL_free(vpninfo->https_ssl);
606                                         vpninfo->https_ssl = NULL;
607                                         close(vpninfo->ssl_fd);
608                                         vpninfo->ssl_fd = -1;
609                                 }
610
611                                 for (opt = vpninfo->cookies; opt; opt = next) {
612                                         next = opt->next;
613
614                                         free(opt->option);
615                                         free(opt->value);
616                                         free(opt);
617                                 }
618                                 vpninfo->cookies = NULL;
619                         } else
620                                 free(host);
621
622                         free(vpninfo->redirect_url);
623                         vpninfo->redirect_url = NULL;
624
625                         goto retry;
626                 } else if (vpninfo->redirect_url[0] == '/') {
627                         /* Absolute redirect within same host */
628                         free(vpninfo->urlpath);
629                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
630                         free(vpninfo->redirect_url);
631                         vpninfo->redirect_url = NULL;
632                         goto retry;
633                 } else {
634                         vpninfo->progress(vpninfo, PRG_ERR, "Relative redirect (to '%s') not supported\n",
635                                 vpninfo->redirect_url);
636                         return -EINVAL;
637                 }
638         }
639
640         if (vpninfo->csd_stuburl) {
641                 /* This is the CSD stub script, which we now need to run */
642                 result = run_csd_script(vpninfo, buf, buflen);
643                 if (result)
644                         return result;
645
646                 /* Now we'll be redirected to the waiturl */
647                 goto retry;
648         }
649         if (strncmp(buf, "<?xml", 5)) {
650                 /* Not XML? Perhaps it's HTML with a refresh... */
651                 if (strcasestr(buf, "http-equiv=\"refresh\"")) {
652                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
653                                           vpninfo->urlpath);
654                         sleep(1);
655                         goto retry;
656                 }
657                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
658                 return -EINVAL;
659         }
660         request_body[0] = 0;
661         result = parse_xml_response(vpninfo, buf, request_body, sizeof(request_body),
662                                     &method, &request_body_type);
663         if (!result)
664                 goto redirect;
665
666         if (result != 2)
667                 return result;
668         /* A return value of 2 means the XML form indicated
669            success. We _should_ have a cookie... */
670
671         for (opt = vpninfo->cookies; opt; opt = opt->next) {
672
673                 if (!strcmp(opt->option, "webvpn"))
674                         vpninfo->cookie = opt->value;
675                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
676                         char *tok = opt->value;
677                         char *bu = NULL, *fu = NULL, *sha = NULL;
678
679                         do {
680                                 if (tok != opt->value)
681                                         *(tok++) = 0;
682
683                                 if (!strncmp(tok, "bu:", 3))
684                                         bu = tok + 3;
685                                 else if (!strncmp(tok, "fu:", 3))
686                                         fu = tok + 3;
687                                 else if (!strncmp(tok, "fh:", 3)) {
688                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
689                                                          SHA_DIGEST_LENGTH * 2))
690                                                 break;
691                                         sha = tok + 3;
692                                 }
693                         } while ((tok = strchr(tok, '&')));
694
695                         if (bu && fu && sha)
696                                 fetch_config(vpninfo, bu, fu, sha);
697                 }
698         }
699         if (vpninfo->csd_scriptname) {
700                 unlink(vpninfo->csd_scriptname);
701                 free(vpninfo->csd_scriptname);
702                 vpninfo->csd_scriptname = NULL;
703         }
704         return 0;
705 }
706
707 char *openconnect_create_useragent(char *base)
708 {
709         char *uagent;
710
711         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
712                 return NULL;
713
714         return uagent;
715 }
716
717 static int proxy_gets(int fd, char *buf, size_t len)
718 {
719         int i = 0;
720         int ret;
721
722         if (len < 2)
723                 return -EINVAL;
724
725         while ( (ret = read(fd, buf + i, 1)) == 1) {
726                 if (buf[i] == '\n') {
727                         buf[i] = 0;
728                         if (i && buf[i-1] == '\r') {
729                                 buf[i-1] = 0;
730                                 i--;
731                         }
732                         return i;
733                 }
734                 i++;
735
736                 if (i >= len - 1) {
737                         buf[i] = 0;
738                         return i;
739                 }
740         }
741         if (ret < 0)
742                 ret = -errno;
743
744         buf[i] = 0;
745         return i ?: ret;
746 }
747
748 static int proxy_write(int fd, unsigned char *buf, size_t len)
749 {
750         size_t count;
751         
752         for (count = 0; count < len; ) {
753                 int i = write(fd, buf + count, len - count);
754                 if (i < 0)
755                         return -errno;
756
757                 count += i;
758         }
759         return 0;
760 }
761
762 static int proxy_read(int fd, unsigned char *buf, size_t len)
763 {
764         size_t count;
765
766         for (count = 0; count < len; ) {
767                 int i = read(fd, buf + count, len - count);
768                 if (i < 0)
769                         return -errno;
770
771                 count += i;
772         }
773         return 0;
774 }
775
776 static const char *socks_errors[] = {
777         "request granted",
778         "general failure",
779         "connection not allowed by ruleset",
780         "network unreachable",
781         "host unreachable",
782         "connection refused by destination host",
783         "TTL expired",
784         "command not supported / protocol error",
785         "address type not supported"
786 };
787
788 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
789 {
790         unsigned char buf[1024];
791         int i;
792
793         buf[0] = 5; /* SOCKS version */
794         buf[1] = 1; /* # auth methods */
795         buf[2] = 0; /* No auth supported */
796
797         if ((i = proxy_write(ssl_sock, buf, 3))) {
798                 vpninfo->progress(vpninfo, PRG_ERR,
799                                   "Error writing auth request to SOCKS proxy: %s\n",
800                                   strerror(-i));
801                 return i;
802         }
803         
804         if ((i = proxy_read(ssl_sock, buf, 2))) {
805                 vpninfo->progress(vpninfo, PRG_ERR,
806                                   "Error reading auth response from SOCKS proxy: %s\n",
807                                   strerror(-i));
808                 return i;
809         }
810         if (buf[0] != 5) {
811                 vpninfo->progress(vpninfo, PRG_ERR,
812                                   "Unexpected auth response from SOCKS proxy: %02x %02x\n",
813                                   buf[0], buf[1]);
814                 return -EIO;
815         }
816         if (buf[1]) {
817         socks_err:
818                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
819                         vpninfo->progress(vpninfo, PRG_ERR,
820                                           "SOCKS proxy error %02x: %s\n",
821                                           buf[1], socks_errors[buf[1]]);
822                 else
823                         vpninfo->progress(vpninfo, PRG_ERR,
824                                           "SOCKS proxy error %02x\n",
825                                           buf[1]);
826                 return -EIO;
827         }
828
829         vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
830                           vpninfo->hostname, vpninfo->port);
831
832         buf[0] = 5; /* SOCKS version */
833         buf[1] = 1; /* CONNECT */
834         buf[2] = 0; /* Reserved */
835         buf[3] = 3; /* Address type is domain name */
836         buf[4] = strlen(vpninfo->hostname);
837         strcpy((char *)buf + 5, vpninfo->hostname);
838         i = strlen(vpninfo->hostname) + 5;
839         buf[i++] = vpninfo->port >> 8;
840         buf[i++] = vpninfo->port & 0xff;
841
842         if ((i = proxy_write(ssl_sock, buf, i))) {
843                 vpninfo->progress(vpninfo, PRG_ERR,
844                                   "Error writing connect request to SOCKS proxy: %s\n",
845                                   strerror(-i));
846                 return i;
847         }
848         /* Read 5 bytes -- up to and including the first byte of the returned
849            address (which might be the length byte of a domain name) */
850         if ((i = proxy_read(ssl_sock, buf, 5))) {
851                 vpninfo->progress(vpninfo, PRG_ERR,
852                                   "Error reading connect response from SOCKS proxy: %s\n",
853                                   strerror(-i));
854                 return i;
855         }
856         if (buf[0] != 5) {
857                 vpninfo->progress(vpninfo, PRG_ERR,
858                                   "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
859                                   buf[0], buf[1]);
860                 return -EIO;
861         }
862         if (buf[1])
863                 goto socks_err;
864
865         /* Connect responses contain an address */
866         switch(buf[3]) {
867         case 1: /* Legacy IP */
868                 i = 5;
869                 break;
870         case 3: /* Domain name */
871                 i = buf[4] + 2;
872                 break;
873         case 4: /* IPv6 */
874                 i = 17;
875                 break;
876         default:
877                 vpninfo->progress(vpninfo, PRG_ERR,
878                                   "Unexpected address type %02x in SOCKS connect response\n",
879                                   buf[3]);
880                 return -EIO;
881         }
882
883         if ((i = proxy_read(ssl_sock, buf, i))) {
884                 vpninfo->progress(vpninfo, PRG_ERR,
885                                   "Error reading connect response from SOCKS proxy: %s\n",
886                                   strerror(-i));
887                 return i;
888         }
889         return 0;
890 }
891
892 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
893 {
894         char buf[MAX_BUF_LEN];
895         int buflen, result;
896
897         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
898         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
899         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
900         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
901         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
902         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
903         sprintf(buf + strlen(buf), "\r\n");
904
905         vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
906                           vpninfo->hostname, vpninfo->port);
907
908         if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
909                 result = -errno;
910                 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
911                                   strerror(errno));
912                 return result;
913         }
914
915         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
916                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
917                 return -EIO;
918         }
919
920         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
921             buf[8] != ' ' || !(result = atoi(buf+9))) {
922                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
923                                   buf);
924                 return -EINVAL;
925         }
926
927         if (result != 200) {
928                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
929                                   buf);
930                 return -EIO;
931         }
932
933         while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
934                 if (buflen < 0) {
935                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
936                         return -EIO;
937                 }
938                 vpninfo->progress(vpninfo, PRG_ERR,
939                                   "Unexpected continuation line after CONNECT response: '%s'\n",
940                                   buf);
941         }
942
943         return 0;
944 }
945
946 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
947 {
948         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
949                 return process_http_proxy(vpninfo, ssl_sock);
950         
951         if (!strcmp(vpninfo->proxy_type, "socks") ||
952             !strcmp(vpninfo->proxy_type, "socks5"))
953                 return process_socks_proxy(vpninfo, ssl_sock);
954
955         vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
956                                   vpninfo->proxy_type);
957         return -EIO;
958 }
959
960 int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
961 {
962         char *url = strdup(proxy);
963         int ret;
964
965         if (!url)
966                 return -ENOMEM;
967
968         free(vpninfo->proxy_type);
969         vpninfo->proxy_type = NULL;
970         free(vpninfo->proxy);
971         vpninfo->proxy = NULL;
972
973         ret = parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
974                         &vpninfo->proxy_port, NULL, 80);
975         if (ret)
976                 goto out;
977
978         if (vpninfo->proxy_type &&
979             strcmp(vpninfo->proxy_type, "http") &&
980             strcmp(vpninfo->proxy_type, "socks") &&
981             strcmp(vpninfo->proxy_type, "socks5")) {
982                 vpninfo->progress(vpninfo, PRG_ERR,
983                                   "Only http or socks(5) proxies supported\n");
984                 free(vpninfo->proxy_type);
985                 vpninfo->proxy_type = NULL;
986                 free(vpninfo->proxy);
987                 vpninfo->proxy = NULL;
988                 return -EINVAL;
989         }
990  out:
991         free(url);
992         return ret;
993 }