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