Move strcasestr() implementation to compat.c
[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 int internal_parse_url(char *url, char **res_proto, char **res_host,
530                        int *res_port, char **res_path, int default_port)
531 {
532         char *proto = url;
533         char *host, *path, *port_str;
534         int port;
535
536         host = strstr(url, "://");
537         if (host) {
538                 *host = 0;
539                 host += 3;
540
541                 if (!strcasecmp(proto, "https"))
542                         port = 443;
543                 else if (!strcasecmp(proto, "http"))
544                         port = 80;
545                 else if (!strcasecmp(proto, "socks") ||
546                          !strcasecmp(proto, "socks4") ||
547                          !strcasecmp(proto, "socks5"))
548                         port = 1080;
549                 else
550                         return -EPROTONOSUPPORT;
551         } else {
552                 if (default_port) {
553                         proto = NULL;
554                         port = default_port;
555                         host = url;
556                 } else
557                         return -EINVAL;
558         }
559
560         path = strchr(host, '/');
561         if (path)
562                 *(path++) = 0;
563
564         port_str = strrchr(host, ':');
565         if (port_str) {
566                 char *end;
567                 int new_port = strtol(port_str + 1, &end, 10);
568
569                 if (!*end) {
570                         *port_str = 0;
571                         port = new_port;
572                 }
573         }
574
575         if (res_proto)
576                 *res_proto = proto ? strdup(proto) : NULL;
577         if (res_host)
578                 *res_host = strdup(host);
579         if (res_port)
580                 *res_port = port;
581         if (res_path)
582                 *res_path = (path && *path) ? strdup(path) : NULL;
583
584         /* Undo the damage we did to the original string */
585         if (port_str)
586                 *(port_str) = ':';
587         if (path)
588                 *(path - 1) = '/';
589         if (proto)
590                 *(host - 3) = ':';
591         return 0;
592 }
593
594 /* Return value:
595  *  < 0, on error
596  *  = 0, no cookie (user cancel)
597  *  = 1, obtained cookie
598  */
599 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
600 {
601         struct vpn_option *opt, *next;
602         char buf[MAX_BUF_LEN];
603         char *form_buf = NULL;
604         int result, buflen;
605         char request_body[2048];
606         const char *request_body_type = NULL;
607         const char *method = "GET";
608
609  retry:
610         if (form_buf) {
611                 free(form_buf);
612                 form_buf = NULL;
613         }
614         if (openconnect_open_https(vpninfo)) {
615                 vpn_progress(vpninfo, PRG_ERR,
616                              _("Failed to open HTTPS connection to %s\n"),
617                              vpninfo->hostname);
618                 return -EINVAL;
619         }
620
621         /*
622          * It would be nice to use cURL for this, but we really need to guarantee
623          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
624          * to have any way to let us provide our own socket read/write functions.
625          * We can only provide a socket _open_ function. Which would require having
626          * a socketpair() and servicing the "other" end of it.
627          *
628          * So we process the HTTP for ourselves...
629          */
630         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
631         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
632         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
633         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
634         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
635
636         if (vpninfo->cookies) {
637                 sprintf(buf + strlen(buf),  "Cookie: ");
638                 for (opt = vpninfo->cookies; opt; opt = opt->next)
639                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
640                                       opt->value, opt->next ? "; " : "\r\n");
641         }
642         if (request_body_type) {
643                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
644                               request_body_type);
645                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
646                               strlen(request_body));
647         }
648         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
649         if (request_body_type)
650                 sprintf(buf + strlen(buf), "%s", request_body);
651
652         if (vpninfo->port == 443)
653                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
654                              method, vpninfo->hostname,
655                              vpninfo->urlpath ?: "");
656         else
657                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
658                              method, vpninfo->hostname, vpninfo->port,
659                              vpninfo->urlpath ?: "");
660
661         result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
662         if (result < 0)
663                 return result;
664
665         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
666         if (buflen < 0) {
667                 /* We'll already have complained about whatever offended us */
668                 return buflen;
669         }
670
671         if (result != 200 && vpninfo->redirect_url) {
672         redirect:
673                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
674                         /* New host. Tear down the existing connection and make a new one */
675                         char *host;
676                         int port;
677                         int ret;
678
679                         free(vpninfo->urlpath);
680                         vpninfo->urlpath = NULL;
681
682                         ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
683                         if (ret) {
684                                 vpn_progress(vpninfo, PRG_ERR,
685                                              _("Failed to parse redirected URL '%s': %s\n"),
686                                              vpninfo->redirect_url, strerror(-ret));
687                                 free(vpninfo->redirect_url);
688                                 vpninfo->redirect_url = NULL;
689                                 free(form_buf);
690                                 return ret;
691                         }
692
693                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
694                                 free(vpninfo->hostname);
695                                 vpninfo->hostname = host;
696                                 vpninfo->port = port;
697
698                                 /* Kill the existing connection, and a new one will happen */
699                                 free(vpninfo->peer_addr);
700                                 vpninfo->peer_addr = NULL;
701                                 openconnect_close_https(vpninfo, 0);
702
703                                 for (opt = vpninfo->cookies; opt; opt = next) {
704                                         next = opt->next;
705
706                                         free(opt->option);
707                                         free(opt->value);
708                                         free(opt);
709                                 }
710                                 vpninfo->cookies = NULL;
711                         } else
712                                 free(host);
713
714                         free(vpninfo->redirect_url);
715                         vpninfo->redirect_url = NULL;
716
717                         goto retry;
718                 } else if (strstr(vpninfo->redirect_url, "://")) {
719                         vpn_progress(vpninfo, PRG_ERR,
720                                      _("Cannot follow redirection to non-https URL '%s'\n"),
721                                      vpninfo->redirect_url);
722                         free(vpninfo->redirect_url);
723                         vpninfo->redirect_url = NULL;
724                         free(form_buf);
725                         return -EINVAL;
726                 } else if (vpninfo->redirect_url[0] == '/') {
727                         /* Absolute redirect within same host */
728                         free(vpninfo->urlpath);
729                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
730                         free(vpninfo->redirect_url);
731                         vpninfo->redirect_url = NULL;
732                         goto retry;
733                 } else {
734                         char *lastslash = NULL;
735                         if (vpninfo->urlpath)
736                                 lastslash = strrchr(vpninfo->urlpath, '/');
737                         if (!lastslash) {
738                                 free(vpninfo->urlpath);
739                                 vpninfo->urlpath = vpninfo->redirect_url;
740                                 vpninfo->redirect_url = NULL;
741                         } else {
742                                 char *oldurl = vpninfo->urlpath;
743                                 *lastslash = 0;
744                                 vpninfo->urlpath = NULL;
745                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
746                                              oldurl, vpninfo->redirect_url) == -1) {
747                                         int err = -errno;
748                                         vpn_progress(vpninfo, PRG_ERR,
749                                                      _("Allocating new path for relative redirect failed: %s\n"),
750                                                      strerror(-err));
751                                         return err;
752                                 }
753                                 free(oldurl);
754                                 free(vpninfo->redirect_url);
755                                 vpninfo->redirect_url = NULL;
756                         }
757                         goto retry;
758                 }
759         }
760         if (!form_buf || result != 200) {
761                 vpn_progress(vpninfo, PRG_ERR,
762                              _("Unexpected %d result from server\n"),
763                              result);
764                 free(form_buf);
765                 return -EINVAL;
766         }
767         if (vpninfo->csd_stuburl) {
768                 /* This is the CSD stub script, which we now need to run */
769                 result = run_csd_script(vpninfo, form_buf, buflen);
770                 if (result) {
771                         free(form_buf);
772                         return result;
773                 }
774
775                 /* Now we'll be redirected to the waiturl */
776                 goto retry;
777         }
778         if (strncmp(form_buf, "<?xml", 5)) {
779                 /* Not XML? Perhaps it's HTML with a refresh... */
780                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
781                         vpn_progress(vpninfo, PRG_INFO,
782                                      _("Refreshing %s after 1 second...\n"),
783                                      vpninfo->urlpath);
784                         sleep(1);
785                         goto retry;
786                 }
787                 vpn_progress(vpninfo, PRG_ERR,
788                              _("Unknown response from server\n"));
789                 free(form_buf);
790                 return -EINVAL;
791         }
792         request_body[0] = 0;
793         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
794                                     &method, &request_body_type);
795
796         if (!result)
797                 goto redirect;
798
799         free(form_buf);
800
801         if (result != 2)
802                 return result;
803
804         /* A return value of 2 means the XML form indicated
805            success. We _should_ have a cookie... */
806
807         for (opt = vpninfo->cookies; opt; opt = opt->next) {
808
809                 if (!strcmp(opt->option, "webvpn"))
810                         vpninfo->cookie = opt->value;
811                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
812                         char *tok = opt->value;
813                         char *bu = NULL, *fu = NULL, *sha = NULL;
814
815                         do {
816                                 if (tok != opt->value)
817                                         *(tok++) = 0;
818
819                                 if (!strncmp(tok, "bu:", 3))
820                                         bu = tok + 3;
821                                 else if (!strncmp(tok, "fu:", 3))
822                                         fu = tok + 3;
823                                 else if (!strncmp(tok, "fh:", 3)) {
824                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
825                                                          SHA1_SIZE * 2))
826                                                 break;
827                                         sha = tok + 3;
828                                 }
829                         } while ((tok = strchr(tok, '&')));
830
831                         if (bu && fu && sha)
832                                 fetch_config(vpninfo, bu, fu, sha);
833                 }
834         }
835         if (vpninfo->csd_scriptname) {
836                 unlink(vpninfo->csd_scriptname);
837                 free(vpninfo->csd_scriptname);
838                 vpninfo->csd_scriptname = NULL;
839         }
840         return 0;
841 }
842
843 char *openconnect_create_useragent(const char *base)
844 {
845         char *uagent;
846
847         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
848                 return NULL;
849
850         return uagent;
851 }
852
853 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
854                       char *buf, size_t len)
855 {
856         int i = 0;
857         int ret;
858
859         if (len < 2)
860                 return -EINVAL;
861
862         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
863                 if (buf[i] == '\n') {
864                         buf[i] = 0;
865                         if (i && buf[i-1] == '\r') {
866                                 buf[i-1] = 0;
867                                 i--;
868                         }
869                         return i;
870                 }
871                 i++;
872
873                 if (i >= len - 1) {
874                         buf[i] = 0;
875                         return i;
876                 }
877         }
878         buf[i] = 0;
879         return i ?: ret;
880 }
881
882 static int proxy_write(struct openconnect_info *vpninfo, int fd,
883                        unsigned char *buf, size_t len)
884 {
885         size_t count;
886
887         for (count = 0; count < len; ) {
888                 fd_set rd_set, wr_set;
889                 int maxfd = fd;
890                 int i;
891
892                 FD_ZERO(&wr_set);
893                 FD_ZERO(&rd_set);
894                 FD_SET(fd, &wr_set);
895                 if (vpninfo->cancel_fd != -1) {
896                         FD_SET(vpninfo->cancel_fd, &rd_set);
897                         if (vpninfo->cancel_fd > fd)
898                                 maxfd = vpninfo->cancel_fd;
899                 }
900
901                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
902                 if (vpninfo->cancel_fd != -1 &&
903                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
904                         return -EINTR;
905
906                 /* Not that this should ever be able to happen... */
907                 if (!FD_ISSET(fd, &wr_set))
908                         continue;
909
910                 i = write(fd, buf + count, len - count);
911                 if (i < 0)
912                         return -errno;
913
914                 count += i;
915         }
916         return 0;
917 }
918
919 static int proxy_read(struct openconnect_info *vpninfo, int fd,
920                       unsigned char *buf, size_t len)
921 {
922         size_t count;
923
924         for (count = 0; count < len; ) {
925                 fd_set rd_set;
926                 int maxfd = fd;
927                 int i;
928
929                 FD_ZERO(&rd_set);
930                 FD_SET(fd, &rd_set);
931                 if (vpninfo->cancel_fd != -1) {
932                         FD_SET(vpninfo->cancel_fd, &rd_set);
933                         if (vpninfo->cancel_fd > fd)
934                                 maxfd = vpninfo->cancel_fd;
935                 }
936
937                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
938                 if (vpninfo->cancel_fd != -1 &&
939                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
940                         return -EINTR;
941
942                 /* Not that this should ever be able to happen... */
943                 if (!FD_ISSET(fd, &rd_set))
944                         continue;
945
946                 i = read(fd, buf + count, len - count);
947                 if (i < 0)
948                         return -errno;
949
950                 count += i;
951         }
952         return 0;
953 }
954
955 static const char *socks_errors[] = {
956         N_("request granted"),
957         N_("general failure"),
958         N_("connection not allowed by ruleset"),
959         N_("network unreachable"),
960         N_("host unreachable"),
961         N_("connection refused by destination host"),
962         N_("TTL expired"),
963         N_("command not supported / protocol error"),
964         N_("address type not supported")
965 };
966
967 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
968 {
969         unsigned char buf[1024];
970         int i;
971
972         buf[0] = 5; /* SOCKS version */
973         buf[1] = 1; /* # auth methods */
974         buf[2] = 0; /* No auth supported */
975
976         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
977                 vpn_progress(vpninfo, PRG_ERR,
978                              _("Error writing auth request to SOCKS proxy: %s\n"),
979                              strerror(-i));
980                 return i;
981         }
982         
983         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
984                 vpn_progress(vpninfo, PRG_ERR,
985                              _("Error reading auth response from SOCKS proxy: %s\n"),
986                              strerror(-i));
987                 return i;
988         }
989         if (buf[0] != 5) {
990                 vpn_progress(vpninfo, PRG_ERR,
991                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
992                              buf[0], buf[1]);
993                 return -EIO;
994         }
995         if (buf[1]) {
996         socks_err:
997                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
998                         vpn_progress(vpninfo, PRG_ERR,
999                                      _("SOCKS proxy error %02x: %s\n"),
1000                                      buf[1], _(socks_errors[buf[1]]));
1001                 else
1002                         vpn_progress(vpninfo, PRG_ERR,
1003                                      _("SOCKS proxy error %02x\n"),
1004                                      buf[1]);
1005                 return -EIO;
1006         }
1007
1008         vpn_progress(vpninfo, PRG_INFO,
1009                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1010                      vpninfo->hostname, vpninfo->port);
1011
1012         buf[0] = 5; /* SOCKS version */
1013         buf[1] = 1; /* CONNECT */
1014         buf[2] = 0; /* Reserved */
1015         buf[3] = 3; /* Address type is domain name */
1016         buf[4] = strlen(vpninfo->hostname);
1017         strcpy((char *)buf + 5, vpninfo->hostname);
1018         i = strlen(vpninfo->hostname) + 5;
1019         buf[i++] = vpninfo->port >> 8;
1020         buf[i++] = vpninfo->port & 0xff;
1021
1022         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1023                 vpn_progress(vpninfo, PRG_ERR,
1024                              _("Error writing connect request to SOCKS proxy: %s\n"),
1025                              strerror(-i));
1026                 return i;
1027         }
1028         /* Read 5 bytes -- up to and including the first byte of the returned
1029            address (which might be the length byte of a domain name) */
1030         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1031                 vpn_progress(vpninfo, PRG_ERR,
1032                              _("Error reading connect response from SOCKS proxy: %s\n"),
1033                              strerror(-i));
1034                 return i;
1035         }
1036         if (buf[0] != 5) {
1037                 vpn_progress(vpninfo, PRG_ERR,
1038                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1039                              buf[0], buf[1]);
1040                 return -EIO;
1041         }
1042         if (buf[1])
1043                 goto socks_err;
1044
1045         /* Connect responses contain an address */
1046         switch(buf[3]) {
1047         case 1: /* Legacy IP */
1048                 i = 5;
1049                 break;
1050         case 3: /* Domain name */
1051                 i = buf[4] + 2;
1052                 break;
1053         case 4: /* IPv6 */
1054                 i = 17;
1055                 break;
1056         default:
1057                 vpn_progress(vpninfo, PRG_ERR,
1058                              _("Unexpected address type %02x in SOCKS connect response\n"),
1059                              buf[3]);
1060                 return -EIO;
1061         }
1062
1063         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1064                 vpn_progress(vpninfo, PRG_ERR,
1065                              _("Error reading connect response from SOCKS proxy: %s\n"),
1066                              strerror(-i));
1067                 return i;
1068         }
1069         return 0;
1070 }
1071
1072 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1073 {
1074         char buf[MAX_BUF_LEN];
1075         int buflen, result;
1076
1077         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1078         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1079         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1080         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1081         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1082         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1083         sprintf(buf + strlen(buf), "\r\n");
1084
1085         vpn_progress(vpninfo, PRG_INFO,
1086                      _("Requesting HTTP proxy connection to %s:%d\n"),
1087                      vpninfo->hostname, vpninfo->port);
1088
1089         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1090         if (result) {
1091                 vpn_progress(vpninfo, PRG_ERR,
1092                              _("Sending proxy request failed: %s\n"),
1093                              strerror(-result));
1094                 return result;
1095         }
1096
1097         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1098                 vpn_progress(vpninfo, PRG_ERR,
1099                              _("Error fetching proxy response\n"));
1100                 return -EIO;
1101         }
1102
1103         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1104             buf[8] != ' ' || !(result = atoi(buf+9))) {
1105                 vpn_progress(vpninfo, PRG_ERR,
1106                              _("Failed to parse proxy response '%s'\n"), buf);
1107                 return -EINVAL;
1108         }
1109
1110         if (result != 200) {
1111                 vpn_progress(vpninfo, PRG_ERR,
1112                              _("Proxy CONNECT request failed: %s\n"), buf);
1113                 return -EIO;
1114         }
1115
1116         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1117                 if (buflen < 0) {
1118                         vpn_progress(vpninfo, PRG_ERR,
1119                                      _("Failed to read proxy response\n"));
1120                         return -EIO;
1121                 }
1122                 vpn_progress(vpninfo, PRG_ERR,
1123                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1124                              buf);
1125         }
1126
1127         return 0;
1128 }
1129
1130 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1131 {
1132         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1133                 return process_http_proxy(vpninfo, ssl_sock);
1134         
1135         if (!strcmp(vpninfo->proxy_type, "socks") ||
1136             !strcmp(vpninfo->proxy_type, "socks5"))
1137                 return process_socks_proxy(vpninfo, ssl_sock);
1138
1139         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1140                      vpninfo->proxy_type);
1141         return -EIO;
1142 }
1143
1144 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1145 {
1146         char *url = proxy;
1147         int ret;
1148
1149         if (!url)
1150                 return -ENOMEM;
1151
1152         free(vpninfo->proxy_type);
1153         vpninfo->proxy_type = NULL;
1154         free(vpninfo->proxy);
1155         vpninfo->proxy = NULL;
1156
1157         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1158                                  &vpninfo->proxy_port, NULL, 80);
1159         if (ret)
1160                 goto out;
1161
1162         if (vpninfo->proxy_type &&
1163             strcmp(vpninfo->proxy_type, "http") &&
1164             strcmp(vpninfo->proxy_type, "socks") &&
1165             strcmp(vpninfo->proxy_type, "socks5")) {
1166                 vpn_progress(vpninfo, PRG_ERR,
1167                              _("Only http or socks(5) proxies supported\n"));
1168                 free(vpninfo->proxy_type);
1169                 vpninfo->proxy_type = NULL;
1170                 free(vpninfo->proxy);
1171                 vpninfo->proxy = NULL;
1172                 return -EINVAL;
1173         }
1174  out:
1175         free(url);
1176         return ret;
1177 }