http: Rewrite openconnect_obtain_cookie() loop
[platform/upstream/openconnect.git] / http.c
1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2012 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 #include <netdb.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <time.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <pwd.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <errno.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <stdarg.h>
39
40 #include "openconnect-internal.h"
41
42 static int proxy_write(struct openconnect_info *vpninfo, int fd,
43                        unsigned char *buf, size_t len);
44 static int proxy_read(struct openconnect_info *vpninfo, int fd,
45                       unsigned char *buf, size_t len);
46
47 #define MAX_BUF_LEN 131072
48 #define BUF_CHUNK_SIZE 4096
49
50 struct oc_text_buf {
51         char *data;
52         int pos;
53         int buf_len;
54         int error;
55 };
56
57 static struct oc_text_buf *buf_alloc(void)
58 {
59         return calloc(1, sizeof(struct oc_text_buf));
60 }
61
62 static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
63 {
64         va_list ap;
65
66         if (!buf || buf->error)
67                 return;
68
69         if (!buf->data) {
70                 buf->data = malloc(BUF_CHUNK_SIZE);
71                 if (!buf->data) {
72                         buf->error = -ENOMEM;
73                         return;
74                 }
75                 buf->buf_len = BUF_CHUNK_SIZE;
76         }
77
78         while (1) {
79                 int max_len = buf->buf_len - buf->pos, ret;
80
81                 va_start(ap, fmt);
82                 ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
83                 va_end(ap);
84                 if (ret < 0) {
85                         buf->error = -EIO;
86                         break;
87                 } else if (ret < max_len) {
88                         buf->pos += ret;
89                         break;
90                 } else {
91                         int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
92
93                         if (new_buf_len > MAX_BUF_LEN) {
94                                 /* probably means somebody is messing with us */
95                                 buf->error = -E2BIG;
96                                 break;
97                         }
98
99                         buf->data = realloc(buf->data, new_buf_len);
100                         if (!buf->data) {
101                                 buf->error = -ENOMEM;
102                                 break;
103                         }
104                         buf->buf_len = new_buf_len;
105                 }
106         }
107 }
108
109 static int buf_error(struct oc_text_buf *buf)
110 {
111         return buf ? buf->error : -ENOMEM;
112 }
113
114 static int buf_free(struct oc_text_buf *buf)
115 {
116         int error = buf_error(buf);
117
118         if (buf) {
119                 if (buf->data)
120                         free(buf->data);
121                 free(buf);
122         }
123
124         return error;
125 }
126
127 /*
128  * We didn't really want to have to do this for ourselves -- one might have
129  * thought that it would be available in a library somewhere. But neither
130  * cURL nor Neon have reliable cross-platform ways of either using a cert
131  * from the TPM, or just reading from / writing to a transport which is
132  * provided by their caller.
133  */
134
135 static int http_add_cookie(struct openconnect_info *vpninfo,
136                            const char *option, const char *value)
137 {
138         struct vpn_option *new, **this;
139
140         if (*value) {
141                 new = malloc(sizeof(*new));
142                 if (!new) {
143                         vpn_progress(vpninfo, PRG_ERR,
144                                      _("No memory for allocating cookies\n"));
145                         return -ENOMEM;
146                 }
147                 new->next = NULL;
148                 new->option = strdup(option);
149                 new->value = strdup(value);
150                 if (!new->option || !new->value) {
151                         free(new->option);
152                         free(new->value);
153                         free(new);
154                         return -ENOMEM;
155                 }
156         } else {
157                 /* Kill cookie; don't replace it */
158                 new = NULL;
159         }
160         for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
161                 if (!strcmp(option, (*this)->option)) {
162                         /* Replace existing cookie */
163                         if (new)
164                                 new->next = (*this)->next;
165                         else
166                                 new = (*this)->next;
167                         
168                         free((*this)->option);
169                         free((*this)->value);
170                         free(*this);
171                         *this = new;
172                         break;
173                 }
174         }
175         if (new && !*this) {
176                 *this = new;
177                 new->next = NULL;
178         }
179         return 0;
180 }
181
182 #define BODY_HTTP10 -1
183 #define BODY_CHUNKED -2
184
185 static int process_http_response(struct openconnect_info *vpninfo, int *result,
186                                  int (*header_cb)(struct openconnect_info *, char *, char *),
187                                  char **body_ret)
188 {
189         char buf[MAX_BUF_LEN];
190         char *body = NULL;
191         int bodylen = BODY_HTTP10;
192         int done = 0;
193         int closeconn = 0;
194         int i;
195
196  cont:
197         if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
198                 vpn_progress(vpninfo, PRG_ERR,
199                              _("Error fetching HTTPS response\n"));
200                 return -EINVAL;
201         }
202
203         if (!strncmp(buf, "HTTP/1.0 ", 9))
204                 closeconn = 1;
205         
206         if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
207                 vpn_progress(vpninfo, PRG_ERR,
208                              _("Failed to parse HTTP response '%s'\n"), buf);
209                 return -EINVAL;
210         }
211
212         vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
213                      _("Got HTTP response: %s\n"), buf);
214
215         /* Eat headers... */
216         while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
217                 char *colon;
218
219                 if (i < 0) {
220                         vpn_progress(vpninfo, PRG_ERR,
221                                      _("Error processing HTTP response\n"));
222                         return -EINVAL;
223                 }
224                 colon = strchr(buf, ':');
225                 if (!colon) {
226                         vpn_progress(vpninfo, PRG_ERR,
227                                      _("Ignoring unknown HTTP response line '%s'\n"), buf);
228                         continue;
229                 }
230                 *(colon++) = 0;
231                 if (*colon == ' ')
232                         colon++;
233
234                 /* Handle Set-Cookie first so that we can avoid printing the
235                    webvpn cookie in the verbose debug output */
236                 if (!strcasecmp(buf, "Set-Cookie")) {
237                         char *semicolon = strchr(colon, ';');
238                         const char *print_equals;
239                         char *equals = strchr(colon, '=');
240                         int ret;
241
242                         if (semicolon)
243                                 *semicolon = 0;
244
245                         if (!equals) {
246                                 vpn_progress(vpninfo, PRG_ERR,
247                                              _("Invalid cookie offered: %s\n"), buf);
248                                 return -EINVAL;
249                         }
250                         *(equals++) = 0;
251
252                         print_equals = equals;
253                         /* Don't print the webvpn cookie unless it's empty; we don't
254                            want people posting it in public with debugging output */
255                         if (!strcmp(colon, "webvpn") && *equals)
256                                 print_equals = _("<elided>");
257                         vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
258                                      buf, colon, print_equals, semicolon?";":"",
259                                      semicolon?(semicolon+1):"");
260
261                         /* The server tends to ask for the username and password as
262                            usual, even if we've already failed because it didn't like
263                            our cert. Thankfully it does give us this hint... */
264                         if (!strcmp(colon, "ClientCertAuthFailed"))
265                                 vpn_progress(vpninfo, PRG_ERR,
266                                              _("SSL certificate authentication failed\n"));
267
268                         ret = http_add_cookie(vpninfo, colon, equals);
269                         if (ret)
270                                 return ret;
271                 } else {
272                         vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
273                 }
274
275                 if (!strcasecmp(buf, "Connection")) {
276                         if (!strcasecmp(colon, "Close"))
277                                 closeconn = 1;
278 #if 0
279                         /* This might seem reasonable, but in fact it breaks
280                            certificate authentication with some servers. If
281                            they give an HTTP/1.0 response, even if they
282                            explicitly give a Connection: Keep-Alive header,
283                            just close the connection. */
284                         else if (!strcasecmp(colon, "Keep-Alive"))
285                                 closeconn = 0;
286 #endif
287                 }
288                 if (!strcasecmp(buf, "Location")) {
289                         vpninfo->redirect_url = strdup(colon);
290                         if (!vpninfo->redirect_url)
291                                 return -ENOMEM;
292                 }
293                 if (!strcasecmp(buf, "Content-Length")) {
294                         bodylen = atoi(colon);
295                         if (bodylen < 0) {
296                                 vpn_progress(vpninfo, PRG_ERR,
297                                              _("Response body has negative size (%d)\n"),
298                                              bodylen);
299                                 return -EINVAL;
300                         }
301                 }
302                 if (!strcasecmp(buf, "Transfer-Encoding")) {
303                         if (!strcasecmp(colon, "chunked"))
304                                 bodylen = BODY_CHUNKED;
305                         else {
306                                 vpn_progress(vpninfo, PRG_ERR,
307                                              _("Unknown Transfer-Encoding: %s\n"),
308                                              colon);
309                                 return -EINVAL;
310                         }
311                 }
312                 if (header_cb && !strncmp(buf, "X-", 2))
313                         header_cb(vpninfo, buf, colon);
314         }
315
316         /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
317         if (*result == 100)
318                 goto cont;
319
320         /* Now the body, if there is one */
321         vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"), 
322                      bodylen==BODY_HTTP10?"http 1.0" :
323                      bodylen==BODY_CHUNKED?"chunked" : "length: ",
324                      bodylen);
325
326         /* If we were given Content-Length, it's nice and easy... */
327         if (bodylen > 0) {
328                 body = malloc(bodylen + 1);
329                 if (!body)
330                         return -ENOMEM;
331                 while (done < bodylen) {
332                         i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
333                         if (i < 0) {
334                                 vpn_progress(vpninfo, PRG_ERR,
335                                              _("Error reading HTTP response body\n"));
336                                 free(body);
337                                 return -EINVAL;
338                         }
339                         done += i;
340                 }
341         } else if (bodylen == BODY_CHUNKED) {
342                 /* ... else, chunked */
343                 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
344                         int chunklen, lastchunk = 0;
345
346                         if (i < 0) {
347                                 vpn_progress(vpninfo, PRG_ERR,
348                                              _("Error fetching chunk header\n"));
349                                 return i;
350                         }
351                         chunklen = strtol(buf, NULL, 16);
352                         if (!chunklen) {
353                                 lastchunk = 1;
354                                 goto skip;
355                         }
356                         body = realloc(body, done + chunklen + 1);
357                         if (!body)
358                                 return -ENOMEM;
359                         while (chunklen) {
360                                 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
361                                 if (i < 0) {
362                                         vpn_progress(vpninfo, PRG_ERR,
363                                                      _("Error reading HTTP response body\n"));
364                                         free(body);
365                                         return -EINVAL;
366                                 }
367                                 chunklen -= i;
368                                 done += i;
369                         }
370                 skip:
371                         if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
372                                 if (i < 0) {
373                                         vpn_progress(vpninfo, PRG_ERR,
374                                                      _("Error fetching HTTP response body\n"));
375                                 } else {
376                                         vpn_progress(vpninfo, PRG_ERR,
377                                                      _("Error in chunked decoding. Expected '', got: '%s'"),
378                                                      buf);
379                                 }
380                                 free(body);
381                                 return -EINVAL;
382                         }
383
384                         if (lastchunk)
385                                 break;
386                 }
387         } else if (bodylen == BODY_HTTP10) {
388                 if (!closeconn) {
389                         vpn_progress(vpninfo, PRG_ERR,
390                                      _("Cannot receive HTTP 1.0 body without closing connection\n"));
391                         return -EINVAL;
392                 }
393
394                 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
395                 while (1) {
396                         body = realloc(body, done + 16384);
397                         if (!body)
398                                 return -ENOMEM;
399                         i = openconnect_SSL_read(vpninfo, body + done, 16384);
400                         if (i > 0) {
401                                 /* Got more data */
402                                 done += i;
403                         } else if (i < 0) {
404                                 /* Error */
405                                 free(body);
406                                 return i;
407                         } else {
408                                 /* Connection closed. Reduce allocation to just what we need */
409                                 body = realloc(body, done + 1);
410                                 if (!body)
411                                         return -ENOMEM;
412                                 break;
413                         }
414                 }
415         }
416
417         if (closeconn || vpninfo->no_http_keepalive)
418                 openconnect_close_https(vpninfo, 0);
419
420         if (body)
421                 body[done] = 0;
422         *body_ret = body;
423         return done;
424 }
425
426 static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
427 {
428         struct vpn_option *opt;
429
430         buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
431         buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
432         buf_append(buf, "Accept: */*\r\n");
433         buf_append(buf, "Accept-Encoding: identity\r\n");
434
435         if (vpninfo->cookies) {
436                 buf_append(buf, "Cookie: ");
437                 for (opt = vpninfo->cookies; opt; opt = opt->next)
438                         buf_append(buf, "%s=%s%s", opt->option,
439                                       opt->value, opt->next ? "; " : "\r\n");
440         }
441         buf_append(buf, "X-Transcend-Version: 1\r\n");
442         buf_append(buf, "X-Aggregate-Auth: 1\r\n");
443         buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
444 }
445
446 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
447                         char *server_sha1)
448 {
449         struct oc_text_buf *buf;
450         char *config_buf = NULL;
451         int result, buflen;
452         unsigned char local_sha1_bin[SHA1_SIZE];
453         char local_sha1_ascii[(SHA1_SIZE * 2)+1];
454         int i;
455
456         if (openconnect_open_https(vpninfo)) {
457                 vpn_progress(vpninfo, PRG_ERR,
458                              _("Failed to open HTTPS connection to %s\n"),
459                              vpninfo->hostname);
460                 return -EINVAL;
461         }
462
463         buf = buf_alloc();
464         buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
465         add_common_headers(vpninfo, buf);
466         buf_append(buf, "\r\n");
467
468         if (buf_error(buf))
469                 return buf_free(buf);
470
471         if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
472                 vpn_progress(vpninfo, PRG_ERR,
473                              _("Failed to send GET request for new config\n"));
474                 buf_free(buf);
475                 return -EIO;
476         }
477         buf_free(buf);
478
479         buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
480         if (buflen < 0) {
481                 /* We'll already have complained about whatever offended us */
482                 return -EINVAL;
483         }
484
485         if (result != 200) {
486                 free(config_buf);
487                 return -EINVAL;
488         }
489
490         openconnect_sha1(local_sha1_bin, config_buf, buflen);
491
492         for (i = 0; i < SHA1_SIZE; i++)
493                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
494
495         if (strcasecmp(server_sha1, local_sha1_ascii)) {
496                 vpn_progress(vpninfo, PRG_ERR,
497                              _("Downloaded config file did not match intended SHA1\n"));
498                 free(config_buf);
499                 return -EINVAL;
500         }
501
502         result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
503         free(config_buf);
504         return result;
505 }
506
507 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
508 {
509         char fname[16];
510         int fd, ret;
511
512         if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
513                 vpn_progress(vpninfo, PRG_ERR,
514                              _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
515                                "This facility is disabled by default for security reasons, so you may wish to enable it."));
516                 return -EPERM;
517         }
518
519 #ifndef __linux__
520         vpn_progress(vpninfo, PRG_INFO,
521                      _("Trying to run Linux CSD trojan script."));
522 #endif
523
524         sprintf(fname, "/tmp/csdXXXXXX");
525         fd = mkstemp(fname);
526         if (fd < 0) {
527                 int err = -errno;
528                 vpn_progress(vpninfo, PRG_ERR,
529                              _("Failed to open temporary CSD script file: %s\n"),
530                              strerror(errno));
531                 return err;
532         }
533
534         ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
535         if (ret) {
536                 vpn_progress(vpninfo, PRG_ERR,
537                              _("Failed to write temporary CSD script file: %s\n"),
538                              strerror(-ret));
539                 return ret;
540         }
541         fchmod(fd, 0755);
542         close(fd);
543
544         if (!fork()) {
545                 char scertbuf[MD5_SIZE * 2 + 1];
546                 char ccertbuf[MD5_SIZE * 2 + 1];
547                 char *csd_argv[32];
548                 int i = 0;
549
550                 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
551                         struct passwd *pw;
552
553                         if (setuid(vpninfo->uid_csd)) {
554                                 fprintf(stderr, _("Failed to set uid %ld\n"),
555                                         (long)vpninfo->uid_csd);
556                                 exit(1);
557                         }
558                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
559                                 fprintf(stderr, _("Invalid user uid=%ld\n"),
560                                         (long)vpninfo->uid_csd);
561                                 exit(1);
562                         }
563                         setenv("HOME", pw->pw_dir, 1);
564                         if (chdir(pw->pw_dir)) {
565                                 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
566                                         pw->pw_dir, strerror(errno));
567                                 exit(1);
568                         }
569                 }
570                 if (getuid() == 0 && !vpninfo->csd_wrapper) {
571                         fprintf(stderr, _("Warning: you are running insecure "
572                                           "CSD code with root privileges\n"
573                                           "\t Use command line option \"--csd-user\"\n"));
574                 }
575                 if (vpninfo->uid_csd_given == 2) {
576                         /* The NM tool really needs not to get spurious output
577                            on stdout, which the CSD trojan spews. */
578                         dup2(2, 1);
579                 }
580                 if (vpninfo->csd_wrapper)
581                         csd_argv[i++] = vpninfo->csd_wrapper;
582                 csd_argv[i++] = fname;
583                 csd_argv[i++]= (char *)"-ticket";
584                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
585                         goto out;
586                 csd_argv[i++]= (char *)"-stub";
587                 csd_argv[i++]= (char *)"\"0\"";
588                 csd_argv[i++]= (char *)"-group";
589                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
590                         goto out;
591
592                 openconnect_local_cert_md5(vpninfo, ccertbuf);
593                 scertbuf[0] = 0;
594                 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
595                 csd_argv[i++]= (char *)"-certhash";
596                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
597                         goto out;
598
599                 csd_argv[i++]= (char *)"-url";
600                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
601                         goto out;
602
603                 csd_argv[i++]= (char *)"-langselen";
604                 csd_argv[i++] = NULL;
605
606                 if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
607                         goto out;
608                 if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
609                         goto out;
610
611                 execv(csd_argv[0], csd_argv);
612
613 out:
614                 vpn_progress(vpninfo, PRG_ERR,
615                              _("Failed to exec CSD script %s\n"), csd_argv[0]);
616                 exit(1);
617         }
618
619         free(vpninfo->csd_stuburl);
620         vpninfo->csd_stuburl = NULL;
621         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
622                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
623         vpninfo->csd_waiturl = NULL;
624         vpninfo->csd_scriptname = strdup(fname);
625
626         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
627
628         return 0;
629 }
630
631 int internal_parse_url(char *url, char **res_proto, char **res_host,
632                        int *res_port, char **res_path, int default_port)
633 {
634         char *proto = url;
635         char *host, *path, *port_str;
636         int port;
637
638         host = strstr(url, "://");
639         if (host) {
640                 *host = 0;
641                 host += 3;
642
643                 if (!strcasecmp(proto, "https"))
644                         port = 443;
645                 else if (!strcasecmp(proto, "http"))
646                         port = 80;
647                 else if (!strcasecmp(proto, "socks") ||
648                          !strcasecmp(proto, "socks4") ||
649                          !strcasecmp(proto, "socks5"))
650                         port = 1080;
651                 else
652                         return -EPROTONOSUPPORT;
653         } else {
654                 if (default_port) {
655                         proto = NULL;
656                         port = default_port;
657                         host = url;
658                 } else
659                         return -EINVAL;
660         }
661
662         path = strchr(host, '/');
663         if (path)
664                 *(path++) = 0;
665
666         port_str = strrchr(host, ':');
667         if (port_str) {
668                 char *end;
669                 int new_port = strtol(port_str + 1, &end, 10);
670
671                 if (!*end) {
672                         *port_str = 0;
673                         port = new_port;
674                 }
675         }
676
677         if (res_proto)
678                 *res_proto = proto ? strdup(proto) : NULL;
679         if (res_host)
680                 *res_host = strdup(host);
681         if (res_port)
682                 *res_port = port;
683         if (res_path)
684                 *res_path = (path && *path) ? strdup(path) : NULL;
685
686         /* Undo the damage we did to the original string */
687         if (port_str)
688                 *(port_str) = ':';
689         if (path)
690                 *(path - 1) = '/';
691         if (proto)
692                 *(host - 3) = ':';
693         return 0;
694 }
695
696 static void clear_cookies(struct openconnect_info *vpninfo)
697 {
698         struct vpn_option *opt, *next;
699
700         for (opt = vpninfo->cookies; opt; opt = next) {
701                 next = opt->next;
702
703                 free(opt->option);
704                 free(opt->value);
705                 free(opt);
706         }
707         vpninfo->cookies = NULL;
708 }
709
710 /* Return value:
711  *  < 0, on error
712  *  = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
713  */
714 static int handle_redirect(struct openconnect_info *vpninfo)
715 {
716         vpninfo->redirect_type = REDIR_TYPE_LOCAL;
717
718         if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
719                 /* New host. Tear down the existing connection and make a new one */
720                 char *host;
721                 int port;
722                 int ret;
723
724                 free(vpninfo->urlpath);
725                 vpninfo->urlpath = NULL;
726
727                 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
728                 if (ret) {
729                         vpn_progress(vpninfo, PRG_ERR,
730                                      _("Failed to parse redirected URL '%s': %s\n"),
731                                      vpninfo->redirect_url, strerror(-ret));
732                         free(vpninfo->redirect_url);
733                         vpninfo->redirect_url = NULL;
734                         return ret;
735                 }
736
737                 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
738                         free(vpninfo->hostname);
739                         vpninfo->hostname = host;
740                         vpninfo->port = port;
741
742                         /* Kill the existing connection, and a new one will happen */
743                         free(vpninfo->peer_addr);
744                         vpninfo->peer_addr = NULL;
745                         openconnect_close_https(vpninfo, 0);
746                         clear_cookies(vpninfo);
747                         vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
748                 } else
749                         free(host);
750
751                 free(vpninfo->redirect_url);
752                 vpninfo->redirect_url = NULL;
753
754                 return 0;
755         } else if (strstr(vpninfo->redirect_url, "://")) {
756                 vpn_progress(vpninfo, PRG_ERR,
757                              _("Cannot follow redirection to non-https URL '%s'\n"),
758                              vpninfo->redirect_url);
759                 free(vpninfo->redirect_url);
760                 vpninfo->redirect_url = NULL;
761                 return -EINVAL;
762         } else if (vpninfo->redirect_url[0] == '/') {
763                 /* Absolute redirect within same host */
764                 free(vpninfo->urlpath);
765                 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
766                 free(vpninfo->redirect_url);
767                 vpninfo->redirect_url = NULL;
768                 return 0;
769         } else {
770                 char *lastslash = NULL;
771                 if (vpninfo->urlpath)
772                         lastslash = strrchr(vpninfo->urlpath, '/');
773                 if (!lastslash) {
774                         free(vpninfo->urlpath);
775                         vpninfo->urlpath = vpninfo->redirect_url;
776                         vpninfo->redirect_url = NULL;
777                 } else {
778                         char *oldurl = vpninfo->urlpath;
779                         *lastslash = 0;
780                         vpninfo->urlpath = NULL;
781                         if (asprintf(&vpninfo->urlpath, "%s/%s",
782                                      oldurl, vpninfo->redirect_url) == -1) {
783                                 int err = -errno;
784                                 vpn_progress(vpninfo, PRG_ERR,
785                                              _("Allocating new path for relative redirect failed: %s\n"),
786                                              strerror(-err));
787                                 return err;
788                         }
789                         free(oldurl);
790                         free(vpninfo->redirect_url);
791                         vpninfo->redirect_url = NULL;
792                 }
793                 return 0;
794         }
795 }
796
797 /* Inputs:
798  *  method:             GET or POST
799  *  vpninfo->hostname:  Host DNS name
800  *  vpninfo->port:      TCP port, typically 443
801  *  vpninfo->urlpath:   Relative path, e.g. /+webvpn+/foo.html
802  *  request_body_type:  Content type for a POST (e.g. text/html).  Can be NULL.
803  *  request_body:       POST content
804  *  form_buf:           Callee-allocated buffer for server content
805  *
806  * Return value:
807  *  < 0, on error
808  *  >=0, on success, indicating the length of the data in *form_buf
809  */
810 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
811                             const char *request_body_type, const char *request_body,
812                             char **form_buf, int fetch_redirect)
813 {
814         struct oc_text_buf *buf;
815         int result, buflen;
816
817  retry:
818         vpninfo->redirect_type = REDIR_TYPE_NONE;
819
820         if (*form_buf) {
821                 free(*form_buf);
822                 *form_buf = NULL;
823         }
824         if (openconnect_open_https(vpninfo)) {
825                 vpn_progress(vpninfo, PRG_ERR,
826                              _("Failed to open HTTPS connection to %s\n"),
827                              vpninfo->hostname);
828                 return -EINVAL;
829         }
830
831         /*
832          * It would be nice to use cURL for this, but we really need to guarantee
833          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
834          * to have any way to let us provide our own socket read/write functions.
835          * We can only provide a socket _open_ function. Which would require having
836          * a socketpair() and servicing the "other" end of it.
837          *
838          * So we process the HTTP for ourselves...
839          */
840         buf = buf_alloc();
841         buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
842         add_common_headers(vpninfo, buf);
843
844         if (request_body_type) {
845                 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
846                 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
847         }
848         buf_append(buf, "\r\n");
849
850         if (request_body_type)
851                 buf_append(buf, "%s", request_body);
852
853         if (vpninfo->port == 443)
854                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
855                              method, vpninfo->hostname,
856                              vpninfo->urlpath ?: "");
857         else
858                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
859                              method, vpninfo->hostname, vpninfo->port,
860                              vpninfo->urlpath ?: "");
861
862         if (buf_error(buf))
863                 return buf_free(buf);
864
865         result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
866         buf_free(buf);
867         if (result < 0)
868                 return result;
869
870         buflen = process_http_response(vpninfo, &result, NULL, form_buf);
871         if (buflen < 0) {
872                 /* We'll already have complained about whatever offended us */
873                 return buflen;
874         }
875
876         if (result != 200 && vpninfo->redirect_url) {
877                 result = handle_redirect(vpninfo);
878                 if (result == 0) {
879                         if (!fetch_redirect)
880                                 return 0;
881                         goto retry;
882                 }
883                 goto out;
884         }
885         if (!*form_buf || result != 200) {
886                 vpn_progress(vpninfo, PRG_ERR,
887                              _("Unexpected %d result from server\n"),
888                              result);
889                 result = -EINVAL;
890                 goto out;
891         }
892
893         return buflen;
894
895  out:
896         free(*form_buf);
897         *form_buf = NULL;
898         return result;
899 }
900
901 /* Return value:
902  *  < 0, if the data is unrecognized
903  *  = 0, if the page contains an XML document
904  *  = 1, if the page is a wait/refresh HTML page
905  */
906 static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
907 {
908         if (strncmp(form_buf, "<?xml", 5)) {
909                 /* Not XML? Perhaps it's HTML with a refresh... */
910                 if (strcasestr(form_buf, "http-equiv=\"refresh\""))
911                         return 1;
912                 vpn_progress(vpninfo, PRG_ERR,
913                              _("Unknown response from server\n"));
914                 return -EINVAL;
915         }
916         return 0;
917 }
918
919 /* Return value:
920  *  < 0, on error
921  *  > 0, no cookie (user cancel)
922  *  = 0, obtained cookie
923  */
924 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
925 {
926         struct vpn_option *opt;
927         char *form_buf = NULL;
928         struct oc_auth_form *form = NULL;
929         int result, buflen, tries;
930         char request_body[2048];
931         const char *request_body_type = "application/x-www-form-urlencoded";
932         const char *method = "POST";
933         int xmlpost = 0;
934
935         /* Step 1: Unlock software token (if applicable) */
936         if (vpninfo->use_stoken) {
937                 result = prepare_stoken(vpninfo);
938                 if (result)
939                         return result;
940         }
941
942         /*
943          * Step 2: Probe for XML POST compatibility
944          *
945          * This can get stuck in a redirect loop, so give up after any of:
946          *
947          * a) HTTP error (e.g. 400 Bad Request)
948          * b) Same-host redirect (e.g. Location: /foo/bar)
949          * c) Three redirects without seeing a plausible login form
950          */
951         result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
952         if (result < 0)
953                 return result;
954
955         for (tries = 0; ; tries++) {
956                 if (tries == 3)
957                         break;
958                 buflen = do_https_request(vpninfo, method, request_body_type, request_body,
959                                           &form_buf, 0);
960                 if (buflen == -EINVAL)
961                         break;
962                 if (buflen < 0)
963                         return buflen;
964
965                 if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
966                         break;
967                 else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
968                         continue;
969
970                 result = parse_xml_response(vpninfo, form_buf, &form);
971                 if (result < 0)
972                         break;
973
974                 xmlpost = 1;
975                 vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
976                 break;
977         }
978
979         /* Step 3: Fetch and parse the login form, if not using XML POST */
980         if (!xmlpost) {
981                 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
982                 if (buflen < 0)
983                         return buflen;
984
985                 result = parse_xml_response(vpninfo, form_buf, &form);
986                 if (result < 0) {
987                         free(form_buf);
988                         return result;
989                 }
990         }
991
992         /* Step 4: Run the CSD trojan, if applicable */
993         if (vpninfo->csd_starturl) {
994                 char *form_path = NULL;
995
996                 if (vpninfo->urlpath) {
997                         form_path = strdup(vpninfo->urlpath);
998                         if (!form_path) {
999                                 result = -ENOMEM;
1000                                 goto out;
1001                         }
1002                 }
1003
1004                 /* fetch the CSD program, if available */
1005                 if (vpninfo->csd_stuburl) {
1006                         buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1007                         if (buflen <= 0) {
1008                                 result = -EINVAL;
1009                                 goto out;
1010                         }
1011                 }
1012
1013                 /* This is the CSD stub script, which we now need to run */
1014                 result = run_csd_script(vpninfo, form_buf, buflen);
1015                 if (result)
1016                         goto out;
1017
1018                 /* vpninfo->urlpath now points to the wait page */
1019                 while (1) {
1020                         result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1021                         if (result <= 0)
1022                                 break;
1023
1024                         result = check_response_type(vpninfo, form_buf);
1025                         if (result <= 0)
1026                                 break;
1027
1028                         vpn_progress(vpninfo, PRG_INFO,
1029                                      _("Refreshing %s after 1 second...\n"),
1030                                      vpninfo->urlpath);
1031                         sleep(1);
1032                 }
1033                 if (result < 0)
1034                         goto out;
1035
1036                 /* refresh the form page, to see if we're authorized now */
1037                 free(vpninfo->urlpath);
1038                 vpninfo->urlpath = form_path;
1039
1040                 result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
1041                                           request_body_type, request_body, &form_buf, 1);
1042                 if (result < 0)
1043                         goto out;
1044
1045                 result = parse_xml_response(vpninfo, form_buf, &form);
1046                 if (result < 0)
1047                         goto out;
1048         }
1049
1050         /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
1051         while (1) {
1052                 request_body[0] = 0;
1053                 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
1054                                           &method, &request_body_type, xmlpost);
1055                 if (result < 0 || result == 1)
1056                         goto out;
1057                 if (result == 2)
1058                         break;
1059
1060                 result = do_https_request(vpninfo, method, request_body_type, request_body,
1061                                           &form_buf, 1);
1062                 if (result < 0)
1063                         goto out;
1064
1065                 result = parse_xml_response(vpninfo, form_buf, &form);
1066                 if (result < 0)
1067                         goto out;
1068         }
1069
1070         /* A return value of 2 means the XML form indicated
1071            success. We _should_ have a cookie... */
1072
1073         for (opt = vpninfo->cookies; opt; opt = opt->next) {
1074
1075                 if (!strcmp(opt->option, "webvpn"))
1076                         vpninfo->cookie = opt->value;
1077                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
1078                         char *tok = opt->value;
1079                         char *bu = NULL, *fu = NULL, *sha = NULL;
1080
1081                         do {
1082                                 if (tok != opt->value)
1083                                         *(tok++) = 0;
1084
1085                                 if (!strncmp(tok, "bu:", 3))
1086                                         bu = tok + 3;
1087                                 else if (!strncmp(tok, "fu:", 3))
1088                                         fu = tok + 3;
1089                                 else if (!strncmp(tok, "fh:", 3)) {
1090                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
1091                                                          SHA1_SIZE * 2))
1092                                                 break;
1093                                         sha = tok + 3;
1094                                 }
1095                         } while ((tok = strchr(tok, '&')));
1096
1097                         if (bu && fu && sha)
1098                                 fetch_config(vpninfo, bu, fu, sha);
1099                 }
1100         }
1101         if (vpninfo->csd_scriptname) {
1102                 unlink(vpninfo->csd_scriptname);
1103                 free(vpninfo->csd_scriptname);
1104                 vpninfo->csd_scriptname = NULL;
1105         }
1106         result = 0;
1107
1108 out:
1109         free(form_buf);
1110         free_auth_form(form);
1111         return result;
1112 }
1113
1114 char *openconnect_create_useragent(const char *base)
1115 {
1116         char *uagent;
1117
1118         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1119                 return NULL;
1120
1121         return uagent;
1122 }
1123
1124 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1125                       char *buf, size_t len)
1126 {
1127         int i = 0;
1128         int ret;
1129
1130         if (len < 2)
1131                 return -EINVAL;
1132
1133         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1134                 if (buf[i] == '\n') {
1135                         buf[i] = 0;
1136                         if (i && buf[i-1] == '\r') {
1137                                 buf[i-1] = 0;
1138                                 i--;
1139                         }
1140                         return i;
1141                 }
1142                 i++;
1143
1144                 if (i >= len - 1) {
1145                         buf[i] = 0;
1146                         return i;
1147                 }
1148         }
1149         buf[i] = 0;
1150         return i ?: ret;
1151 }
1152
1153 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1154                        unsigned char *buf, size_t len)
1155 {
1156         size_t count;
1157
1158         for (count = 0; count < len; ) {
1159                 fd_set rd_set, wr_set;
1160                 int maxfd = fd;
1161                 int i;
1162
1163                 FD_ZERO(&wr_set);
1164                 FD_ZERO(&rd_set);
1165                 FD_SET(fd, &wr_set);
1166                 if (vpninfo->cancel_fd != -1) {
1167                         FD_SET(vpninfo->cancel_fd, &rd_set);
1168                         if (vpninfo->cancel_fd > fd)
1169                                 maxfd = vpninfo->cancel_fd;
1170                 }
1171
1172                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1173                 if (vpninfo->cancel_fd != -1 &&
1174                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1175                         return -EINTR;
1176
1177                 /* Not that this should ever be able to happen... */
1178                 if (!FD_ISSET(fd, &wr_set))
1179                         continue;
1180
1181                 i = write(fd, buf + count, len - count);
1182                 if (i < 0)
1183                         return -errno;
1184
1185                 count += i;
1186         }
1187         return 0;
1188 }
1189
1190 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1191                       unsigned char *buf, size_t len)
1192 {
1193         size_t count;
1194
1195         for (count = 0; count < len; ) {
1196                 fd_set rd_set;
1197                 int maxfd = fd;
1198                 int i;
1199
1200                 FD_ZERO(&rd_set);
1201                 FD_SET(fd, &rd_set);
1202                 if (vpninfo->cancel_fd != -1) {
1203                         FD_SET(vpninfo->cancel_fd, &rd_set);
1204                         if (vpninfo->cancel_fd > fd)
1205                                 maxfd = vpninfo->cancel_fd;
1206                 }
1207
1208                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1209                 if (vpninfo->cancel_fd != -1 &&
1210                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1211                         return -EINTR;
1212
1213                 /* Not that this should ever be able to happen... */
1214                 if (!FD_ISSET(fd, &rd_set))
1215                         continue;
1216
1217                 i = read(fd, buf + count, len - count);
1218                 if (i < 0)
1219                         return -errno;
1220
1221                 count += i;
1222         }
1223         return 0;
1224 }
1225
1226 static const char *socks_errors[] = {
1227         N_("request granted"),
1228         N_("general failure"),
1229         N_("connection not allowed by ruleset"),
1230         N_("network unreachable"),
1231         N_("host unreachable"),
1232         N_("connection refused by destination host"),
1233         N_("TTL expired"),
1234         N_("command not supported / protocol error"),
1235         N_("address type not supported")
1236 };
1237
1238 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1239 {
1240         unsigned char buf[1024];
1241         int i;
1242
1243         buf[0] = 5; /* SOCKS version */
1244         buf[1] = 1; /* # auth methods */
1245         buf[2] = 0; /* No auth supported */
1246
1247         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1248                 vpn_progress(vpninfo, PRG_ERR,
1249                              _("Error writing auth request to SOCKS proxy: %s\n"),
1250                              strerror(-i));
1251                 return i;
1252         }
1253         
1254         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1255                 vpn_progress(vpninfo, PRG_ERR,
1256                              _("Error reading auth response from SOCKS proxy: %s\n"),
1257                              strerror(-i));
1258                 return i;
1259         }
1260         if (buf[0] != 5) {
1261                 vpn_progress(vpninfo, PRG_ERR,
1262                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1263                              buf[0], buf[1]);
1264                 return -EIO;
1265         }
1266         if (buf[1]) {
1267         socks_err:
1268                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1269                         vpn_progress(vpninfo, PRG_ERR,
1270                                      _("SOCKS proxy error %02x: %s\n"),
1271                                      buf[1], _(socks_errors[buf[1]]));
1272                 else
1273                         vpn_progress(vpninfo, PRG_ERR,
1274                                      _("SOCKS proxy error %02x\n"),
1275                                      buf[1]);
1276                 return -EIO;
1277         }
1278
1279         vpn_progress(vpninfo, PRG_INFO,
1280                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1281                      vpninfo->hostname, vpninfo->port);
1282
1283         buf[0] = 5; /* SOCKS version */
1284         buf[1] = 1; /* CONNECT */
1285         buf[2] = 0; /* Reserved */
1286         buf[3] = 3; /* Address type is domain name */
1287         buf[4] = strlen(vpninfo->hostname);
1288         strcpy((char *)buf + 5, vpninfo->hostname);
1289         i = strlen(vpninfo->hostname) + 5;
1290         buf[i++] = vpninfo->port >> 8;
1291         buf[i++] = vpninfo->port & 0xff;
1292
1293         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1294                 vpn_progress(vpninfo, PRG_ERR,
1295                              _("Error writing connect request to SOCKS proxy: %s\n"),
1296                              strerror(-i));
1297                 return i;
1298         }
1299         /* Read 5 bytes -- up to and including the first byte of the returned
1300            address (which might be the length byte of a domain name) */
1301         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1302                 vpn_progress(vpninfo, PRG_ERR,
1303                              _("Error reading connect response from SOCKS proxy: %s\n"),
1304                              strerror(-i));
1305                 return i;
1306         }
1307         if (buf[0] != 5) {
1308                 vpn_progress(vpninfo, PRG_ERR,
1309                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1310                              buf[0], buf[1]);
1311                 return -EIO;
1312         }
1313         if (buf[1])
1314                 goto socks_err;
1315
1316         /* Connect responses contain an address */
1317         switch(buf[3]) {
1318         case 1: /* Legacy IP */
1319                 i = 5;
1320                 break;
1321         case 3: /* Domain name */
1322                 i = buf[4] + 2;
1323                 break;
1324         case 4: /* IPv6 */
1325                 i = 17;
1326                 break;
1327         default:
1328                 vpn_progress(vpninfo, PRG_ERR,
1329                              _("Unexpected address type %02x in SOCKS connect response\n"),
1330                              buf[3]);
1331                 return -EIO;
1332         }
1333
1334         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1335                 vpn_progress(vpninfo, PRG_ERR,
1336                              _("Error reading connect response from SOCKS proxy: %s\n"),
1337                              strerror(-i));
1338                 return i;
1339         }
1340         return 0;
1341 }
1342
1343 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1344 {
1345         char buf[MAX_BUF_LEN];
1346         struct oc_text_buf *reqbuf;
1347         int buflen, result;
1348
1349         reqbuf = buf_alloc();
1350         buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1351         buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1352         buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1353         buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1354         buf_append(reqbuf, "Connection: keep-alive\r\n");
1355         buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1356         buf_append(reqbuf, "\r\n");
1357
1358         if (buf_error(reqbuf))
1359                 return buf_free(reqbuf);
1360
1361         vpn_progress(vpninfo, PRG_INFO,
1362                      _("Requesting HTTP proxy connection to %s:%d\n"),
1363                      vpninfo->hostname, vpninfo->port);
1364
1365         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1366         buf_free(reqbuf);
1367
1368         if (result) {
1369                 vpn_progress(vpninfo, PRG_ERR,
1370                              _("Sending proxy request failed: %s\n"),
1371                              strerror(-result));
1372                 return result;
1373         }
1374
1375         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1376                 vpn_progress(vpninfo, PRG_ERR,
1377                              _("Error fetching proxy response\n"));
1378                 return -EIO;
1379         }
1380
1381         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1382             buf[8] != ' ' || !(result = atoi(buf+9))) {
1383                 vpn_progress(vpninfo, PRG_ERR,
1384                              _("Failed to parse proxy response '%s'\n"), buf);
1385                 return -EINVAL;
1386         }
1387
1388         if (result != 200) {
1389                 vpn_progress(vpninfo, PRG_ERR,
1390                              _("Proxy CONNECT request failed: %s\n"), buf);
1391                 return -EIO;
1392         }
1393
1394         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1395                 if (buflen < 0) {
1396                         vpn_progress(vpninfo, PRG_ERR,
1397                                      _("Failed to read proxy response\n"));
1398                         return -EIO;
1399                 }
1400                 vpn_progress(vpninfo, PRG_ERR,
1401                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1402                              buf);
1403         }
1404
1405         return 0;
1406 }
1407
1408 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1409 {
1410         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1411                 return process_http_proxy(vpninfo, ssl_sock);
1412         
1413         if (!strcmp(vpninfo->proxy_type, "socks") ||
1414             !strcmp(vpninfo->proxy_type, "socks5"))
1415                 return process_socks_proxy(vpninfo, ssl_sock);
1416
1417         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1418                      vpninfo->proxy_type);
1419         return -EIO;
1420 }
1421
1422 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1423 {
1424         char *url = proxy;
1425         int ret;
1426
1427         if (!url)
1428                 return -ENOMEM;
1429
1430         free(vpninfo->proxy_type);
1431         vpninfo->proxy_type = NULL;
1432         free(vpninfo->proxy);
1433         vpninfo->proxy = NULL;
1434
1435         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1436                                  &vpninfo->proxy_port, NULL, 80);
1437         if (ret)
1438                 goto out;
1439
1440         if (vpninfo->proxy_type &&
1441             strcmp(vpninfo->proxy_type, "http") &&
1442             strcmp(vpninfo->proxy_type, "socks") &&
1443             strcmp(vpninfo->proxy_type, "socks5")) {
1444                 vpn_progress(vpninfo, PRG_ERR,
1445                              _("Only http or socks(5) proxies supported\n"));
1446                 free(vpninfo->proxy_type);
1447                 vpninfo->proxy_type = NULL;
1448                 free(vpninfo->proxy);
1449                 vpninfo->proxy = NULL;
1450                 return -EINVAL;
1451         }
1452  out:
1453         free(url);
1454         return ret;
1455 }