Add --csd-wrapper
[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 && !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 openconnect_parse_url(char *url, char **res_proto, char **res_host, int *res_port,
524               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 (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
603                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
604                         vpninfo->hostname);
605                 return -EINVAL;
606         }
607
608         /*
609          * It would be nice to use cURL for this, but we really need to guarantee
610          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
611          * to have any way to let us provide our own socket read/write functions.
612          * We can only provide a socket _open_ function. Which would require having
613          * a socketpair() and servicing the "other" end of it.
614          *
615          * So we process the HTTP for ourselves...
616          */
617         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
618         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
619         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
620         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
621         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
622
623         if (vpninfo->cookies) {
624                 sprintf(buf + strlen(buf),  "Cookie: ");
625                 for (opt = vpninfo->cookies; opt; opt = opt->next)
626                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
627                                       opt->value, opt->next ? "; " : "\r\n");
628         }
629         if (request_body_type) {
630                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
631                               request_body_type);
632                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
633                               strlen(request_body));
634         }
635         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
636         if (request_body_type)
637                 sprintf(buf + strlen(buf), "%s", request_body);
638
639         if (vpninfo->port == 443)
640                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
641                                   method, vpninfo->hostname,
642                                   vpninfo->urlpath ?: "");
643         else
644                 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
645                                   method, vpninfo->hostname, vpninfo->port,
646                                   vpninfo->urlpath ?: "");
647
648         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
649
650         buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
651         if (buflen < 0) {
652                 /* We'll already have complained about whatever offended us */
653                 exit(1);
654         }
655
656         if (result != 200 && vpninfo->redirect_url) {
657         redirect:
658                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
659                         /* New host. Tear down the existing connection and make a new one */
660                         char *host;
661                         int port;
662                         int ret;
663
664                         free(vpninfo->urlpath);
665                         vpninfo->urlpath = NULL;
666
667                         ret = openconnect_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
668                         if (ret) {
669                                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse redirected URL '%s': %s\n",
670                                                   vpninfo->redirect_url, strerror(-ret));
671                                 free(vpninfo->redirect_url);
672                                 free(form_buf);
673                                 return ret;
674                         }
675
676                         if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
677                                 free(vpninfo->hostname);
678                                 vpninfo->hostname = host;
679                                 vpninfo->port = port;
680
681                                 /* Kill the existing connection, and a new one will happen */
682                                 free(vpninfo->peer_addr);
683                                 vpninfo->peer_addr = NULL;
684                                 if (vpninfo->https_ssl) {
685                                         SSL_free(vpninfo->https_ssl);
686                                         vpninfo->https_ssl = NULL;
687                                         close(vpninfo->ssl_fd);
688                                         vpninfo->ssl_fd = -1;
689                                 }
690
691                                 for (opt = vpninfo->cookies; opt; opt = next) {
692                                         next = opt->next;
693
694                                         free(opt->option);
695                                         free(opt->value);
696                                         free(opt);
697                                 }
698                                 vpninfo->cookies = NULL;
699                         } else
700                                 free(host);
701
702                         free(vpninfo->redirect_url);
703                         vpninfo->redirect_url = NULL;
704
705                         goto retry;
706                 } else if (vpninfo->redirect_url[0] == '/') {
707                         /* Absolute redirect within same host */
708                         free(vpninfo->urlpath);
709                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
710                         free(vpninfo->redirect_url);
711                         vpninfo->redirect_url = NULL;
712                         goto retry;
713                 } else {
714                         char *lastslash = NULL;
715                         if (vpninfo->urlpath)
716                                 lastslash = strrchr(vpninfo->urlpath, '/');
717                         if (!lastslash) {
718                                 free(vpninfo->urlpath);
719                                 vpninfo->urlpath = vpninfo->redirect_url;
720                                 vpninfo->redirect_url = NULL;
721                         } else {
722                                 char *oldurl = vpninfo->urlpath;
723                                 *lastslash = 0;
724                                 vpninfo->urlpath = NULL;
725                                 if (asprintf(&vpninfo->urlpath, "%s/%s",
726                                              oldurl, vpninfo->redirect_url) == -1) {
727                                         int err = -errno;
728                                         vpninfo->progress(vpninfo, PRG_ERR,
729                                                           "Allocating new path for relative redirect failed: %s\n",
730                                                           strerror(-err));
731                                         return err;
732                                 }
733                                 free(oldurl);
734                                 free(vpninfo->redirect_url);
735                                 vpninfo->redirect_url = NULL;
736                         }
737                         goto retry;
738                 }
739         }
740         if (!form_buf || result != 200) {
741                 vpninfo->progress(vpninfo, PRG_ERR,
742                                   "Unexpected %d result from server\n",
743                                   result);
744                 free(form_buf);
745                 return -EINVAL;
746         }
747         if (vpninfo->csd_stuburl) {
748                 /* This is the CSD stub script, which we now need to run */
749                 result = run_csd_script(vpninfo, form_buf, buflen);
750                 if (result) {
751                         free(form_buf);
752                         return result;
753                 }
754
755                 /* Now we'll be redirected to the waiturl */
756                 goto retry;
757         }
758         if (strncmp(form_buf, "<?xml", 5)) {
759                 /* Not XML? Perhaps it's HTML with a refresh... */
760                 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
761                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
762                                           vpninfo->urlpath);
763                         sleep(1);
764                         goto retry;
765                 }
766                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
767                 free(form_buf);
768                 return -EINVAL;
769         }
770         request_body[0] = 0;
771         result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
772                                     &method, &request_body_type);
773
774         if (!result)
775                 goto redirect;
776
777         free(form_buf);
778
779         if (result != 2)
780                 return result;
781
782         /* A return value of 2 means the XML form indicated
783            success. We _should_ have a cookie... */
784
785         for (opt = vpninfo->cookies; opt; opt = opt->next) {
786
787                 if (!strcmp(opt->option, "webvpn"))
788                         vpninfo->cookie = opt->value;
789                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
790                         char *tok = opt->value;
791                         char *bu = NULL, *fu = NULL, *sha = NULL;
792
793                         do {
794                                 if (tok != opt->value)
795                                         *(tok++) = 0;
796
797                                 if (!strncmp(tok, "bu:", 3))
798                                         bu = tok + 3;
799                                 else if (!strncmp(tok, "fu:", 3))
800                                         fu = tok + 3;
801                                 else if (!strncmp(tok, "fh:", 3)) {
802                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
803                                                          SHA_DIGEST_LENGTH * 2))
804                                                 break;
805                                         sha = tok + 3;
806                                 }
807                         } while ((tok = strchr(tok, '&')));
808
809                         if (bu && fu && sha)
810                                 fetch_config(vpninfo, bu, fu, sha);
811                 }
812         }
813         if (vpninfo->csd_scriptname) {
814                 unlink(vpninfo->csd_scriptname);
815                 free(vpninfo->csd_scriptname);
816                 vpninfo->csd_scriptname = NULL;
817         }
818         return 0;
819 }
820
821 char *openconnect_create_useragent(char *base)
822 {
823         char *uagent;
824
825         if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
826                 return NULL;
827
828         return uagent;
829 }
830
831 static int proxy_gets(int fd, char *buf, size_t len)
832 {
833         int i = 0;
834         int ret;
835
836         if (len < 2)
837                 return -EINVAL;
838
839         while ( (ret = read(fd, buf + i, 1)) == 1) {
840                 if (buf[i] == '\n') {
841                         buf[i] = 0;
842                         if (i && buf[i-1] == '\r') {
843                                 buf[i-1] = 0;
844                                 i--;
845                         }
846                         return i;
847                 }
848                 i++;
849
850                 if (i >= len - 1) {
851                         buf[i] = 0;
852                         return i;
853                 }
854         }
855         if (ret < 0)
856                 ret = -errno;
857
858         buf[i] = 0;
859         return i ?: ret;
860 }
861
862 static int proxy_write(int fd, unsigned char *buf, size_t len)
863 {
864         size_t count;
865         
866         for (count = 0; count < len; ) {
867                 int i = write(fd, buf + count, len - count);
868                 if (i < 0)
869                         return -errno;
870
871                 count += i;
872         }
873         return 0;
874 }
875
876 static int proxy_read(int fd, unsigned char *buf, size_t len)
877 {
878         size_t count;
879
880         for (count = 0; count < len; ) {
881                 int i = read(fd, buf + count, len - count);
882                 if (i < 0)
883                         return -errno;
884
885                 count += i;
886         }
887         return 0;
888 }
889
890 static const char *socks_errors[] = {
891         "request granted",
892         "general failure",
893         "connection not allowed by ruleset",
894         "network unreachable",
895         "host unreachable",
896         "connection refused by destination host",
897         "TTL expired",
898         "command not supported / protocol error",
899         "address type not supported"
900 };
901
902 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
903 {
904         unsigned char buf[1024];
905         int i;
906
907         buf[0] = 5; /* SOCKS version */
908         buf[1] = 1; /* # auth methods */
909         buf[2] = 0; /* No auth supported */
910
911         if ((i = proxy_write(ssl_sock, buf, 3))) {
912                 vpninfo->progress(vpninfo, PRG_ERR,
913                                   "Error writing auth request to SOCKS proxy: %s\n",
914                                   strerror(-i));
915                 return i;
916         }
917         
918         if ((i = proxy_read(ssl_sock, buf, 2))) {
919                 vpninfo->progress(vpninfo, PRG_ERR,
920                                   "Error reading auth response from SOCKS proxy: %s\n",
921                                   strerror(-i));
922                 return i;
923         }
924         if (buf[0] != 5) {
925                 vpninfo->progress(vpninfo, PRG_ERR,
926                                   "Unexpected auth response from SOCKS proxy: %02x %02x\n",
927                                   buf[0], buf[1]);
928                 return -EIO;
929         }
930         if (buf[1]) {
931         socks_err:
932                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
933                         vpninfo->progress(vpninfo, PRG_ERR,
934                                           "SOCKS proxy error %02x: %s\n",
935                                           buf[1], socks_errors[buf[1]]);
936                 else
937                         vpninfo->progress(vpninfo, PRG_ERR,
938                                           "SOCKS proxy error %02x\n",
939                                           buf[1]);
940                 return -EIO;
941         }
942
943         vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
944                           vpninfo->hostname, vpninfo->port);
945
946         buf[0] = 5; /* SOCKS version */
947         buf[1] = 1; /* CONNECT */
948         buf[2] = 0; /* Reserved */
949         buf[3] = 3; /* Address type is domain name */
950         buf[4] = strlen(vpninfo->hostname);
951         strcpy((char *)buf + 5, vpninfo->hostname);
952         i = strlen(vpninfo->hostname) + 5;
953         buf[i++] = vpninfo->port >> 8;
954         buf[i++] = vpninfo->port & 0xff;
955
956         if ((i = proxy_write(ssl_sock, buf, i))) {
957                 vpninfo->progress(vpninfo, PRG_ERR,
958                                   "Error writing connect request to SOCKS proxy: %s\n",
959                                   strerror(-i));
960                 return i;
961         }
962         /* Read 5 bytes -- up to and including the first byte of the returned
963            address (which might be the length byte of a domain name) */
964         if ((i = proxy_read(ssl_sock, buf, 5))) {
965                 vpninfo->progress(vpninfo, PRG_ERR,
966                                   "Error reading connect response from SOCKS proxy: %s\n",
967                                   strerror(-i));
968                 return i;
969         }
970         if (buf[0] != 5) {
971                 vpninfo->progress(vpninfo, PRG_ERR,
972                                   "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
973                                   buf[0], buf[1]);
974                 return -EIO;
975         }
976         if (buf[1])
977                 goto socks_err;
978
979         /* Connect responses contain an address */
980         switch(buf[3]) {
981         case 1: /* Legacy IP */
982                 i = 5;
983                 break;
984         case 3: /* Domain name */
985                 i = buf[4] + 2;
986                 break;
987         case 4: /* IPv6 */
988                 i = 17;
989                 break;
990         default:
991                 vpninfo->progress(vpninfo, PRG_ERR,
992                                   "Unexpected address type %02x in SOCKS connect response\n",
993                                   buf[3]);
994                 return -EIO;
995         }
996
997         if ((i = proxy_read(ssl_sock, buf, i))) {
998                 vpninfo->progress(vpninfo, PRG_ERR,
999                                   "Error reading connect response from SOCKS proxy: %s\n",
1000                                   strerror(-i));
1001                 return i;
1002         }
1003         return 0;
1004 }
1005
1006 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1007 {
1008         char buf[MAX_BUF_LEN];
1009         int buflen, result;
1010
1011         sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1012         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1013         sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1014         sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1015         sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1016         sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1017         sprintf(buf + strlen(buf), "\r\n");
1018
1019         vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
1020                           vpninfo->hostname, vpninfo->port);
1021
1022         if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1023                 result = -errno;
1024                 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
1025                                   strerror(errno));
1026                 return result;
1027         }
1028
1029         if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1030                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
1031                 return -EIO;
1032         }
1033
1034         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1035             buf[8] != ' ' || !(result = atoi(buf+9))) {
1036                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
1037                                   buf);
1038                 return -EINVAL;
1039         }
1040
1041         if (result != 200) {
1042                 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
1043                                   buf);
1044                 return -EIO;
1045         }
1046
1047         while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1048                 if (buflen < 0) {
1049                         vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
1050                         return -EIO;
1051                 }
1052                 vpninfo->progress(vpninfo, PRG_ERR,
1053                                   "Unexpected continuation line after CONNECT response: '%s'\n",
1054                                   buf);
1055         }
1056
1057         return 0;
1058 }
1059
1060 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1061 {
1062         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1063                 return process_http_proxy(vpninfo, ssl_sock);
1064         
1065         if (!strcmp(vpninfo->proxy_type, "socks") ||
1066             !strcmp(vpninfo->proxy_type, "socks5"))
1067                 return process_socks_proxy(vpninfo, ssl_sock);
1068
1069         vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
1070                                   vpninfo->proxy_type);
1071         return -EIO;
1072 }
1073
1074 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1075 {
1076         char *url = strdup(proxy);
1077         int ret;
1078
1079         if (!url)
1080                 return -ENOMEM;
1081
1082         free(vpninfo->proxy_type);
1083         vpninfo->proxy_type = NULL;
1084         free(vpninfo->proxy);
1085         vpninfo->proxy = NULL;
1086
1087         ret = openconnect_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1088                         &vpninfo->proxy_port, NULL, 80);
1089         if (ret)
1090                 goto out;
1091
1092         if (vpninfo->proxy_type &&
1093             strcmp(vpninfo->proxy_type, "http") &&
1094             strcmp(vpninfo->proxy_type, "socks") &&
1095             strcmp(vpninfo->proxy_type, "socks5")) {
1096                 vpninfo->progress(vpninfo, PRG_ERR,
1097                                   "Only http or socks(5) proxies supported\n");
1098                 free(vpninfo->proxy_type);
1099                 vpninfo->proxy_type = NULL;
1100                 free(vpninfo->proxy);
1101                 vpninfo->proxy = NULL;
1102                 return -EINVAL;
1103         }
1104  out:
1105         free(url);
1106         return ret;
1107 }