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