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