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