Add openconnect_sha1() function and use it instead of using OpenSSL directly
[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[SHA1_SIZE];
355         char local_sha1_ascii[(SHA1_SIZE * 2)+1];
356         int i;
357
358         sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
359         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
360         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
361         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
362         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
363
364         if (vpninfo->cookies) {
365                 sprintf(buf + strlen(buf),  "Cookie: ");
366                 for (opt = vpninfo->cookies; opt; opt = opt->next)
367                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
368                                       opt->value, opt->next ? "; " : "\r\n");
369         }
370         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
371
372         if (openconnect_SSL_write(vpninfo, buf, strlen(buf))) {
373                 vpn_progress(vpninfo, PRG_ERR,
374                              _("Failed to send GET request for new config\n"));
375                 return -EIO;
376         }
377
378         buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
379         if (buflen < 0) {
380                 /* We'll already have complained about whatever offended us */
381                 return -EINVAL;
382         }
383
384         if (result != 200) {
385                 free(config_buf);
386                 return -EINVAL;
387         }
388
389         openconnect_sha1(local_sha1_bin, config_buf, buflen);
390
391         for (i = 0; i < SHA1_SIZE; i++)
392                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
393
394         if (strcasecmp(server_sha1, local_sha1_ascii)) {
395                 vpn_progress(vpninfo, PRG_ERR,
396                              _("Downloaded config file did not match intended SHA1\n"));
397                 free(config_buf);
398                 return -EINVAL;
399         }
400
401         result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
402         free(config_buf);
403         return result;
404 }
405
406 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
407 {
408         char fname[16];
409         int fd, ret;
410
411         if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
412                 vpn_progress(vpninfo, PRG_ERR,
413                              _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
414                                "This facility is disabled by default for security reasons, so you may wish to enable it."));
415                 return -EPERM;
416         }
417
418 #ifndef __linux__
419         vpn_progress(vpninfo, PRG_INFO,
420                      _("Trying to run Linux CSD trojan script."));
421 #endif
422
423         sprintf(fname, "/tmp/csdXXXXXX");
424         fd = mkstemp(fname);
425         if (fd < 0) {
426                 int err = -errno;
427                 vpn_progress(vpninfo, PRG_ERR,
428                              _("Failed to open temporary CSD script file: %s\n"),
429                              strerror(errno));
430                 return err;
431         }
432
433         ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
434         if (ret) {
435                 vpn_progress(vpninfo, PRG_ERR,
436                              _("Failed to write temporary CSD script file: %s\n"),
437                              strerror(ret));
438                 return ret;
439         }
440         fchmod(fd, 0755);
441         close(fd);
442
443         if (!fork()) {
444                 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
445                 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
446                 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
447                 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
448                 char *csd_argv[32];
449                 int i = 0;
450
451                 if (vpninfo->uid_csd != getuid()) {
452                         struct passwd *pw;
453
454                         if (setuid(vpninfo->uid_csd)) {
455                                 fprintf(stderr, _("Failed to set uid %ld\n"),
456                                         (long)vpninfo->uid_csd);
457                                 exit(1);
458                         }
459                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
460                                 fprintf(stderr, _("Invalid user uid=%ld\n"),
461                                         (long)vpninfo->uid_csd);
462                                 exit(1);
463                         }
464                         setenv("HOME", pw->pw_dir, 1);
465                         if (chdir(pw->pw_dir)) {
466                                 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
467                                         pw->pw_dir, strerror(errno));
468                                 exit(1);
469                         }
470                 }
471                 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
472                         fprintf(stderr, _("Warning: you are running insecure "
473                                           "CSD code with root privileges\n"
474                                           "\t Use command line option \"--csd-user\"\n"));
475                 }
476                 if (vpninfo->uid_csd_given == 2) {             
477                         /* The NM tool really needs not to get spurious output
478                            on stdout, which the CSD trojan spews. */
479                         dup2(2, 1);
480                 }
481                 if (vpninfo->csd_wrapper)
482                         csd_argv[i++] = vpninfo->csd_wrapper;
483                 csd_argv[i++] = fname;
484                 csd_argv[i++]= (char *)"-ticket";
485                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
486                         return -ENOMEM;
487                 csd_argv[i++]= (char *)"-stub";
488                 csd_argv[i++]= (char *)"\"0\"";
489                 csd_argv[i++]= (char *)"-group";
490                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
491                         return -ENOMEM;
492
493                 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
494                 if (ccert)
495                         get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
496                 else
497                         ccertbuf[0] = 0;
498
499                 csd_argv[i++]= (char *)"-certhash";
500                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
501                         return -ENOMEM;
502                 csd_argv[i++]= (char *)"-url";
503                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
504                         return -ENOMEM;
505                 /* WTF would it want to know this for? */
506                 csd_argv[i++]= (char *)"-vpnclient";
507                 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
508                 csd_argv[i++]= (char *)"-connect";
509                 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
510                         return -ENOMEM;
511                 csd_argv[i++]= (char *)"-connectparam";
512                 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
513                         return -ENOMEM;
514                 csd_argv[i++]= (char *)"-langselen";
515                 csd_argv[i++] = NULL;
516
517                 execv(csd_argv[0], csd_argv);
518                 vpn_progress(vpninfo, PRG_ERR,
519                              _("Failed to exec CSD script %s\n"), csd_argv[0]);
520                 exit(1);
521         }
522
523         free(vpninfo->csd_stuburl);
524         vpninfo->csd_stuburl = NULL;
525         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
526                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
527         vpninfo->csd_waiturl = NULL;
528         vpninfo->csd_scriptname = strdup(fname);
529
530         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
531
532         return 0;
533 }
534
535 #ifndef HAVE_STRCASESTR
536 static char *openconnect__strcasestr(const char *haystack, const char *needle)
537 {
538         int hlen = strlen(haystack);
539         int nlen = strlen(needle);
540         int i, j;
541
542         for (i = 0; i < hlen - nlen + 1; i++) {
543                 for (j = 0; j < nlen; j++) {
544                         if (tolower(haystack[i + j]) != 
545                             tolower(needle[j]))
546                                 break;
547                 }
548                 if (j == nlen)
549                         return (char *)haystack + i;
550         }
551         return NULL;
552 }
553 #define strcasestr openconnect__strcasestr
554 #endif
555
556
557 int internal_parse_url(char *url, char **res_proto, char **res_host,
558                        int *res_port, char **res_path, int default_port)
559 {
560         char *proto = url;
561         char *host, *path, *port_str;
562         int port;
563
564         host = strstr(url, "://");
565         if (host) {
566                 *host = 0;
567                 host += 3;
568
569                 if (!strcasecmp(proto, "https"))
570                         port = 443;
571                 else if (!strcasecmp(proto, "http"))
572                         port = 80;
573                 else if (!strcasecmp(proto, "socks") ||
574                          !strcasecmp(proto, "socks4") ||
575                          !strcasecmp(proto, "socks5"))
576                         port = 1080;
577                 else
578                         return -EPROTONOSUPPORT;
579         } else {
580                 if (default_port) {
581                         proto = NULL;
582                         port = default_port;
583                         host = url;
584                 } else
585                         return -EINVAL;
586         }
587
588         path = strchr(host, '/');
589         if (path)
590                 *(path++) = 0;
591
592         port_str = strrchr(host, ':');
593         if (port_str) {
594                 char *end;
595                 int new_port = strtol(port_str + 1, &end, 10);
596
597                 if (!*end) {
598                         *port_str = 0;
599                         port = new_port;
600                 }
601         }
602
603         if (res_proto)
604                 *res_proto = proto ? strdup(proto) : NULL;
605         if (res_host)
606                 *res_host = strdup(host);
607         if (res_port)
608                 *res_port = port;
609         if (res_path)
610                 *res_path = (path && *path) ? strdup(path) : NULL;
611
612         /* Undo the damage we did to the original string */
613         if (port_str)
614                 *(port_str) = ':';
615         if (path)
616                 *(path - 1) = '/';
617         if (proto)
618                 *(host - 3) = ':';
619         return 0;
620 }
621
622 /* Return value:
623  *  < 0, on error
624  *  = 0, no cookie (user cancel)
625  *  = 1, obtained cookie
626  */
627 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
628 {
629         struct vpn_option *opt, *next;
630         char buf[MAX_BUF_LEN];
631         char *form_buf = NULL;
632         int result, buflen;
633         char request_body[2048];
634         const char *request_body_type = NULL;
635         const char *method = "GET";
636
637  retry:
638         if (form_buf) {
639                 free(form_buf);
640                 form_buf = NULL;
641         }
642         if (openconnect_open_https(vpninfo)) {
643                 vpn_progress(vpninfo, PRG_ERR,
644                              _("Failed to open HTTPS connection to %s\n"),
645                              vpninfo->hostname);
646                 return -EINVAL;
647         }
648
649         /*
650          * It would be nice to use cURL for this, but we really need to guarantee
651          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
652          * to have any way to let us provide our own socket read/write functions.
653          * We can only provide a socket _open_ function. Which would require having
654          * a socketpair() and servicing the "other" end of it.
655          *
656          * So we process the HTTP for ourselves...
657          */
658         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
659         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
660         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
661         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
662         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
663
664         if (vpninfo->cookies) {
665                 sprintf(buf + strlen(buf),  "Cookie: ");
666                 for (opt = vpninfo->cookies; opt; opt = opt->next)
667                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
668                                       opt->value, opt->next ? "; " : "\r\n");
669         }
670         if (request_body_type) {
671                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
672                               request_body_type);
673                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
674                               strlen(request_body));
675         }
676         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
677         if (request_body_type)
678                 sprintf(buf + strlen(buf), "%s", request_body);
679
680         if (vpninfo->port == 443)
681                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
682                              method, vpninfo->hostname,
683                              vpninfo->urlpath ?: "");
684         else
685                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
686                              method, vpninfo->hostname, vpninfo->port,
687                              vpninfo->urlpath ?: "");
688
689         result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
690         if (result < 0)
691                 return result;
692
693         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
694         if (buflen < 0) {
695                 /* We'll already have complained about whatever offended us */
696                 return buflen;
697         }
698
699         if (result != 200 && vpninfo->redirect_url) {
700         redirect:
701                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
702                         /* New host. Tear down the existing connection and make a new one */
703                         char *host;
704                         int port;
705                         int ret;
706
707                         free(vpninfo->urlpath);
708                         vpninfo->urlpath = NULL;
709
710                         ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
711                         if (ret) {
712                                 vpn_progress(vpninfo, PRG_ERR,
713                                              _("Failed to parse redirected URL '%s': %s\n"),
714                                              vpninfo->redirect_url, strerror(-ret));
715                                 free(vpninfo->redirect_url);
716                                 vpninfo->redirect_url = NULL;
717                                 free(form_buf);
718                                 return ret;
719                         }
720
721                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
722                                 free(vpninfo->hostname);
723                                 vpninfo->hostname = host;
724                                 vpninfo->port = port;
725
726                                 /* Kill the existing connection, and a new one will happen */
727                                 free(vpninfo->peer_addr);
728                                 vpninfo->peer_addr = NULL;
729                                 openconnect_close_https(vpninfo);
730
731                                 for (opt = vpninfo->cookies; opt; opt = next) {
732                                         next = opt->next;
733
734                                         free(opt->option);
735                                         free(opt->value);
736                                         free(opt);
737                                 }
738                                 vpninfo->cookies = NULL;
739                         } else
740                                 free(host);
741
742                         free(vpninfo->redirect_url);
743                         vpninfo->redirect_url = NULL;
744
745                         goto retry;
746                 } else if (strstr(vpninfo->redirect_url, "://")) {
747                         vpn_progress(vpninfo, PRG_ERR,
748                                      _("Cannot follow redirection to non-https URL '%s'\n"),
749                                      vpninfo->redirect_url);
750                         free(vpninfo->redirect_url);
751                         vpninfo->redirect_url = NULL;
752                         free(form_buf);
753                         return -EINVAL;
754                 } else if (vpninfo->redirect_url[0] == '/') {
755                         /* Absolute redirect within same host */
756                         free(vpninfo->urlpath);
757                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
758                         free(vpninfo->redirect_url);
759                         vpninfo->redirect_url = NULL;
760                         goto retry;
761                 } else {
762                         char *lastslash = NULL;
763                         if (vpninfo->urlpath)
764                                 lastslash = strrchr(vpninfo->urlpath, '/');
765                         if (!lastslash) {
766                                 free(vpninfo->urlpath);
767                                 vpninfo->urlpath = vpninfo->redirect_url;
768                                 vpninfo->redirect_url = NULL;
769                         } else {
770                                 char *oldurl = vpninfo->urlpath;
771                                 *lastslash = 0;
772                                 vpninfo->urlpath = NULL;
773                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
774                                              oldurl, vpninfo->redirect_url) == -1) {
775                                         int err = -errno;
776                                         vpn_progress(vpninfo, PRG_ERR,
777                                                      _("Allocating new path for relative redirect failed: %s\n"),
778                                                      strerror(-err));
779                                         return err;
780                                 }
781                                 free(oldurl);
782                                 free(vpninfo->redirect_url);
783                                 vpninfo->redirect_url = NULL;
784                         }
785                         goto retry;
786                 }
787         }
788         if (!form_buf || result != 200) {
789                 vpn_progress(vpninfo, PRG_ERR,
790                              _("Unexpected %d result from server\n"),
791                              result);
792                 free(form_buf);
793                 return -EINVAL;
794         }
795         if (vpninfo->csd_stuburl) {
796                 /* This is the CSD stub script, which we now need to run */
797                 result = run_csd_script(vpninfo, form_buf, buflen);
798                 if (result) {
799                         free(form_buf);
800                         return result;
801                 }
802
803                 /* Now we'll be redirected to the waiturl */
804                 goto retry;
805         }
806         if (strncmp(form_buf, "<?xml", 5)) {
807                 /* Not XML? Perhaps it's HTML with a refresh... */
808                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
809                         vpn_progress(vpninfo, PRG_INFO,
810                                      _("Refreshing %s after 1 second...\n"),
811                                      vpninfo->urlpath);
812                         sleep(1);
813                         goto retry;
814                 }
815                 vpn_progress(vpninfo, PRG_ERR,
816                              _("Unknown response from server\n"));
817                 free(form_buf);
818                 return -EINVAL;
819         }
820         request_body[0] = 0;
821         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
822                                     &method, &request_body_type);
823
824         if (!result)
825                 goto redirect;
826
827         free(form_buf);
828
829         if (result != 2)
830                 return result;
831
832         /* A return value of 2 means the XML form indicated
833            success. We _should_ have a cookie... */
834
835         for (opt = vpninfo->cookies; opt; opt = opt->next) {
836
837                 if (!strcmp(opt->option, "webvpn"))
838                         vpninfo->cookie = opt->value;
839                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
840                         char *tok = opt->value;
841                         char *bu = NULL, *fu = NULL, *sha = NULL;
842
843                         do {
844                                 if (tok != opt->value)
845                                         *(tok++) = 0;
846
847                                 if (!strncmp(tok, "bu:", 3))
848                                         bu = tok + 3;
849                                 else if (!strncmp(tok, "fu:", 3))
850                                         fu = tok + 3;
851                                 else if (!strncmp(tok, "fh:", 3)) {
852                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
853                                                          SHA1_SIZE * 2))
854                                                 break;
855                                         sha = tok + 3;
856                                 }
857                         } while ((tok = strchr(tok, '&')));
858
859                         if (bu && fu && sha)
860                                 fetch_config(vpninfo, bu, fu, sha);
861                 }
862         }
863         if (vpninfo->csd_scriptname) {
864                 unlink(vpninfo->csd_scriptname);
865                 free(vpninfo->csd_scriptname);
866                 vpninfo->csd_scriptname = NULL;
867         }
868         return 0;
869 }
870
871 char *openconnect_create_useragent(const char *base)
872 {
873         char *uagent;
874
875         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
876                 return NULL;
877
878         return uagent;
879 }
880
881 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
882                       char *buf, size_t len)
883 {
884         int i = 0;
885         int ret;
886
887         if (len < 2)
888                 return -EINVAL;
889
890         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
891                 if (buf[i] == '\n') {
892                         buf[i] = 0;
893                         if (i && buf[i-1] == '\r') {
894                                 buf[i-1] = 0;
895                                 i--;
896                         }
897                         return i;
898                 }
899                 i++;
900
901                 if (i >= len - 1) {
902                         buf[i] = 0;
903                         return i;
904                 }
905         }
906         buf[i] = 0;
907         return i ?: ret;
908 }
909
910 static int proxy_write(struct openconnect_info *vpninfo, int fd,
911                        unsigned char *buf, size_t len)
912 {
913         size_t count;
914
915         for (count = 0; count < len; ) {
916                 fd_set rd_set, wr_set;
917                 int maxfd = fd;
918                 int i;
919
920                 FD_ZERO(&wr_set);
921                 FD_ZERO(&rd_set);
922                 FD_SET(fd, &wr_set);
923                 if (vpninfo->cancel_fd != -1) {
924                         FD_SET(vpninfo->cancel_fd, &rd_set);
925                         if (vpninfo->cancel_fd > fd)
926                                 maxfd = vpninfo->cancel_fd;
927                 }
928
929                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
930                 if (vpninfo->cancel_fd != -1 &&
931                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
932                         return -EINTR;
933
934                 /* Not that this should ever be able to happen... */
935                 if (!FD_ISSET(fd, &wr_set))
936                         continue;
937
938                 i = write(fd, buf + count, len - count);
939                 if (i < 0)
940                         return -errno;
941
942                 count += i;
943         }
944         return 0;
945 }
946
947 static int proxy_read(struct openconnect_info *vpninfo, int fd,
948                       unsigned char *buf, size_t len)
949 {
950         size_t count;
951
952         for (count = 0; count < len; ) {
953                 fd_set rd_set;
954                 int maxfd = fd;
955                 int i;
956
957                 FD_ZERO(&rd_set);
958                 FD_SET(fd, &rd_set);
959                 if (vpninfo->cancel_fd != -1) {
960                         FD_SET(vpninfo->cancel_fd, &rd_set);
961                         if (vpninfo->cancel_fd > fd)
962                                 maxfd = vpninfo->cancel_fd;
963                 }
964
965                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
966                 if (vpninfo->cancel_fd != -1 &&
967                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
968                         return -EINTR;
969
970                 /* Not that this should ever be able to happen... */
971                 if (!FD_ISSET(fd, &rd_set))
972                         continue;
973
974                 i = read(fd, buf + count, len - count);
975                 if (i < 0)
976                         return -errno;
977
978                 count += i;
979         }
980         return 0;
981 }
982
983 static const char *socks_errors[] = {
984         N_("request granted"),
985         N_("general failure"),
986         N_("connection not allowed by ruleset"),
987         N_("network unreachable"),
988         N_("host unreachable"),
989         N_("connection refused by destination host"),
990         N_("TTL expired"),
991         N_("command not supported / protocol error"),
992         N_("address type not supported")
993 };
994
995 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
996 {
997         unsigned char buf[1024];
998         int i;
999
1000         buf[0] = 5; /* SOCKS version */
1001         buf[1] = 1; /* # auth methods */
1002         buf[2] = 0; /* No auth supported */
1003
1004         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1005                 vpn_progress(vpninfo, PRG_ERR,
1006                              _("Error writing auth request to SOCKS proxy: %s\n"),
1007                              strerror(-i));
1008                 return i;
1009         }
1010         
1011         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1012                 vpn_progress(vpninfo, PRG_ERR,
1013                              _("Error reading auth response from SOCKS proxy: %s\n"),
1014                              strerror(-i));
1015                 return i;
1016         }
1017         if (buf[0] != 5) {
1018                 vpn_progress(vpninfo, PRG_ERR,
1019                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1020                              buf[0], buf[1]);
1021                 return -EIO;
1022         }
1023         if (buf[1]) {
1024         socks_err:
1025                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1026                         vpn_progress(vpninfo, PRG_ERR,
1027                                      _("SOCKS proxy error %02x: %s\n"),
1028                                      buf[1], _(socks_errors[buf[1]]));
1029                 else
1030                         vpn_progress(vpninfo, PRG_ERR,
1031                                      _("SOCKS proxy error %02x\n"),
1032                                      buf[1]);
1033                 return -EIO;
1034         }
1035
1036         vpn_progress(vpninfo, PRG_INFO,
1037                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1038                      vpninfo->hostname, vpninfo->port);
1039
1040         buf[0] = 5; /* SOCKS version */
1041         buf[1] = 1; /* CONNECT */
1042         buf[2] = 0; /* Reserved */
1043         buf[3] = 3; /* Address type is domain name */
1044         buf[4] = strlen(vpninfo->hostname);
1045         strcpy((char *)buf + 5, vpninfo->hostname);
1046         i = strlen(vpninfo->hostname) + 5;
1047         buf[i++] = vpninfo->port >> 8;
1048         buf[i++] = vpninfo->port & 0xff;
1049
1050         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1051                 vpn_progress(vpninfo, PRG_ERR,
1052                              _("Error writing connect request to SOCKS proxy: %s\n"),
1053                              strerror(-i));
1054                 return i;
1055         }
1056         /* Read 5 bytes -- up to and including the first byte of the returned
1057            address (which might be the length byte of a domain name) */
1058         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1059                 vpn_progress(vpninfo, PRG_ERR,
1060                              _("Error reading connect response from SOCKS proxy: %s\n"),
1061                              strerror(-i));
1062                 return i;
1063         }
1064         if (buf[0] != 5) {
1065                 vpn_progress(vpninfo, PRG_ERR,
1066                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1067                              buf[0], buf[1]);
1068                 return -EIO;
1069         }
1070         if (buf[1])
1071                 goto socks_err;
1072
1073         /* Connect responses contain an address */
1074         switch(buf[3]) {
1075         case 1: /* Legacy IP */
1076                 i = 5;
1077                 break;
1078         case 3: /* Domain name */
1079                 i = buf[4] + 2;
1080                 break;
1081         case 4: /* IPv6 */
1082                 i = 17;
1083                 break;
1084         default:
1085                 vpn_progress(vpninfo, PRG_ERR,
1086                              _("Unexpected address type %02x in SOCKS connect response\n"),
1087                              buf[3]);
1088                 return -EIO;
1089         }
1090
1091         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1092                 vpn_progress(vpninfo, PRG_ERR,
1093                              _("Error reading connect response from SOCKS proxy: %s\n"),
1094                              strerror(-i));
1095                 return i;
1096         }
1097         return 0;
1098 }
1099
1100 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1101 {
1102         char buf[MAX_BUF_LEN];
1103         int buflen, result;
1104
1105         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1106         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1107         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1108         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1109         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1110         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1111         sprintf(buf + strlen(buf), "\r\n");
1112
1113         vpn_progress(vpninfo, PRG_INFO,
1114                      _("Requesting HTTP proxy connection to %s:%d\n"),
1115                      vpninfo->hostname, vpninfo->port);
1116
1117         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1118         if (result) {
1119                 vpn_progress(vpninfo, PRG_ERR,
1120                              _("Sending proxy request failed: %s\n"),
1121                              strerror(-result));
1122                 return result;
1123         }
1124
1125         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1126                 vpn_progress(vpninfo, PRG_ERR,
1127                              _("Error fetching proxy response\n"));
1128                 return -EIO;
1129         }
1130
1131         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1132             buf[8] != ' ' || !(result = atoi(buf+9))) {
1133                 vpn_progress(vpninfo, PRG_ERR,
1134                              _("Failed to parse proxy response '%s'\n"), buf);
1135                 return -EINVAL;
1136         }
1137
1138         if (result != 200) {
1139                 vpn_progress(vpninfo, PRG_ERR,
1140                              _("Proxy CONNECT request failed: %s\n"), buf);
1141                 return -EIO;
1142         }
1143
1144         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1145                 if (buflen < 0) {
1146                         vpn_progress(vpninfo, PRG_ERR,
1147                                      _("Failed to read proxy response\n"));
1148                         return -EIO;
1149                 }
1150                 vpn_progress(vpninfo, PRG_ERR,
1151                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1152                              buf);
1153         }
1154
1155         return 0;
1156 }
1157
1158 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1159 {
1160         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1161                 return process_http_proxy(vpninfo, ssl_sock);
1162         
1163         if (!strcmp(vpninfo->proxy_type, "socks") ||
1164             !strcmp(vpninfo->proxy_type, "socks5"))
1165                 return process_socks_proxy(vpninfo, ssl_sock);
1166
1167         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1168                      vpninfo->proxy_type);
1169         return -EIO;
1170 }
1171
1172 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1173 {
1174         char *url = proxy;
1175         int ret;
1176
1177         if (!url)
1178                 return -ENOMEM;
1179
1180         free(vpninfo->proxy_type);
1181         vpninfo->proxy_type = NULL;
1182         free(vpninfo->proxy);
1183         vpninfo->proxy = NULL;
1184
1185         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1186                                  &vpninfo->proxy_port, NULL, 80);
1187         if (ret)
1188                 goto out;
1189
1190         if (vpninfo->proxy_type &&
1191             strcmp(vpninfo->proxy_type, "http") &&
1192             strcmp(vpninfo->proxy_type, "socks") &&
1193             strcmp(vpninfo->proxy_type, "socks5")) {
1194                 vpn_progress(vpninfo, PRG_ERR,
1195                              _("Only http or socks(5) proxies supported\n"));
1196                 free(vpninfo->proxy_type);
1197                 vpninfo->proxy_type = NULL;
1198                 free(vpninfo->proxy);
1199                 vpninfo->proxy = NULL;
1200                 return -EINVAL;
1201         }
1202  out:
1203         free(url);
1204         return ret;
1205 }