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