Namespace cleanup: s/set_http_proxy/openconnect_set_http_proxy/
[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 = NULL;
713                         if (vpninfo->urlpath)
714                                 lastslash = strrchr(vpninfo->urlpath, '/');
715                         if (!lastslash) {
716                                 free(vpninfo->urlpath);
717                                 vpninfo->urlpath = vpninfo->redirect_url;
718                                 vpninfo->redirect_url = NULL;
719                         } else {
720                                 char *oldurl = vpninfo->urlpath;
721                                 *lastslash = 0;
722                                 vpninfo->urlpath = NULL;
723                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
724                                              oldurl, vpninfo->redirect_url) == -1) {
725                                         int err = -errno;
726                                         vpninfo->progress(vpninfo, PRG_ERR,
727                                                           "Allocating new path for relative redirect failed: %s\n",
728                                                           strerror(-err));
729                                         return err;
730                                 }
731                                 free(oldurl);
732                                 free(vpninfo->redirect_url);
733                                 vpninfo->redirect_url = NULL;
734                         }
735                         goto retry;
736                 }
737         }
738         if (!form_buf || result != 200) {
739                 vpninfo->progress(vpninfo, PRG_ERR,
740                                   "Unexpected %d result from server\n",
741                                   result);
742                 free(form_buf);
743                 return -EINVAL;
744         }
745         if (vpninfo->csd_stuburl) {
746                 /* This is the CSD stub script, which we now need to run */
747                 result = run_csd_script(vpninfo, form_buf, buflen);
748                 if (result) {
749                         free(form_buf);
750                         return result;
751                 }
752
753                 /* Now we'll be redirected to the waiturl */
754                 goto retry;
755         }
756         if (strncmp(form_buf, "<?xml", 5)) {
757                 /* Not XML? Perhaps it's HTML with a refresh... */
758                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
759                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
760                                           vpninfo->urlpath);
761                         sleep(1);
762                         goto retry;
763                 }
764                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
765                 free(form_buf);
766                 return -EINVAL;
767         }
768         request_body[0] = 0;
769         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
770                                     &method, &request_body_type);
771
772         if (!result)
773                 goto redirect;
774
775         free(form_buf);
776
777         if (result != 2)
778                 return result;
779
780         /* A return value of 2 means the XML form indicated
781            success. We _should_ have a cookie... */
782
783         for (opt = vpninfo->cookies; opt; opt = opt->next) {
784
785                 if (!strcmp(opt->option, "webvpn"))
786                         vpninfo->cookie = opt->value;
787                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
788                         char *tok = opt->value;
789                         char *bu = NULL, *fu = NULL, *sha = NULL;
790
791                         do {
792                                 if (tok != opt->value)
793                                         *(tok++) = 0;
794
795                                 if (!strncmp(tok, "bu:", 3))
796                                         bu = tok + 3;
797                                 else if (!strncmp(tok, "fu:", 3))
798                                         fu = tok + 3;
799                                 else if (!strncmp(tok, "fh:", 3)) {
800                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
801                                                          SHA_DIGEST_LENGTH * 2))
802                                                 break;
803                                         sha = tok + 3;
804                                 }
805                         } while ((tok = strchr(tok, '&')));
806
807                         if (bu && fu && sha)
808                                 fetch_config(vpninfo, bu, fu, sha);
809                 }
810         }
811         if (vpninfo->csd_scriptname) {
812                 unlink(vpninfo->csd_scriptname);
813                 free(vpninfo->csd_scriptname);
814                 vpninfo->csd_scriptname = NULL;
815         }
816         return 0;
817 }
818
819 char *openconnect_create_useragent(char *base)
820 {
821         char *uagent;
822
823         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
824                 return NULL;
825
826         return uagent;
827 }
828
829 static int proxy_gets(int fd, char *buf, size_t len)
830 {
831         int i = 0;
832         int ret;
833
834         if (len < 2)
835                 return -EINVAL;
836
837         while ( (ret = read(fd, buf + i, 1)) == 1) {
838                 if (buf[i] == '\n') {
839                         buf[i] = 0;
840                         if (i && buf[i-1] == '\r') {
841                                 buf[i-1] = 0;
842                                 i--;
843                         }
844                         return i;
845                 }
846                 i++;
847
848                 if (i >= len - 1) {
849                         buf[i] = 0;
850                         return i;
851                 }
852         }
853         if (ret < 0)
854                 ret = -errno;
855
856         buf[i] = 0;
857         return i ?: ret;
858 }
859
860 static int proxy_write(int fd, unsigned char *buf, size_t len)
861 {
862         size_t count;
863         
864         for (count = 0; count < len; ) {
865                 int i = write(fd, buf + count, len - count);
866                 if (i < 0)
867                         return -errno;
868
869                 count += i;
870         }
871         return 0;
872 }
873
874 static int proxy_read(int fd, unsigned char *buf, size_t len)
875 {
876         size_t count;
877
878         for (count = 0; count < len; ) {
879                 int i = read(fd, buf + count, len - count);
880                 if (i < 0)
881                         return -errno;
882
883                 count += i;
884         }
885         return 0;
886 }
887
888 static const char *socks_errors[] = {
889         "request granted",
890         "general failure",
891         "connection not allowed by ruleset",
892         "network unreachable",
893         "host unreachable",
894         "connection refused by destination host",
895         "TTL expired",
896         "command not supported / protocol error",
897         "address type not supported"
898 };
899
900 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
901 {
902         unsigned char buf[1024];
903         int i;
904
905         buf[0] = 5; /* SOCKS version */
906         buf[1] = 1; /* # auth methods */
907         buf[2] = 0; /* No auth supported */
908
909         if ((i = proxy_write(ssl_sock, buf, 3))) {
910                 vpninfo->progress(vpninfo, PRG_ERR,
911                                   "Error writing auth request to SOCKS proxy: %s\n",
912                                   strerror(-i));
913                 return i;
914         }
915         
916         if ((i = proxy_read(ssl_sock, buf, 2))) {
917                 vpninfo->progress(vpninfo, PRG_ERR,
918                                   "Error reading auth response from SOCKS proxy: %s\n",
919                                   strerror(-i));
920                 return i;
921         }
922         if (buf[0] != 5) {
923                 vpninfo->progress(vpninfo, PRG_ERR,
924                                   "Unexpected auth response from SOCKS proxy: %02x %02x\n",
925                                   buf[0], buf[1]);
926                 return -EIO;
927         }
928         if (buf[1]) {
929         socks_err:
930                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
931                         vpninfo->progress(vpninfo, PRG_ERR,
932                                           "SOCKS proxy error %02x: %s\n",
933                                           buf[1], socks_errors[buf[1]]);
934                 else
935                         vpninfo->progress(vpninfo, PRG_ERR,
936                                           "SOCKS proxy error %02x\n",
937                                           buf[1]);
938                 return -EIO;
939         }
940
941         vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
942                           vpninfo->hostname, vpninfo->port);
943
944         buf[0] = 5; /* SOCKS version */
945         buf[1] = 1; /* CONNECT */
946         buf[2] = 0; /* Reserved */
947         buf[3] = 3; /* Address type is domain name */
948         buf[4] = strlen(vpninfo->hostname);
949         strcpy((char *)buf + 5, vpninfo->hostname);
950         i = strlen(vpninfo->hostname) + 5;
951         buf[i++] = vpninfo->port >> 8;
952         buf[i++] = vpninfo->port & 0xff;
953
954         if ((i = proxy_write(ssl_sock, buf, i))) {
955                 vpninfo->progress(vpninfo, PRG_ERR,
956                                   "Error writing connect request to SOCKS proxy: %s\n",
957                                   strerror(-i));
958                 return i;
959         }
960         /* Read 5 bytes -- up to and including the first byte of the returned
961            address (which might be the length byte of a domain name) */
962         if ((i = proxy_read(ssl_sock, buf, 5))) {
963                 vpninfo->progress(vpninfo, PRG_ERR,
964                                   "Error reading connect response from SOCKS proxy: %s\n",
965                                   strerror(-i));
966                 return i;
967         }
968         if (buf[0] != 5) {
969                 vpninfo->progress(vpninfo, PRG_ERR,
970                                   "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
971                                   buf[0], buf[1]);
972                 return -EIO;
973         }
974         if (buf[1])
975                 goto socks_err;
976
977         /* Connect responses contain an address */
978         switch(buf[3]) {
979         case 1: /* Legacy IP */
980                 i = 5;
981                 break;
982         case 3: /* Domain name */
983                 i = buf[4] + 2;
984                 break;
985         case 4: /* IPv6 */
986                 i = 17;
987                 break;
988         default:
989                 vpninfo->progress(vpninfo, PRG_ERR,
990                                   "Unexpected address type %02x in SOCKS connect response\n",
991                                   buf[3]);
992                 return -EIO;
993         }
994
995         if ((i = proxy_read(ssl_sock, buf, i))) {
996                 vpninfo->progress(vpninfo, PRG_ERR,
997                                   "Error reading connect response from SOCKS proxy: %s\n",
998                                   strerror(-i));
999                 return i;
1000         }
1001         return 0;
1002 }
1003
1004 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1005 {
1006         char buf[MAX_BUF_LEN];
1007         int buflen, result;
1008
1009         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1010         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1011         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1012         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1013         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1014         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1015         sprintf(buf + strlen(buf), "\r\n");
1016
1017         vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
1018                           vpninfo->hostname, vpninfo->port);
1019
1020         if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1021                 result = -errno;
1022                 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
1023                                   strerror(errno));
1024                 return result;
1025         }
1026
1027         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1028                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
1029                 return -EIO;
1030         }
1031
1032         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1033             buf[8] != ' ' || !(result = atoi(buf+9))) {
1034                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
1035                                   buf);
1036                 return -EINVAL;
1037         }
1038
1039         if (result != 200) {
1040                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
1041                                   buf);
1042                 return -EIO;
1043         }
1044
1045         while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1046                 if (buflen < 0) {
1047                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
1048                         return -EIO;
1049                 }
1050                 vpninfo->progress(vpninfo, PRG_ERR,
1051                                   "Unexpected continuation line after CONNECT response: '%s'\n",
1052                                   buf);
1053         }
1054
1055         return 0;
1056 }
1057
1058 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1059 {
1060         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1061                 return process_http_proxy(vpninfo, ssl_sock);
1062         
1063         if (!strcmp(vpninfo->proxy_type, "socks") ||
1064             !strcmp(vpninfo->proxy_type, "socks5"))
1065                 return process_socks_proxy(vpninfo, ssl_sock);
1066
1067         vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
1068                                   vpninfo->proxy_type);
1069         return -EIO;
1070 }
1071
1072 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1073 {
1074         char *url = strdup(proxy);
1075         int ret;
1076
1077         if (!url)
1078                 return -ENOMEM;
1079
1080         free(vpninfo->proxy_type);
1081         vpninfo->proxy_type = NULL;
1082         free(vpninfo->proxy);
1083         vpninfo->proxy = NULL;
1084
1085         ret = parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1086                         &vpninfo->proxy_port, NULL, 80);
1087         if (ret)
1088                 goto out;
1089
1090         if (vpninfo->proxy_type &&
1091             strcmp(vpninfo->proxy_type, "http") &&
1092             strcmp(vpninfo->proxy_type, "socks") &&
1093             strcmp(vpninfo->proxy_type, "socks5")) {
1094                 vpninfo->progress(vpninfo, PRG_ERR,
1095                                   "Only http or socks(5) proxies supported\n");
1096                 free(vpninfo->proxy_type);
1097                 vpninfo->proxy_type = NULL;
1098                 free(vpninfo->proxy);
1099                 vpninfo->proxy = NULL;
1100                 return -EINVAL;
1101         }
1102  out:
1103         free(url);
1104         return ret;
1105 }