Fix a couple of valgrind warnings
[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         free(vpninfo->urlpath);
622         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
623                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
624         free(vpninfo->csd_waiturl);
625         vpninfo->csd_waiturl = NULL;
626         vpninfo->csd_scriptname = strdup(fname);
627
628         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
629
630         return 0;
631 }
632
633 int internal_parse_url(char *url, char **res_proto, char **res_host,
634                        int *res_port, char **res_path, int default_port)
635 {
636         char *proto = url;
637         char *host, *path, *port_str;
638         int port;
639
640         host = strstr(url, "://");
641         if (host) {
642                 *host = 0;
643                 host += 3;
644
645                 if (!strcasecmp(proto, "https"))
646                         port = 443;
647                 else if (!strcasecmp(proto, "http"))
648                         port = 80;
649                 else if (!strcasecmp(proto, "socks") ||
650                          !strcasecmp(proto, "socks4") ||
651                          !strcasecmp(proto, "socks5"))
652                         port = 1080;
653                 else
654                         return -EPROTONOSUPPORT;
655         } else {
656                 if (default_port) {
657                         proto = NULL;
658                         port = default_port;
659                         host = url;
660                 } else
661                         return -EINVAL;
662         }
663
664         path = strchr(host, '/');
665         if (path)
666                 *(path++) = 0;
667
668         port_str = strrchr(host, ':');
669         if (port_str) {
670                 char *end;
671                 int new_port = strtol(port_str + 1, &end, 10);
672
673                 if (!*end) {
674                         *port_str = 0;
675                         port = new_port;
676                 }
677         }
678
679         if (res_proto)
680                 *res_proto = proto ? strdup(proto) : NULL;
681         if (res_host)
682                 *res_host = strdup(host);
683         if (res_port)
684                 *res_port = port;
685         if (res_path)
686                 *res_path = (path && *path) ? strdup(path) : NULL;
687
688         /* Undo the damage we did to the original string */
689         if (port_str)
690                 *(port_str) = ':';
691         if (path)
692                 *(path - 1) = '/';
693         if (proto)
694                 *(host - 3) = ':';
695         return 0;
696 }
697
698 static void clear_cookies(struct openconnect_info *vpninfo)
699 {
700         struct vpn_option *opt, *next;
701
702         for (opt = vpninfo->cookies; opt; opt = next) {
703                 next = opt->next;
704
705                 free(opt->option);
706                 free(opt->value);
707                 free(opt);
708         }
709         vpninfo->cookies = NULL;
710 }
711
712 /* Return value:
713  *  < 0, on error
714  *  = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
715  */
716 static int handle_redirect(struct openconnect_info *vpninfo)
717 {
718         vpninfo->redirect_type = REDIR_TYPE_LOCAL;
719
720         if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
721                 /* New host. Tear down the existing connection and make a new one */
722                 char *host;
723                 int port;
724                 int ret;
725
726                 free(vpninfo->urlpath);
727                 vpninfo->urlpath = NULL;
728
729                 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
730                 if (ret) {
731                         vpn_progress(vpninfo, PRG_ERR,
732                                      _("Failed to parse redirected URL '%s': %s\n"),
733                                      vpninfo->redirect_url, strerror(-ret));
734                         free(vpninfo->redirect_url);
735                         vpninfo->redirect_url = NULL;
736                         return ret;
737                 }
738
739                 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
740                         free(vpninfo->hostname);
741                         vpninfo->hostname = host;
742                         vpninfo->port = port;
743
744                         /* Kill the existing connection, and a new one will happen */
745                         free(vpninfo->peer_addr);
746                         vpninfo->peer_addr = NULL;
747                         openconnect_close_https(vpninfo, 0);
748                         clear_cookies(vpninfo);
749                         vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
750                 } else
751                         free(host);
752
753                 free(vpninfo->redirect_url);
754                 vpninfo->redirect_url = NULL;
755
756                 return 0;
757         } else if (strstr(vpninfo->redirect_url, "://")) {
758                 vpn_progress(vpninfo, PRG_ERR,
759                              _("Cannot follow redirection to non-https URL '%s'\n"),
760                              vpninfo->redirect_url);
761                 free(vpninfo->redirect_url);
762                 vpninfo->redirect_url = NULL;
763                 return -EINVAL;
764         } else if (vpninfo->redirect_url[0] == '/') {
765                 /* Absolute redirect within same host */
766                 free(vpninfo->urlpath);
767                 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
768                 free(vpninfo->redirect_url);
769                 vpninfo->redirect_url = NULL;
770                 return 0;
771         } else {
772                 char *lastslash = NULL;
773                 if (vpninfo->urlpath)
774                         lastslash = strrchr(vpninfo->urlpath, '/');
775                 if (!lastslash) {
776                         free(vpninfo->urlpath);
777                         vpninfo->urlpath = vpninfo->redirect_url;
778                         vpninfo->redirect_url = NULL;
779                 } else {
780                         char *oldurl = vpninfo->urlpath;
781                         *lastslash = 0;
782                         vpninfo->urlpath = NULL;
783                         if (asprintf(&vpninfo->urlpath, "%s/%s",
784                                      oldurl, vpninfo->redirect_url) == -1) {
785                                 int err = -errno;
786                                 vpn_progress(vpninfo, PRG_ERR,
787                                              _("Allocating new path for relative redirect failed: %s\n"),
788                                              strerror(-err));
789                                 return err;
790                         }
791                         free(oldurl);
792                         free(vpninfo->redirect_url);
793                         vpninfo->redirect_url = NULL;
794                 }
795                 return 0;
796         }
797 }
798
799 /* Inputs:
800  *  method:             GET or POST
801  *  vpninfo->hostname:  Host DNS name
802  *  vpninfo->port:      TCP port, typically 443
803  *  vpninfo->urlpath:   Relative path, e.g. /+webvpn+/foo.html
804  *  request_body_type:  Content type for a POST (e.g. text/html).  Can be NULL.
805  *  request_body:       POST content
806  *  form_buf:           Callee-allocated buffer for server content
807  *
808  * Return value:
809  *  < 0, on error
810  *  >=0, on success, indicating the length of the data in *form_buf
811  */
812 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
813                             const char *request_body_type, const char *request_body,
814                             char **form_buf, int fetch_redirect)
815 {
816         struct oc_text_buf *buf;
817         int result, buflen;
818
819  retry:
820         vpninfo->redirect_type = REDIR_TYPE_NONE;
821
822         if (*form_buf) {
823                 free(*form_buf);
824                 *form_buf = NULL;
825         }
826         if (openconnect_open_https(vpninfo)) {
827                 vpn_progress(vpninfo, PRG_ERR,
828                              _("Failed to open HTTPS connection to %s\n"),
829                              vpninfo->hostname);
830                 return -EINVAL;
831         }
832
833         /*
834          * It would be nice to use cURL for this, but we really need to guarantee
835          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
836          * to have any way to let us provide our own socket read/write functions.
837          * We can only provide a socket _open_ function. Which would require having
838          * a socketpair() and servicing the "other" end of it.
839          *
840          * So we process the HTTP for ourselves...
841          */
842         buf = buf_alloc();
843         buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
844         add_common_headers(vpninfo, buf);
845
846         if (request_body_type) {
847                 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
848                 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
849         }
850         buf_append(buf, "\r\n");
851
852         if (request_body_type)
853                 buf_append(buf, "%s", request_body);
854
855         if (vpninfo->port == 443)
856                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
857                              method, vpninfo->hostname,
858                              vpninfo->urlpath ?: "");
859         else
860                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
861                              method, vpninfo->hostname, vpninfo->port,
862                              vpninfo->urlpath ?: "");
863
864         if (buf_error(buf))
865                 return buf_free(buf);
866
867         result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
868         buf_free(buf);
869         if (result < 0)
870                 return result;
871
872         buflen = process_http_response(vpninfo, &result, NULL, form_buf);
873         if (buflen < 0) {
874                 /* We'll already have complained about whatever offended us */
875                 return buflen;
876         }
877
878         if (result != 200 && vpninfo->redirect_url) {
879                 result = handle_redirect(vpninfo);
880                 if (result == 0) {
881                         if (!fetch_redirect)
882                                 return 0;
883                         goto retry;
884                 }
885                 goto out;
886         }
887         if (!*form_buf || result != 200) {
888                 vpn_progress(vpninfo, PRG_ERR,
889                              _("Unexpected %d result from server\n"),
890                              result);
891                 result = -EINVAL;
892                 goto out;
893         }
894
895         return buflen;
896
897  out:
898         free(*form_buf);
899         *form_buf = NULL;
900         return result;
901 }
902
903 /* Return value:
904  *  < 0, if the data is unrecognized
905  *  = 0, if the page contains an XML document
906  *  = 1, if the page is a wait/refresh HTML page
907  */
908 static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
909 {
910         if (strncmp(form_buf, "<?xml", 5)) {
911                 /* Not XML? Perhaps it's HTML with a refresh... */
912                 if (strcasestr(form_buf, "http-equiv=\"refresh\""))
913                         return 1;
914                 vpn_progress(vpninfo, PRG_ERR,
915                              _("Unknown response from server\n"));
916                 return -EINVAL;
917         }
918         return 0;
919 }
920
921 /* Return value:
922  *  < 0, on error
923  *  > 0, no cookie (user cancel)
924  *  = 0, obtained cookie
925  */
926 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
927 {
928         struct vpn_option *opt;
929         char *form_buf = NULL;
930         struct oc_auth_form *form = NULL;
931         int result, buflen, tries;
932         char request_body[2048];
933         const char *request_body_type = "application/x-www-form-urlencoded";
934         const char *method = "POST";
935         int xmlpost = 0;
936
937         /* Step 1: Unlock software token (if applicable) */
938         if (vpninfo->use_stoken) {
939                 result = prepare_stoken(vpninfo);
940                 if (result)
941                         return result;
942         }
943
944         /*
945          * Step 2: Probe for XML POST compatibility
946          *
947          * This can get stuck in a redirect loop, so give up after any of:
948          *
949          * a) HTTP error (e.g. 400 Bad Request)
950          * b) Same-host redirect (e.g. Location: /foo/bar)
951          * c) Three redirects without seeing a plausible login form
952          */
953         result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
954         if (result < 0)
955                 return result;
956
957         for (tries = 0; ; tries++) {
958                 if (tries == 3)
959                         break;
960                 buflen = do_https_request(vpninfo, method, request_body_type, request_body,
961                                           &form_buf, 0);
962                 if (buflen == -EINVAL)
963                         break;
964                 if (buflen < 0)
965                         return buflen;
966
967                 if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
968                         break;
969                 else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
970                         continue;
971
972                 result = parse_xml_response(vpninfo, form_buf, &form);
973                 if (result < 0)
974                         break;
975
976                 xmlpost = 1;
977                 vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
978                 break;
979         }
980
981         /* Step 3: Fetch and parse the login form, if not using XML POST */
982         if (!xmlpost) {
983                 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
984                 if (buflen < 0)
985                         return buflen;
986
987                 result = parse_xml_response(vpninfo, form_buf, &form);
988                 if (result < 0) {
989                         free(form_buf);
990                         return result;
991                 }
992         }
993
994         /* Step 4: Run the CSD trojan, if applicable */
995         if (vpninfo->csd_starturl) {
996                 char *form_path = NULL;
997
998                 if (vpninfo->urlpath) {
999                         form_path = strdup(vpninfo->urlpath);
1000                         if (!form_path) {
1001                                 result = -ENOMEM;
1002                                 goto out;
1003                         }
1004                 }
1005
1006                 /* fetch the CSD program, if available */
1007                 if (vpninfo->csd_stuburl) {
1008                         buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1009                         if (buflen <= 0) {
1010                                 result = -EINVAL;
1011                                 goto out;
1012                         }
1013                 }
1014
1015                 /* This is the CSD stub script, which we now need to run */
1016                 result = run_csd_script(vpninfo, form_buf, buflen);
1017                 if (result)
1018                         goto out;
1019
1020                 /* vpninfo->urlpath now points to the wait page */
1021                 while (1) {
1022                         result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1023                         if (result <= 0)
1024                                 break;
1025
1026                         result = check_response_type(vpninfo, form_buf);
1027                         if (result <= 0)
1028                                 break;
1029
1030                         vpn_progress(vpninfo, PRG_INFO,
1031                                      _("Refreshing %s after 1 second...\n"),
1032                                      vpninfo->urlpath);
1033                         sleep(1);
1034                 }
1035                 if (result < 0)
1036                         goto out;
1037
1038                 /* refresh the form page, to see if we're authorized now */
1039                 free(vpninfo->urlpath);
1040                 vpninfo->urlpath = form_path;
1041
1042                 result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
1043                                           request_body_type, request_body, &form_buf, 1);
1044                 if (result < 0)
1045                         goto out;
1046
1047                 result = parse_xml_response(vpninfo, form_buf, &form);
1048                 if (result < 0)
1049                         goto out;
1050         }
1051
1052         /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
1053         while (1) {
1054                 request_body[0] = 0;
1055                 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
1056                                           &method, &request_body_type, xmlpost);
1057                 if (result < 0 || result == 1)
1058                         goto out;
1059                 if (result == 2)
1060                         break;
1061
1062                 result = do_https_request(vpninfo, method, request_body_type, request_body,
1063                                           &form_buf, 1);
1064                 if (result < 0)
1065                         goto out;
1066
1067                 result = parse_xml_response(vpninfo, form_buf, &form);
1068                 if (result < 0)
1069                         goto out;
1070         }
1071
1072         /* A return value of 2 means the XML form indicated
1073            success. We _should_ have a cookie... */
1074
1075         for (opt = vpninfo->cookies; opt; opt = opt->next) {
1076
1077                 if (!strcmp(opt->option, "webvpn"))
1078                         vpninfo->cookie = opt->value;
1079                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
1080                         char *tok = opt->value;
1081                         char *bu = NULL, *fu = NULL, *sha = NULL;
1082
1083                         do {
1084                                 if (tok != opt->value)
1085                                         *(tok++) = 0;
1086
1087                                 if (!strncmp(tok, "bu:", 3))
1088                                         bu = tok + 3;
1089                                 else if (!strncmp(tok, "fu:", 3))
1090                                         fu = tok + 3;
1091                                 else if (!strncmp(tok, "fh:", 3)) {
1092                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
1093                                                          SHA1_SIZE * 2))
1094                                                 break;
1095                                         sha = tok + 3;
1096                                 }
1097                         } while ((tok = strchr(tok, '&')));
1098
1099                         if (bu && fu && sha)
1100                                 fetch_config(vpninfo, bu, fu, sha);
1101                 }
1102         }
1103         result = 0;
1104
1105 out:
1106         free(form_buf);
1107         free_auth_form(form);
1108
1109         if (vpninfo->csd_scriptname) {
1110                 unlink(vpninfo->csd_scriptname);
1111                 free(vpninfo->csd_scriptname);
1112                 vpninfo->csd_scriptname = NULL;
1113         }
1114
1115         return result;
1116 }
1117
1118 char *openconnect_create_useragent(const char *base)
1119 {
1120         char *uagent;
1121
1122         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1123                 return NULL;
1124
1125         return uagent;
1126 }
1127
1128 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1129                       char *buf, size_t len)
1130 {
1131         int i = 0;
1132         int ret;
1133
1134         if (len < 2)
1135                 return -EINVAL;
1136
1137         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1138                 if (buf[i] == '\n') {
1139                         buf[i] = 0;
1140                         if (i && buf[i-1] == '\r') {
1141                                 buf[i-1] = 0;
1142                                 i--;
1143                         }
1144                         return i;
1145                 }
1146                 i++;
1147
1148                 if (i >= len - 1) {
1149                         buf[i] = 0;
1150                         return i;
1151                 }
1152         }
1153         buf[i] = 0;
1154         return i ?: ret;
1155 }
1156
1157 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1158                        unsigned char *buf, size_t len)
1159 {
1160         size_t count;
1161
1162         for (count = 0; count < len; ) {
1163                 fd_set rd_set, wr_set;
1164                 int maxfd = fd;
1165                 int i;
1166
1167                 FD_ZERO(&wr_set);
1168                 FD_ZERO(&rd_set);
1169                 FD_SET(fd, &wr_set);
1170                 if (vpninfo->cancel_fd != -1) {
1171                         FD_SET(vpninfo->cancel_fd, &rd_set);
1172                         if (vpninfo->cancel_fd > fd)
1173                                 maxfd = vpninfo->cancel_fd;
1174                 }
1175
1176                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1177                 if (vpninfo->cancel_fd != -1 &&
1178                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1179                         return -EINTR;
1180
1181                 /* Not that this should ever be able to happen... */
1182                 if (!FD_ISSET(fd, &wr_set))
1183                         continue;
1184
1185                 i = write(fd, buf + count, len - count);
1186                 if (i < 0)
1187                         return -errno;
1188
1189                 count += i;
1190         }
1191         return 0;
1192 }
1193
1194 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1195                       unsigned char *buf, size_t len)
1196 {
1197         size_t count;
1198
1199         for (count = 0; count < len; ) {
1200                 fd_set rd_set;
1201                 int maxfd = fd;
1202                 int i;
1203
1204                 FD_ZERO(&rd_set);
1205                 FD_SET(fd, &rd_set);
1206                 if (vpninfo->cancel_fd != -1) {
1207                         FD_SET(vpninfo->cancel_fd, &rd_set);
1208                         if (vpninfo->cancel_fd > fd)
1209                                 maxfd = vpninfo->cancel_fd;
1210                 }
1211
1212                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1213                 if (vpninfo->cancel_fd != -1 &&
1214                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1215                         return -EINTR;
1216
1217                 /* Not that this should ever be able to happen... */
1218                 if (!FD_ISSET(fd, &rd_set))
1219                         continue;
1220
1221                 i = read(fd, buf + count, len - count);
1222                 if (i < 0)
1223                         return -errno;
1224
1225                 count += i;
1226         }
1227         return 0;
1228 }
1229
1230 static const char *socks_errors[] = {
1231         N_("request granted"),
1232         N_("general failure"),
1233         N_("connection not allowed by ruleset"),
1234         N_("network unreachable"),
1235         N_("host unreachable"),
1236         N_("connection refused by destination host"),
1237         N_("TTL expired"),
1238         N_("command not supported / protocol error"),
1239         N_("address type not supported")
1240 };
1241
1242 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1243 {
1244         unsigned char buf[1024];
1245         int i;
1246
1247         buf[0] = 5; /* SOCKS version */
1248         buf[1] = 1; /* # auth methods */
1249         buf[2] = 0; /* No auth supported */
1250
1251         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1252                 vpn_progress(vpninfo, PRG_ERR,
1253                              _("Error writing auth request to SOCKS proxy: %s\n"),
1254                              strerror(-i));
1255                 return i;
1256         }
1257         
1258         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1259                 vpn_progress(vpninfo, PRG_ERR,
1260                              _("Error reading auth response from SOCKS proxy: %s\n"),
1261                              strerror(-i));
1262                 return i;
1263         }
1264         if (buf[0] != 5) {
1265                 vpn_progress(vpninfo, PRG_ERR,
1266                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1267                              buf[0], buf[1]);
1268                 return -EIO;
1269         }
1270         if (buf[1]) {
1271         socks_err:
1272                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1273                         vpn_progress(vpninfo, PRG_ERR,
1274                                      _("SOCKS proxy error %02x: %s\n"),
1275                                      buf[1], _(socks_errors[buf[1]]));
1276                 else
1277                         vpn_progress(vpninfo, PRG_ERR,
1278                                      _("SOCKS proxy error %02x\n"),
1279                                      buf[1]);
1280                 return -EIO;
1281         }
1282
1283         vpn_progress(vpninfo, PRG_INFO,
1284                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1285                      vpninfo->hostname, vpninfo->port);
1286
1287         buf[0] = 5; /* SOCKS version */
1288         buf[1] = 1; /* CONNECT */
1289         buf[2] = 0; /* Reserved */
1290         buf[3] = 3; /* Address type is domain name */
1291         buf[4] = strlen(vpninfo->hostname);
1292         strcpy((char *)buf + 5, vpninfo->hostname);
1293         i = strlen(vpninfo->hostname) + 5;
1294         buf[i++] = vpninfo->port >> 8;
1295         buf[i++] = vpninfo->port & 0xff;
1296
1297         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1298                 vpn_progress(vpninfo, PRG_ERR,
1299                              _("Error writing connect request to SOCKS proxy: %s\n"),
1300                              strerror(-i));
1301                 return i;
1302         }
1303         /* Read 5 bytes -- up to and including the first byte of the returned
1304            address (which might be the length byte of a domain name) */
1305         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1306                 vpn_progress(vpninfo, PRG_ERR,
1307                              _("Error reading connect response from SOCKS proxy: %s\n"),
1308                              strerror(-i));
1309                 return i;
1310         }
1311         if (buf[0] != 5) {
1312                 vpn_progress(vpninfo, PRG_ERR,
1313                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1314                              buf[0], buf[1]);
1315                 return -EIO;
1316         }
1317         if (buf[1])
1318                 goto socks_err;
1319
1320         /* Connect responses contain an address */
1321         switch(buf[3]) {
1322         case 1: /* Legacy IP */
1323                 i = 5;
1324                 break;
1325         case 3: /* Domain name */
1326                 i = buf[4] + 2;
1327                 break;
1328         case 4: /* IPv6 */
1329                 i = 17;
1330                 break;
1331         default:
1332                 vpn_progress(vpninfo, PRG_ERR,
1333                              _("Unexpected address type %02x in SOCKS connect response\n"),
1334                              buf[3]);
1335                 return -EIO;
1336         }
1337
1338         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1339                 vpn_progress(vpninfo, PRG_ERR,
1340                              _("Error reading connect response from SOCKS proxy: %s\n"),
1341                              strerror(-i));
1342                 return i;
1343         }
1344         return 0;
1345 }
1346
1347 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1348 {
1349         char buf[MAX_BUF_LEN];
1350         struct oc_text_buf *reqbuf;
1351         int buflen, result;
1352
1353         reqbuf = buf_alloc();
1354         buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1355         buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1356         buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1357         buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1358         buf_append(reqbuf, "Connection: keep-alive\r\n");
1359         buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1360         buf_append(reqbuf, "\r\n");
1361
1362         if (buf_error(reqbuf))
1363                 return buf_free(reqbuf);
1364
1365         vpn_progress(vpninfo, PRG_INFO,
1366                      _("Requesting HTTP proxy connection to %s:%d\n"),
1367                      vpninfo->hostname, vpninfo->port);
1368
1369         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1370         buf_free(reqbuf);
1371
1372         if (result) {
1373                 vpn_progress(vpninfo, PRG_ERR,
1374                              _("Sending proxy request failed: %s\n"),
1375                              strerror(-result));
1376                 return result;
1377         }
1378
1379         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1380                 vpn_progress(vpninfo, PRG_ERR,
1381                              _("Error fetching proxy response\n"));
1382                 return -EIO;
1383         }
1384
1385         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1386             buf[8] != ' ' || !(result = atoi(buf+9))) {
1387                 vpn_progress(vpninfo, PRG_ERR,
1388                              _("Failed to parse proxy response '%s'\n"), buf);
1389                 return -EINVAL;
1390         }
1391
1392         if (result != 200) {
1393                 vpn_progress(vpninfo, PRG_ERR,
1394                              _("Proxy CONNECT request failed: %s\n"), buf);
1395                 return -EIO;
1396         }
1397
1398         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1399                 if (buflen < 0) {
1400                         vpn_progress(vpninfo, PRG_ERR,
1401                                      _("Failed to read proxy response\n"));
1402                         return -EIO;
1403                 }
1404                 vpn_progress(vpninfo, PRG_ERR,
1405                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1406                              buf);
1407         }
1408
1409         return 0;
1410 }
1411
1412 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1413 {
1414         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1415                 return process_http_proxy(vpninfo, ssl_sock);
1416         
1417         if (!strcmp(vpninfo->proxy_type, "socks") ||
1418             !strcmp(vpninfo->proxy_type, "socks5"))
1419                 return process_socks_proxy(vpninfo, ssl_sock);
1420
1421         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1422                      vpninfo->proxy_type);
1423         return -EIO;
1424 }
1425
1426 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1427 {
1428         char *url = proxy;
1429         int ret;
1430
1431         if (!url)
1432                 return -ENOMEM;
1433
1434         free(vpninfo->proxy_type);
1435         vpninfo->proxy_type = NULL;
1436         free(vpninfo->proxy);
1437         vpninfo->proxy = NULL;
1438
1439         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1440                                  &vpninfo->proxy_port, NULL, 80);
1441         if (ret)
1442                 goto out;
1443
1444         if (vpninfo->proxy_type &&
1445             strcmp(vpninfo->proxy_type, "http") &&
1446             strcmp(vpninfo->proxy_type, "socks") &&
1447             strcmp(vpninfo->proxy_type, "socks5")) {
1448                 vpn_progress(vpninfo, PRG_ERR,
1449                              _("Only http or socks(5) proxies supported\n"));
1450                 free(vpninfo->proxy_type);
1451                 vpninfo->proxy_type = NULL;
1452                 free(vpninfo->proxy);
1453                 vpninfo->proxy = NULL;
1454                 return -EINVAL;
1455         }
1456  out:
1457         free(url);
1458         return ret;
1459 }