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