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