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