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