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