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