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