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