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