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