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