Rename openconnect_parse_url() to internal_parse_url()
[platform/upstream/openconnect.git] / http.c
1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2010 Intel Corporation.
5  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
6  *
7  * Author: David Woodhouse <dwmw2@infradead.org>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1, as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to:
20  *
21  *   Free Software Foundation, Inc.
22  *   51 Franklin Street, Fifth Floor,
23  *   Boston, MA 02110-1301 USA
24  */
25
26 #define _GNU_SOURCE
27 #include <netdb.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <time.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <pwd.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/engine.h>
40
41 #include "openconnect-internal.h"
42
43 static int proxy_write(int fd, unsigned char *buf, size_t len);
44
45 #define MAX_BUF_LEN 131072
46 /*
47  * We didn't really want to have to do this for ourselves -- one might have
48  * thought that it would be available in a library somewhere. But neither
49  * cURL nor Neon have reliable cross-platform ways of either using a cert
50  * from the TPM, or just reading from / writing to a transport which is
51  * provided by their caller.
52  */
53
54 static int http_add_cookie(struct openconnect_info *vpninfo,
55                            const char *option, const char *value)
56 {
57         struct vpn_option *new, **this;
58
59         if (*value) {
60                 new = malloc(sizeof(*new));
61                 if (!new) {
62                         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 unless it's empty; we don't
166                            want people posting it in public with debugging output */
167                         if (!strcmp(colon, "webvpn") && *equals)
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 && !vpninfo->csd_wrapper) {
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 && !vpninfo->csd_wrapper) {
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                 if (vpninfo->csd_wrapper)
450                         csd_argv[i++] = vpninfo->csd_wrapper;
451                 csd_argv[i++] = fname;
452                 csd_argv[i++] = "-ticket";
453                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
454                         return -ENOMEM;
455                 csd_argv[i++] = "-stub";
456                 csd_argv[i++] = "\"0\"";
457                 csd_argv[i++] = "-group";
458                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
459                         return -ENOMEM;
460
461                 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
462                 if (ccert)
463                         get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
464                 else
465                         ccertbuf[0] = 0;
466
467                 csd_argv[i++] = "-certhash";
468                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
469                         return -ENOMEM;
470                 csd_argv[i++] = "-url";
471                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
472                         return -ENOMEM;
473                 /* WTF would it want to know this for? */
474                 csd_argv[i++] = "-vpnclient";
475                 csd_argv[i++] = "\"/opt/cisco/vpn/bin/vpnui";
476                 csd_argv[i++] = "-connect";
477                 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
478                         return -ENOMEM;
479                 csd_argv[i++] = "-connectparam";
480                 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
481                         return -ENOMEM;
482                 csd_argv[i++] = "-langselen";
483                 csd_argv[i++] = NULL;
484
485                 execv(csd_argv[0], csd_argv);
486                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", csd_argv[0]);
487                 exit(1);
488         }
489
490         free(vpninfo->csd_stuburl);
491         vpninfo->csd_stuburl = NULL;
492         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
493                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
494         vpninfo->csd_waiturl = NULL;
495         vpninfo->csd_scriptname = strdup(fname);
496
497         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
498
499         return 0;
500 }
501
502 #ifdef __sun__
503 char *local_strcasestr(const char *haystack, const char *needle)
504 {
505         int hlen = strlen(haystack);
506         int nlen = strlen(needle);
507         int i, j;
508
509         for (i = 0; i < hlen - nlen + 1; i++) {
510                 for (j = 0; j < nlen; j++) {
511                         if (tolower(haystack[i + j]) != 
512                             tolower(needle[j]))
513                                 break;
514                 }
515                 if (j == nlen)
516                         return (char *)haystack + i;
517         }
518         return NULL;
519 }
520 #define strcasestr local_strcasestr
521 #endif
522
523 int internal_parse_url(char *url, char **res_proto, char **res_host,
524                        int *res_port, char **res_path, int default_port)
525 {
526         char *proto = url;
527         char *host, *path, *port_str;
528         int port;
529
530         host = strstr(url, "://");
531         if (host) {
532                 *host = 0;
533                 host += 3;
534
535                 if (!strcasecmp(proto, "https"))
536                         port = 443;
537                 else if (!strcasecmp(proto, "http"))
538                         port = 80;
539                 else if (!strcasecmp(proto, "socks") ||
540                          !strcasecmp(proto, "socks4") ||
541                          !strcasecmp(proto, "socks5"))
542                         port = 1080;
543                 else
544                         return -EPROTONOSUPPORT;
545         } else {
546                 if (default_port) {
547                         proto = NULL;
548                         port = default_port;
549                         host = url;
550                 } else
551                         return -EINVAL;
552         }
553
554         path = strchr(host, '/');
555         if (path)
556                 *(path++) = 0;
557
558         port_str = strrchr(host, ':');
559         if (port_str) {
560                 char *end;
561                 int new_port = strtol(port_str + 1, &end, 10);
562
563                 if (!*end) {
564                         *port_str = 0;
565                         port = new_port;
566                 }
567         }
568
569         if (res_proto)
570                 *res_proto = proto ? strdup(proto) : NULL;
571         if (res_host)
572                 *res_host = strdup(host);
573         if (res_port)
574                 *res_port = port;
575         if (res_path)
576                 *res_path = (path && *path) ? strdup(path) : NULL;
577
578         /* Undo the damage we did to the original string */
579         if (path)
580                 *(path - 1) = '/';
581         if (proto)
582                 *(host - 3) = ':';
583         return 0;
584 }
585
586 /* Return value:
587  *  < 0, on error
588  *  = 0, no cookie (user cancel)
589  *  = 1, obtained cookie
590  */
591 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
592 {
593         struct vpn_option *opt, *next;
594         char buf[MAX_BUF_LEN];
595         char *form_buf = NULL;
596         int result, buflen;
597         char request_body[2048];
598         char *request_body_type = NULL;
599         char *method = "GET";
600
601  retry:
602         if (form_buf) {
603                 free(form_buf);
604                 form_buf = NULL;
605         }
606         if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
607                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
608                         vpninfo->hostname);
609                 return -EINVAL;
610         }
611
612         /*
613          * It would be nice to use cURL for this, but we really need to guarantee
614          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
615          * to have any way to let us provide our own socket read/write functions.
616          * We can only provide a socket _open_ function. Which would require having
617          * a socketpair() and servicing the "other" end of it.
618          *
619          * So we process the HTTP for ourselves...
620          */
621         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
622         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
623         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
624         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
625         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
626
627         if (vpninfo->cookies) {
628                 sprintf(buf + strlen(buf),  "Cookie: ");
629                 for (opt = vpninfo->cookies; opt; opt = opt->next)
630                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
631                                       opt->value, opt->next ? "; " : "\r\n");
632         }
633         if (request_body_type) {
634                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
635                               request_body_type);
636                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
637                               strlen(request_body));
638         }
639         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
640         if (request_body_type)
641                 sprintf(buf + strlen(buf), "%s", request_body);
642
643         if (vpninfo->port == 443)
644                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
645                                   method, vpninfo->hostname,
646                                   vpninfo->urlpath ?: "");
647         else
648                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
649                                   method, vpninfo->hostname, vpninfo->port,
650                                   vpninfo->urlpath ?: "");
651
652         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
653
654         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
655         if (buflen < 0) {
656                 /* We'll already have complained about whatever offended us */
657                 exit(1);
658         }
659
660         if (result != 200 && vpninfo->redirect_url) {
661         redirect:
662                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
663                         /* New host. Tear down the existing connection and make a new one */
664                         char *host;
665                         int port;
666                         int ret;
667
668                         free(vpninfo->urlpath);
669                         vpninfo->urlpath = NULL;
670
671                         ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
672                         if (ret) {
673                                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse redirected URL '%s': %s\n",
674                                                   vpninfo->redirect_url, strerror(-ret));
675                                 free(vpninfo->redirect_url);
676                                 free(form_buf);
677                                 return ret;
678                         }
679
680                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
681                                 free(vpninfo->hostname);
682                                 vpninfo->hostname = host;
683                                 vpninfo->port = port;
684
685                                 /* Kill the existing connection, and a new one will happen */
686                                 free(vpninfo->peer_addr);
687                                 vpninfo->peer_addr = NULL;
688                                 if (vpninfo->https_ssl) {
689                                         SSL_free(vpninfo->https_ssl);
690                                         vpninfo->https_ssl = NULL;
691                                         close(vpninfo->ssl_fd);
692                                         vpninfo->ssl_fd = -1;
693                                 }
694
695                                 for (opt = vpninfo->cookies; opt; opt = next) {
696                                         next = opt->next;
697
698                                         free(opt->option);
699                                         free(opt->value);
700                                         free(opt);
701                                 }
702                                 vpninfo->cookies = NULL;
703                         } else
704                                 free(host);
705
706                         free(vpninfo->redirect_url);
707                         vpninfo->redirect_url = NULL;
708
709                         goto retry;
710                 } else if (vpninfo->redirect_url[0] == '/') {
711                         /* Absolute redirect within same host */
712                         free(vpninfo->urlpath);
713                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
714                         free(vpninfo->redirect_url);
715                         vpninfo->redirect_url = NULL;
716                         goto retry;
717                 } else {
718                         char *lastslash = NULL;
719                         if (vpninfo->urlpath)
720                                 lastslash = strrchr(vpninfo->urlpath, '/');
721                         if (!lastslash) {
722                                 free(vpninfo->urlpath);
723                                 vpninfo->urlpath = vpninfo->redirect_url;
724                                 vpninfo->redirect_url = NULL;
725                         } else {
726                                 char *oldurl = vpninfo->urlpath;
727                                 *lastslash = 0;
728                                 vpninfo->urlpath = NULL;
729                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
730                                              oldurl, vpninfo->redirect_url) == -1) {
731                                         int err = -errno;
732                                         vpninfo->progress(vpninfo, PRG_ERR,
733                                                           "Allocating new path for relative redirect failed: %s\n",
734                                                           strerror(-err));
735                                         return err;
736                                 }
737                                 free(oldurl);
738                                 free(vpninfo->redirect_url);
739                                 vpninfo->redirect_url = NULL;
740                         }
741                         goto retry;
742                 }
743         }
744         if (!form_buf || result != 200) {
745                 vpninfo->progress(vpninfo, PRG_ERR,
746                                   "Unexpected %d result from server\n",
747                                   result);
748                 free(form_buf);
749                 return -EINVAL;
750         }
751         if (vpninfo->csd_stuburl) {
752                 /* This is the CSD stub script, which we now need to run */
753                 result = run_csd_script(vpninfo, form_buf, buflen);
754                 if (result) {
755                         free(form_buf);
756                         return result;
757                 }
758
759                 /* Now we'll be redirected to the waiturl */
760                 goto retry;
761         }
762         if (strncmp(form_buf, "<?xml", 5)) {
763                 /* Not XML? Perhaps it's HTML with a refresh... */
764                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
765                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
766                                           vpninfo->urlpath);
767                         sleep(1);
768                         goto retry;
769                 }
770                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
771                 free(form_buf);
772                 return -EINVAL;
773         }
774         request_body[0] = 0;
775         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
776                                     &method, &request_body_type);
777
778         if (!result)
779                 goto redirect;
780
781         free(form_buf);
782
783         if (result != 2)
784                 return result;
785
786         /* A return value of 2 means the XML form indicated
787            success. We _should_ have a cookie... */
788
789         for (opt = vpninfo->cookies; opt; opt = opt->next) {
790
791                 if (!strcmp(opt->option, "webvpn"))
792                         vpninfo->cookie = opt->value;
793                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
794                         char *tok = opt->value;
795                         char *bu = NULL, *fu = NULL, *sha = NULL;
796
797                         do {
798                                 if (tok != opt->value)
799                                         *(tok++) = 0;
800
801                                 if (!strncmp(tok, "bu:", 3))
802                                         bu = tok + 3;
803                                 else if (!strncmp(tok, "fu:", 3))
804                                         fu = tok + 3;
805                                 else if (!strncmp(tok, "fh:", 3)) {
806                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
807                                                          SHA_DIGEST_LENGTH * 2))
808                                                 break;
809                                         sha = tok + 3;
810                                 }
811                         } while ((tok = strchr(tok, '&')));
812
813                         if (bu && fu && sha)
814                                 fetch_config(vpninfo, bu, fu, sha);
815                 }
816         }
817         if (vpninfo->csd_scriptname) {
818                 unlink(vpninfo->csd_scriptname);
819                 free(vpninfo->csd_scriptname);
820                 vpninfo->csd_scriptname = NULL;
821         }
822         return 0;
823 }
824
825 char *openconnect_create_useragent(char *base)
826 {
827         char *uagent;
828
829         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
830                 return NULL;
831
832         return uagent;
833 }
834
835 static int proxy_gets(int fd, char *buf, size_t len)
836 {
837         int i = 0;
838         int ret;
839
840         if (len < 2)
841                 return -EINVAL;
842
843         while ( (ret = read(fd, buf + i, 1)) == 1) {
844                 if (buf[i] == '\n') {
845                         buf[i] = 0;
846                         if (i && buf[i-1] == '\r') {
847                                 buf[i-1] = 0;
848                                 i--;
849                         }
850                         return i;
851                 }
852                 i++;
853
854                 if (i >= len - 1) {
855                         buf[i] = 0;
856                         return i;
857                 }
858         }
859         if (ret < 0)
860                 ret = -errno;
861
862         buf[i] = 0;
863         return i ?: ret;
864 }
865
866 static int proxy_write(int fd, unsigned char *buf, size_t len)
867 {
868         size_t count;
869         
870         for (count = 0; count < len; ) {
871                 int i = write(fd, buf + count, len - count);
872                 if (i < 0)
873                         return -errno;
874
875                 count += i;
876         }
877         return 0;
878 }
879
880 static int proxy_read(int fd, unsigned char *buf, size_t len)
881 {
882         size_t count;
883
884         for (count = 0; count < len; ) {
885                 int i = read(fd, buf + count, len - count);
886                 if (i < 0)
887                         return -errno;
888
889                 count += i;
890         }
891         return 0;
892 }
893
894 static const char *socks_errors[] = {
895         "request granted",
896         "general failure",
897         "connection not allowed by ruleset",
898         "network unreachable",
899         "host unreachable",
900         "connection refused by destination host",
901         "TTL expired",
902         "command not supported / protocol error",
903         "address type not supported"
904 };
905
906 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
907 {
908         unsigned char buf[1024];
909         int i;
910
911         buf[0] = 5; /* SOCKS version */
912         buf[1] = 1; /* # auth methods */
913         buf[2] = 0; /* No auth supported */
914
915         if ((i = proxy_write(ssl_sock, buf, 3))) {
916                 vpninfo->progress(vpninfo, PRG_ERR,
917                                   "Error writing auth request to SOCKS proxy: %s\n",
918                                   strerror(-i));
919                 return i;
920         }
921         
922         if ((i = proxy_read(ssl_sock, buf, 2))) {
923                 vpninfo->progress(vpninfo, PRG_ERR,
924                                   "Error reading auth response from SOCKS proxy: %s\n",
925                                   strerror(-i));
926                 return i;
927         }
928         if (buf[0] != 5) {
929                 vpninfo->progress(vpninfo, PRG_ERR,
930                                   "Unexpected auth response from SOCKS proxy: %02x %02x\n",
931                                   buf[0], buf[1]);
932                 return -EIO;
933         }
934         if (buf[1]) {
935         socks_err:
936                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
937                         vpninfo->progress(vpninfo, PRG_ERR,
938                                           "SOCKS proxy error %02x: %s\n",
939                                           buf[1], socks_errors[buf[1]]);
940                 else
941                         vpninfo->progress(vpninfo, PRG_ERR,
942                                           "SOCKS proxy error %02x\n",
943                                           buf[1]);
944                 return -EIO;
945         }
946
947         vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
948                           vpninfo->hostname, vpninfo->port);
949
950         buf[0] = 5; /* SOCKS version */
951         buf[1] = 1; /* CONNECT */
952         buf[2] = 0; /* Reserved */
953         buf[3] = 3; /* Address type is domain name */
954         buf[4] = strlen(vpninfo->hostname);
955         strcpy((char *)buf + 5, vpninfo->hostname);
956         i = strlen(vpninfo->hostname) + 5;
957         buf[i++] = vpninfo->port >> 8;
958         buf[i++] = vpninfo->port & 0xff;
959
960         if ((i = proxy_write(ssl_sock, buf, i))) {
961                 vpninfo->progress(vpninfo, PRG_ERR,
962                                   "Error writing connect request to SOCKS proxy: %s\n",
963                                   strerror(-i));
964                 return i;
965         }
966         /* Read 5 bytes -- up to and including the first byte of the returned
967            address (which might be the length byte of a domain name) */
968         if ((i = proxy_read(ssl_sock, buf, 5))) {
969                 vpninfo->progress(vpninfo, PRG_ERR,
970                                   "Error reading connect response from SOCKS proxy: %s\n",
971                                   strerror(-i));
972                 return i;
973         }
974         if (buf[0] != 5) {
975                 vpninfo->progress(vpninfo, PRG_ERR,
976                                   "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
977                                   buf[0], buf[1]);
978                 return -EIO;
979         }
980         if (buf[1])
981                 goto socks_err;
982
983         /* Connect responses contain an address */
984         switch(buf[3]) {
985         case 1: /* Legacy IP */
986                 i = 5;
987                 break;
988         case 3: /* Domain name */
989                 i = buf[4] + 2;
990                 break;
991         case 4: /* IPv6 */
992                 i = 17;
993                 break;
994         default:
995                 vpninfo->progress(vpninfo, PRG_ERR,
996                                   "Unexpected address type %02x in SOCKS connect response\n",
997                                   buf[3]);
998                 return -EIO;
999         }
1000
1001         if ((i = proxy_read(ssl_sock, buf, i))) {
1002                 vpninfo->progress(vpninfo, PRG_ERR,
1003                                   "Error reading connect response from SOCKS proxy: %s\n",
1004                                   strerror(-i));
1005                 return i;
1006         }
1007         return 0;
1008 }
1009
1010 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1011 {
1012         char buf[MAX_BUF_LEN];
1013         int buflen, result;
1014
1015         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1016         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1017         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1018         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1019         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1020         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1021         sprintf(buf + strlen(buf), "\r\n");
1022
1023         vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
1024                           vpninfo->hostname, vpninfo->port);
1025
1026         if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1027                 result = -errno;
1028                 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
1029                                   strerror(errno));
1030                 return result;
1031         }
1032
1033         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1034                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
1035                 return -EIO;
1036         }
1037
1038         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1039             buf[8] != ' ' || !(result = atoi(buf+9))) {
1040                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
1041                                   buf);
1042                 return -EINVAL;
1043         }
1044
1045         if (result != 200) {
1046                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
1047                                   buf);
1048                 return -EIO;
1049         }
1050
1051         while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1052                 if (buflen < 0) {
1053                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
1054                         return -EIO;
1055                 }
1056                 vpninfo->progress(vpninfo, PRG_ERR,
1057                                   "Unexpected continuation line after CONNECT response: '%s'\n",
1058                                   buf);
1059         }
1060
1061         return 0;
1062 }
1063
1064 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1065 {
1066         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1067                 return process_http_proxy(vpninfo, ssl_sock);
1068         
1069         if (!strcmp(vpninfo->proxy_type, "socks") ||
1070             !strcmp(vpninfo->proxy_type, "socks5"))
1071                 return process_socks_proxy(vpninfo, ssl_sock);
1072
1073         vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
1074                                   vpninfo->proxy_type);
1075         return -EIO;
1076 }
1077
1078 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1079 {
1080         char *url = strdup(proxy);
1081         int ret;
1082
1083         if (!url)
1084                 return -ENOMEM;
1085
1086         free(vpninfo->proxy_type);
1087         vpninfo->proxy_type = NULL;
1088         free(vpninfo->proxy);
1089         vpninfo->proxy = NULL;
1090
1091         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1092                                  &vpninfo->proxy_port, NULL, 80);
1093         if (ret)
1094                 goto out;
1095
1096         if (vpninfo->proxy_type &&
1097             strcmp(vpninfo->proxy_type, "http") &&
1098             strcmp(vpninfo->proxy_type, "socks") &&
1099             strcmp(vpninfo->proxy_type, "socks5")) {
1100                 vpninfo->progress(vpninfo, PRG_ERR,
1101                                   "Only http or socks(5) proxies supported\n");
1102                 free(vpninfo->proxy_type);
1103                 vpninfo->proxy_type = NULL;
1104                 free(vpninfo->proxy);
1105                 vpninfo->proxy = NULL;
1106                 return -EINVAL;
1107         }
1108  out:
1109         free(url);
1110         return ret;
1111 }