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