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