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