Reinstate compatibility with test server hack
[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                         openconnect_close_https(vpninfo, 0);
392                         return -EINVAL;
393                 }
394
395                 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
396                 while (1) {
397                         body = realloc(body, done + 16384);
398                         if (!body)
399                                 return -ENOMEM;
400                         i = openconnect_SSL_read(vpninfo, body + done, 16384);
401                         if (i > 0) {
402                                 /* Got more data */
403                                 done += i;
404                         } else if (i < 0) {
405                                 /* Error */
406                                 free(body);
407                                 return i;
408                         } else {
409                                 /* Connection closed. Reduce allocation to just what we need */
410                                 body = realloc(body, done + 1);
411                                 if (!body)
412                                         return -ENOMEM;
413                                 break;
414                         }
415                 }
416         }
417
418         if (closeconn || vpninfo->no_http_keepalive)
419                 openconnect_close_https(vpninfo, 0);
420
421         if (body)
422                 body[done] = 0;
423         *body_ret = body;
424         return done;
425 }
426
427 static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
428 {
429         struct vpn_option *opt;
430
431         buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
432         buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
433         buf_append(buf, "Accept: */*\r\n");
434         buf_append(buf, "Accept-Encoding: identity\r\n");
435
436         if (vpninfo->cookies) {
437                 buf_append(buf, "Cookie: ");
438                 for (opt = vpninfo->cookies; opt; opt = opt->next)
439                         buf_append(buf, "%s=%s%s", opt->option,
440                                       opt->value, opt->next ? "; " : "\r\n");
441         }
442         buf_append(buf, "X-Transcend-Version: 1\r\n");
443         buf_append(buf, "X-Aggregate-Auth: 1\r\n");
444         buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
445 }
446
447 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
448                         char *server_sha1)
449 {
450         struct oc_text_buf *buf;
451         char *config_buf = NULL;
452         int result, buflen;
453         unsigned char local_sha1_bin[SHA1_SIZE];
454         char local_sha1_ascii[(SHA1_SIZE * 2)+1];
455         int i;
456
457         if (openconnect_open_https(vpninfo)) {
458                 vpn_progress(vpninfo, PRG_ERR,
459                              _("Failed to open HTTPS connection to %s\n"),
460                              vpninfo->hostname);
461                 return -EINVAL;
462         }
463
464         buf = buf_alloc();
465         buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
466         add_common_headers(vpninfo, buf);
467         buf_append(buf, "\r\n");
468
469         if (buf_error(buf))
470                 return buf_free(buf);
471
472         if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
473                 vpn_progress(vpninfo, PRG_ERR,
474                              _("Failed to send GET request for new config\n"));
475                 buf_free(buf);
476                 return -EIO;
477         }
478         buf_free(buf);
479
480         buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
481         if (buflen < 0) {
482                 /* We'll already have complained about whatever offended us */
483                 return -EINVAL;
484         }
485
486         if (result != 200) {
487                 free(config_buf);
488                 return -EINVAL;
489         }
490
491         openconnect_sha1(local_sha1_bin, config_buf, buflen);
492
493         for (i = 0; i < SHA1_SIZE; i++)
494                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
495
496         if (strcasecmp(server_sha1, local_sha1_ascii)) {
497                 vpn_progress(vpninfo, PRG_ERR,
498                              _("Downloaded config file did not match intended SHA1\n"));
499                 free(config_buf);
500                 return -EINVAL;
501         }
502
503         result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
504         free(config_buf);
505         return result;
506 }
507
508 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
509 {
510         char fname[16];
511         int fd, ret;
512
513         if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
514                 vpn_progress(vpninfo, PRG_ERR,
515                              _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
516                                "This facility is disabled by default for security reasons, so you may wish to enable it."));
517                 return -EPERM;
518         }
519
520 #ifndef __linux__
521         vpn_progress(vpninfo, PRG_INFO,
522                      _("Trying to run Linux CSD trojan script."));
523 #endif
524
525         sprintf(fname, "/tmp/csdXXXXXX");
526         fd = mkstemp(fname);
527         if (fd < 0) {
528                 int err = -errno;
529                 vpn_progress(vpninfo, PRG_ERR,
530                              _("Failed to open temporary CSD script file: %s\n"),
531                              strerror(errno));
532                 return err;
533         }
534
535         ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
536         if (ret) {
537                 vpn_progress(vpninfo, PRG_ERR,
538                              _("Failed to write temporary CSD script file: %s\n"),
539                              strerror(-ret));
540                 return ret;
541         }
542         fchmod(fd, 0755);
543         close(fd);
544
545         if (!fork()) {
546                 char scertbuf[MD5_SIZE * 2 + 1];
547                 char ccertbuf[MD5_SIZE * 2 + 1];
548                 char *csd_argv[32];
549                 int i = 0;
550
551                 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
552                         struct passwd *pw;
553
554                         if (setuid(vpninfo->uid_csd)) {
555                                 fprintf(stderr, _("Failed to set uid %ld\n"),
556                                         (long)vpninfo->uid_csd);
557                                 exit(1);
558                         }
559                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
560                                 fprintf(stderr, _("Invalid user uid=%ld\n"),
561                                         (long)vpninfo->uid_csd);
562                                 exit(1);
563                         }
564                         setenv("HOME", pw->pw_dir, 1);
565                         if (chdir(pw->pw_dir)) {
566                                 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
567                                         pw->pw_dir, strerror(errno));
568                                 exit(1);
569                         }
570                 }
571                 if (getuid() == 0 && !vpninfo->csd_wrapper) {
572                         fprintf(stderr, _("Warning: you are running insecure "
573                                           "CSD code with root privileges\n"
574                                           "\t Use command line option \"--csd-user\"\n"));
575                 }
576                 if (vpninfo->uid_csd_given == 2) {
577                         /* The NM tool really needs not to get spurious output
578                            on stdout, which the CSD trojan spews. */
579                         dup2(2, 1);
580                 }
581                 if (vpninfo->csd_wrapper)
582                         csd_argv[i++] = vpninfo->csd_wrapper;
583                 csd_argv[i++] = fname;
584                 csd_argv[i++]= (char *)"-ticket";
585                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
586                         goto out;
587                 csd_argv[i++]= (char *)"-stub";
588                 csd_argv[i++]= (char *)"\"0\"";
589                 csd_argv[i++]= (char *)"-group";
590                 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
591                         goto out;
592
593                 openconnect_local_cert_md5(vpninfo, ccertbuf);
594                 scertbuf[0] = 0;
595                 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
596                 csd_argv[i++]= (char *)"-certhash";
597                 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
598                         goto out;
599
600                 csd_argv[i++]= (char *)"-url";
601                 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
602                         goto out;
603
604                 csd_argv[i++]= (char *)"-langselen";
605                 csd_argv[i++] = NULL;
606
607                 if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
608                         goto out;
609                 if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
610                         goto out;
611
612                 execv(csd_argv[0], csd_argv);
613
614 out:
615                 vpn_progress(vpninfo, PRG_ERR,
616                              _("Failed to exec CSD script %s\n"), csd_argv[0]);
617                 exit(1);
618         }
619
620         free(vpninfo->csd_stuburl);
621         vpninfo->csd_stuburl = NULL;
622         free(vpninfo->urlpath);
623         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
624                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
625         free(vpninfo->csd_waiturl);
626         vpninfo->csd_waiturl = NULL;
627         vpninfo->csd_scriptname = strdup(fname);
628
629         http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
630
631         return 0;
632 }
633
634 int internal_parse_url(char *url, char **res_proto, char **res_host,
635                        int *res_port, char **res_path, int default_port)
636 {
637         char *proto = url;
638         char *host, *path, *port_str;
639         int port;
640
641         host = strstr(url, "://");
642         if (host) {
643                 *host = 0;
644                 host += 3;
645
646                 if (!strcasecmp(proto, "https"))
647                         port = 443;
648                 else if (!strcasecmp(proto, "http"))
649                         port = 80;
650                 else if (!strcasecmp(proto, "socks") ||
651                          !strcasecmp(proto, "socks4") ||
652                          !strcasecmp(proto, "socks5"))
653                         port = 1080;
654                 else
655                         return -EPROTONOSUPPORT;
656         } else {
657                 if (default_port) {
658                         proto = NULL;
659                         port = default_port;
660                         host = url;
661                 } else
662                         return -EINVAL;
663         }
664
665         path = strchr(host, '/');
666         if (path)
667                 *(path++) = 0;
668
669         port_str = strrchr(host, ':');
670         if (port_str) {
671                 char *end;
672                 int new_port = strtol(port_str + 1, &end, 10);
673
674                 if (!*end) {
675                         *port_str = 0;
676                         port = new_port;
677                 }
678         }
679
680         if (res_proto)
681                 *res_proto = proto ? strdup(proto) : NULL;
682         if (res_host)
683                 *res_host = strdup(host);
684         if (res_port)
685                 *res_port = port;
686         if (res_path)
687                 *res_path = (path && *path) ? strdup(path) : NULL;
688
689         /* Undo the damage we did to the original string */
690         if (port_str)
691                 *(port_str) = ':';
692         if (path)
693                 *(path - 1) = '/';
694         if (proto)
695                 *(host - 3) = ':';
696         return 0;
697 }
698
699 static void clear_cookies(struct openconnect_info *vpninfo)
700 {
701         struct vpn_option *opt, *next;
702
703         for (opt = vpninfo->cookies; opt; opt = next) {
704                 next = opt->next;
705
706                 free(opt->option);
707                 free(opt->value);
708                 free(opt);
709         }
710         vpninfo->cookies = NULL;
711 }
712
713 /* Return value:
714  *  < 0, on error
715  *  = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
716  */
717 static int handle_redirect(struct openconnect_info *vpninfo)
718 {
719         vpninfo->redirect_type = REDIR_TYPE_LOCAL;
720
721         if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
722                 /* New host. Tear down the existing connection and make a new one */
723                 char *host;
724                 int port;
725                 int ret;
726
727                 free(vpninfo->urlpath);
728                 vpninfo->urlpath = NULL;
729
730                 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
731                 if (ret) {
732                         vpn_progress(vpninfo, PRG_ERR,
733                                      _("Failed to parse redirected URL '%s': %s\n"),
734                                      vpninfo->redirect_url, strerror(-ret));
735                         free(vpninfo->redirect_url);
736                         vpninfo->redirect_url = NULL;
737                         return ret;
738                 }
739
740                 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
741                         free(vpninfo->hostname);
742                         vpninfo->hostname = host;
743                         vpninfo->port = port;
744
745                         /* Kill the existing connection, and a new one will happen */
746                         free(vpninfo->peer_addr);
747                         vpninfo->peer_addr = NULL;
748                         openconnect_close_https(vpninfo, 0);
749                         clear_cookies(vpninfo);
750                         vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
751                 } else
752                         free(host);
753
754                 free(vpninfo->redirect_url);
755                 vpninfo->redirect_url = NULL;
756
757                 return 0;
758         } else if (strstr(vpninfo->redirect_url, "://")) {
759                 vpn_progress(vpninfo, PRG_ERR,
760                              _("Cannot follow redirection to non-https URL '%s'\n"),
761                              vpninfo->redirect_url);
762                 free(vpninfo->redirect_url);
763                 vpninfo->redirect_url = NULL;
764                 return -EINVAL;
765         } else if (vpninfo->redirect_url[0] == '/') {
766                 /* Absolute redirect within same host */
767                 free(vpninfo->urlpath);
768                 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
769                 free(vpninfo->redirect_url);
770                 vpninfo->redirect_url = NULL;
771                 return 0;
772         } else {
773                 char *lastslash = NULL;
774                 if (vpninfo->urlpath)
775                         lastslash = strrchr(vpninfo->urlpath, '/');
776                 if (!lastslash) {
777                         free(vpninfo->urlpath);
778                         vpninfo->urlpath = vpninfo->redirect_url;
779                         vpninfo->redirect_url = NULL;
780                 } else {
781                         char *oldurl = vpninfo->urlpath;
782                         *lastslash = 0;
783                         vpninfo->urlpath = NULL;
784                         if (asprintf(&vpninfo->urlpath, "%s/%s",
785                                      oldurl, vpninfo->redirect_url) == -1) {
786                                 int err = -errno;
787                                 vpn_progress(vpninfo, PRG_ERR,
788                                              _("Allocating new path for relative redirect failed: %s\n"),
789                                              strerror(-err));
790                                 return err;
791                         }
792                         free(oldurl);
793                         free(vpninfo->redirect_url);
794                         vpninfo->redirect_url = NULL;
795                 }
796                 return 0;
797         }
798 }
799
800 /* Inputs:
801  *  method:             GET or POST
802  *  vpninfo->hostname:  Host DNS name
803  *  vpninfo->port:      TCP port, typically 443
804  *  vpninfo->urlpath:   Relative path, e.g. /+webvpn+/foo.html
805  *  request_body_type:  Content type for a POST (e.g. text/html).  Can be NULL.
806  *  request_body:       POST content
807  *  form_buf:           Callee-allocated buffer for server content
808  *
809  * Return value:
810  *  < 0, on error
811  *  >=0, on success, indicating the length of the data in *form_buf
812  */
813 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
814                             const char *request_body_type, const char *request_body,
815                             char **form_buf, int fetch_redirect)
816 {
817         struct oc_text_buf *buf;
818         int result, buflen;
819
820  retry:
821         vpninfo->redirect_type = REDIR_TYPE_NONE;
822
823         if (*form_buf) {
824                 free(*form_buf);
825                 *form_buf = NULL;
826         }
827         if (openconnect_open_https(vpninfo)) {
828                 vpn_progress(vpninfo, PRG_ERR,
829                              _("Failed to open HTTPS connection to %s\n"),
830                              vpninfo->hostname);
831                 return -EINVAL;
832         }
833
834         /*
835          * It would be nice to use cURL for this, but we really need to guarantee
836          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
837          * to have any way to let us provide our own socket read/write functions.
838          * We can only provide a socket _open_ function. Which would require having
839          * a socketpair() and servicing the "other" end of it.
840          *
841          * So we process the HTTP for ourselves...
842          */
843         buf = buf_alloc();
844         buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
845         add_common_headers(vpninfo, buf);
846
847         if (request_body_type) {
848                 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
849                 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
850         }
851         buf_append(buf, "\r\n");
852
853         if (request_body_type)
854                 buf_append(buf, "%s", request_body);
855
856         if (vpninfo->port == 443)
857                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
858                              method, vpninfo->hostname,
859                              vpninfo->urlpath ?: "");
860         else
861                 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
862                              method, vpninfo->hostname, vpninfo->port,
863                              vpninfo->urlpath ?: "");
864
865         if (buf_error(buf))
866                 return buf_free(buf);
867
868         result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
869         buf_free(buf);
870         if (result < 0)
871                 return result;
872
873         buflen = process_http_response(vpninfo, &result, NULL, form_buf);
874         if (buflen < 0) {
875                 /* We'll already have complained about whatever offended us */
876                 return buflen;
877         }
878
879         if (result != 200 && vpninfo->redirect_url) {
880                 result = handle_redirect(vpninfo);
881                 if (result == 0) {
882                         if (!fetch_redirect)
883                                 return 0;
884                         goto retry;
885                 }
886                 goto out;
887         }
888         if (!*form_buf || result != 200) {
889                 vpn_progress(vpninfo, PRG_ERR,
890                              _("Unexpected %d result from server\n"),
891                              result);
892                 result = -EINVAL;
893                 goto out;
894         }
895
896         return buflen;
897
898  out:
899         free(*form_buf);
900         *form_buf = NULL;
901         return result;
902 }
903
904 /* Return value:
905  *  < 0, if the data is unrecognized
906  *  = 0, if the page contains an XML document
907  *  = 1, if the page is a wait/refresh HTML page
908  */
909 static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
910 {
911         if (strncmp(form_buf, "<?xml", 5)) {
912                 /* Not XML? Perhaps it's HTML with a refresh... */
913                 if (strcasestr(form_buf, "http-equiv=\"refresh\""))
914                         return 1;
915                 vpn_progress(vpninfo, PRG_ERR,
916                              _("Unknown response from server\n"));
917                 return -EINVAL;
918         }
919         return 0;
920 }
921
922 /* Return value:
923  *  < 0, on error
924  *  > 0, no cookie (user cancel)
925  *  = 0, obtained cookie
926  */
927 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
928 {
929         struct vpn_option *opt;
930         char *form_buf = NULL;
931         struct oc_auth_form *form = NULL;
932         int result, buflen, tries;
933         char request_body[2048];
934         const char *request_body_type = "application/x-www-form-urlencoded";
935         const char *method = "POST";
936         int xmlpost = 0;
937
938         /* Step 1: Unlock software token (if applicable) */
939         if (vpninfo->use_stoken) {
940                 result = prepare_stoken(vpninfo);
941                 if (result)
942                         return result;
943         }
944
945         /*
946          * Step 2: Probe for XML POST compatibility
947          *
948          * This can get stuck in a redirect loop, so give up after any of:
949          *
950          * a) HTTP error (e.g. 400 Bad Request)
951          * b) Same-host redirect (e.g. Location: /foo/bar)
952          * c) Three redirects without seeing a plausible login form
953          */
954         result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
955         if (result < 0)
956                 return result;
957
958         for (tries = 0; ; tries++) {
959                 if (tries == 3)
960                         break;
961                 buflen = do_https_request(vpninfo, method, request_body_type, request_body,
962                                           &form_buf, 0);
963                 if (buflen == -EINVAL)
964                         break;
965                 if (buflen < 0)
966                         return buflen;
967
968                 if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
969                         break;
970                 else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
971                         continue;
972
973                 result = parse_xml_response(vpninfo, form_buf, &form);
974                 if (result < 0)
975                         break;
976
977                 xmlpost = 1;
978                 vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
979                 break;
980         }
981
982         /* Step 3: Fetch and parse the login form, if not using XML POST */
983         if (!xmlpost) {
984                 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
985                 if (buflen < 0)
986                         return buflen;
987
988                 result = parse_xml_response(vpninfo, form_buf, &form);
989                 if (result < 0) {
990                         free(form_buf);
991                         return result;
992                 }
993                 if (form->action) {
994                         vpninfo->redirect_url = strdup(form->action);
995                         handle_redirect(vpninfo);
996                 }
997         }
998
999         /* Step 4: Run the CSD trojan, if applicable */
1000         if (vpninfo->csd_starturl) {
1001                 char *form_path = NULL;
1002
1003                 if (vpninfo->urlpath) {
1004                         form_path = strdup(vpninfo->urlpath);
1005                         if (!form_path) {
1006                                 result = -ENOMEM;
1007                                 goto out;
1008                         }
1009                 }
1010
1011                 /* fetch the CSD program, if available */
1012                 if (vpninfo->csd_stuburl) {
1013                         buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1014                         if (buflen <= 0) {
1015                                 result = -EINVAL;
1016                                 goto out;
1017                         }
1018                 }
1019
1020                 /* This is the CSD stub script, which we now need to run */
1021                 result = run_csd_script(vpninfo, form_buf, buflen);
1022                 if (result)
1023                         goto out;
1024
1025                 /* vpninfo->urlpath now points to the wait page */
1026                 while (1) {
1027                         result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1028                         if (result <= 0)
1029                                 break;
1030
1031                         result = check_response_type(vpninfo, form_buf);
1032                         if (result <= 0)
1033                                 break;
1034
1035                         vpn_progress(vpninfo, PRG_INFO,
1036                                      _("Refreshing %s after 1 second...\n"),
1037                                      vpninfo->urlpath);
1038                         sleep(1);
1039                 }
1040                 if (result < 0)
1041                         goto out;
1042
1043                 /* refresh the form page, to see if we're authorized now */
1044                 free(vpninfo->urlpath);
1045                 vpninfo->urlpath = form_path;
1046
1047                 result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
1048                                           request_body_type, request_body, &form_buf, 1);
1049                 if (result < 0)
1050                         goto out;
1051
1052                 result = parse_xml_response(vpninfo, form_buf, &form);
1053                 if (result < 0)
1054                         goto out;
1055         }
1056
1057         /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
1058         while (1) {
1059                 request_body[0] = 0;
1060                 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
1061                                           &method, &request_body_type, xmlpost);
1062                 if (result < 0 || result == 1)
1063                         goto out;
1064                 if (result == 2)
1065                         break;
1066
1067                 result = do_https_request(vpninfo, method, request_body_type, request_body,
1068                                           &form_buf, 1);
1069                 if (result < 0)
1070                         goto out;
1071
1072                 result = parse_xml_response(vpninfo, form_buf, &form);
1073                 if (result < 0)
1074                         goto out;
1075         }
1076
1077         /* A return value of 2 means the XML form indicated
1078            success. We _should_ have a cookie... */
1079
1080         for (opt = vpninfo->cookies; opt; opt = opt->next) {
1081
1082                 if (!strcmp(opt->option, "webvpn"))
1083                         vpninfo->cookie = opt->value;
1084                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
1085                         char *tok = opt->value;
1086                         char *bu = NULL, *fu = NULL, *sha = NULL;
1087
1088                         do {
1089                                 if (tok != opt->value)
1090                                         *(tok++) = 0;
1091
1092                                 if (!strncmp(tok, "bu:", 3))
1093                                         bu = tok + 3;
1094                                 else if (!strncmp(tok, "fu:", 3))
1095                                         fu = tok + 3;
1096                                 else if (!strncmp(tok, "fh:", 3)) {
1097                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
1098                                                          SHA1_SIZE * 2))
1099                                                 break;
1100                                         sha = tok + 3;
1101                                 }
1102                         } while ((tok = strchr(tok, '&')));
1103
1104                         if (bu && fu && sha)
1105                                 fetch_config(vpninfo, bu, fu, sha);
1106                 }
1107         }
1108         result = 0;
1109
1110 out:
1111         free(form_buf);
1112         free_auth_form(form);
1113
1114         if (vpninfo->csd_scriptname) {
1115                 unlink(vpninfo->csd_scriptname);
1116                 free(vpninfo->csd_scriptname);
1117                 vpninfo->csd_scriptname = NULL;
1118         }
1119
1120         return result;
1121 }
1122
1123 char *openconnect_create_useragent(const char *base)
1124 {
1125         char *uagent;
1126
1127         if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1128                 return NULL;
1129
1130         return uagent;
1131 }
1132
1133 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1134                       char *buf, size_t len)
1135 {
1136         int i = 0;
1137         int ret;
1138
1139         if (len < 2)
1140                 return -EINVAL;
1141
1142         while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1143                 if (buf[i] == '\n') {
1144                         buf[i] = 0;
1145                         if (i && buf[i-1] == '\r') {
1146                                 buf[i-1] = 0;
1147                                 i--;
1148                         }
1149                         return i;
1150                 }
1151                 i++;
1152
1153                 if (i >= len - 1) {
1154                         buf[i] = 0;
1155                         return i;
1156                 }
1157         }
1158         buf[i] = 0;
1159         return i ?: ret;
1160 }
1161
1162 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1163                        unsigned char *buf, size_t len)
1164 {
1165         size_t count;
1166
1167         for (count = 0; count < len; ) {
1168                 fd_set rd_set, wr_set;
1169                 int maxfd = fd;
1170                 int i;
1171
1172                 FD_ZERO(&wr_set);
1173                 FD_ZERO(&rd_set);
1174                 FD_SET(fd, &wr_set);
1175                 if (vpninfo->cancel_fd != -1) {
1176                         FD_SET(vpninfo->cancel_fd, &rd_set);
1177                         if (vpninfo->cancel_fd > fd)
1178                                 maxfd = vpninfo->cancel_fd;
1179                 }
1180
1181                 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1182                 if (vpninfo->cancel_fd != -1 &&
1183                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1184                         return -EINTR;
1185
1186                 /* Not that this should ever be able to happen... */
1187                 if (!FD_ISSET(fd, &wr_set))
1188                         continue;
1189
1190                 i = write(fd, buf + count, len - count);
1191                 if (i < 0)
1192                         return -errno;
1193
1194                 count += i;
1195         }
1196         return 0;
1197 }
1198
1199 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1200                       unsigned char *buf, size_t len)
1201 {
1202         size_t count;
1203
1204         for (count = 0; count < len; ) {
1205                 fd_set rd_set;
1206                 int maxfd = fd;
1207                 int i;
1208
1209                 FD_ZERO(&rd_set);
1210                 FD_SET(fd, &rd_set);
1211                 if (vpninfo->cancel_fd != -1) {
1212                         FD_SET(vpninfo->cancel_fd, &rd_set);
1213                         if (vpninfo->cancel_fd > fd)
1214                                 maxfd = vpninfo->cancel_fd;
1215                 }
1216
1217                 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1218                 if (vpninfo->cancel_fd != -1 &&
1219                     FD_ISSET(vpninfo->cancel_fd, &rd_set))
1220                         return -EINTR;
1221
1222                 /* Not that this should ever be able to happen... */
1223                 if (!FD_ISSET(fd, &rd_set))
1224                         continue;
1225
1226                 i = read(fd, buf + count, len - count);
1227                 if (i < 0)
1228                         return -errno;
1229
1230                 count += i;
1231         }
1232         return 0;
1233 }
1234
1235 static const char *socks_errors[] = {
1236         N_("request granted"),
1237         N_("general failure"),
1238         N_("connection not allowed by ruleset"),
1239         N_("network unreachable"),
1240         N_("host unreachable"),
1241         N_("connection refused by destination host"),
1242         N_("TTL expired"),
1243         N_("command not supported / protocol error"),
1244         N_("address type not supported")
1245 };
1246
1247 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1248 {
1249         unsigned char buf[1024];
1250         int i;
1251
1252         buf[0] = 5; /* SOCKS version */
1253         buf[1] = 1; /* # auth methods */
1254         buf[2] = 0; /* No auth supported */
1255
1256         if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1257                 vpn_progress(vpninfo, PRG_ERR,
1258                              _("Error writing auth request to SOCKS proxy: %s\n"),
1259                              strerror(-i));
1260                 return i;
1261         }
1262         
1263         if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1264                 vpn_progress(vpninfo, PRG_ERR,
1265                              _("Error reading auth response from SOCKS proxy: %s\n"),
1266                              strerror(-i));
1267                 return i;
1268         }
1269         if (buf[0] != 5) {
1270                 vpn_progress(vpninfo, PRG_ERR,
1271                              _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1272                              buf[0], buf[1]);
1273                 return -EIO;
1274         }
1275         if (buf[1]) {
1276         socks_err:
1277                 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1278                         vpn_progress(vpninfo, PRG_ERR,
1279                                      _("SOCKS proxy error %02x: %s\n"),
1280                                      buf[1], _(socks_errors[buf[1]]));
1281                 else
1282                         vpn_progress(vpninfo, PRG_ERR,
1283                                      _("SOCKS proxy error %02x\n"),
1284                                      buf[1]);
1285                 return -EIO;
1286         }
1287
1288         vpn_progress(vpninfo, PRG_INFO,
1289                      _("Requesting SOCKS proxy connection to %s:%d\n"),
1290                      vpninfo->hostname, vpninfo->port);
1291
1292         buf[0] = 5; /* SOCKS version */
1293         buf[1] = 1; /* CONNECT */
1294         buf[2] = 0; /* Reserved */
1295         buf[3] = 3; /* Address type is domain name */
1296         buf[4] = strlen(vpninfo->hostname);
1297         strcpy((char *)buf + 5, vpninfo->hostname);
1298         i = strlen(vpninfo->hostname) + 5;
1299         buf[i++] = vpninfo->port >> 8;
1300         buf[i++] = vpninfo->port & 0xff;
1301
1302         if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1303                 vpn_progress(vpninfo, PRG_ERR,
1304                              _("Error writing connect request to SOCKS proxy: %s\n"),
1305                              strerror(-i));
1306                 return i;
1307         }
1308         /* Read 5 bytes -- up to and including the first byte of the returned
1309            address (which might be the length byte of a domain name) */
1310         if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1311                 vpn_progress(vpninfo, PRG_ERR,
1312                              _("Error reading connect response from SOCKS proxy: %s\n"),
1313                              strerror(-i));
1314                 return i;
1315         }
1316         if (buf[0] != 5) {
1317                 vpn_progress(vpninfo, PRG_ERR,
1318                              _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1319                              buf[0], buf[1]);
1320                 return -EIO;
1321         }
1322         if (buf[1])
1323                 goto socks_err;
1324
1325         /* Connect responses contain an address */
1326         switch(buf[3]) {
1327         case 1: /* Legacy IP */
1328                 i = 5;
1329                 break;
1330         case 3: /* Domain name */
1331                 i = buf[4] + 2;
1332                 break;
1333         case 4: /* IPv6 */
1334                 i = 17;
1335                 break;
1336         default:
1337                 vpn_progress(vpninfo, PRG_ERR,
1338                              _("Unexpected address type %02x in SOCKS connect response\n"),
1339                              buf[3]);
1340                 return -EIO;
1341         }
1342
1343         if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1344                 vpn_progress(vpninfo, PRG_ERR,
1345                              _("Error reading connect response from SOCKS proxy: %s\n"),
1346                              strerror(-i));
1347                 return i;
1348         }
1349         return 0;
1350 }
1351
1352 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1353 {
1354         char buf[MAX_BUF_LEN];
1355         struct oc_text_buf *reqbuf;
1356         int buflen, result;
1357
1358         reqbuf = buf_alloc();
1359         buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1360         buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1361         buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1362         buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1363         buf_append(reqbuf, "Connection: keep-alive\r\n");
1364         buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1365         buf_append(reqbuf, "\r\n");
1366
1367         if (buf_error(reqbuf))
1368                 return buf_free(reqbuf);
1369
1370         vpn_progress(vpninfo, PRG_INFO,
1371                      _("Requesting HTTP proxy connection to %s:%d\n"),
1372                      vpninfo->hostname, vpninfo->port);
1373
1374         result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1375         buf_free(reqbuf);
1376
1377         if (result) {
1378                 vpn_progress(vpninfo, PRG_ERR,
1379                              _("Sending proxy request failed: %s\n"),
1380                              strerror(-result));
1381                 return result;
1382         }
1383
1384         if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1385                 vpn_progress(vpninfo, PRG_ERR,
1386                              _("Error fetching proxy response\n"));
1387                 return -EIO;
1388         }
1389
1390         if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1391             buf[8] != ' ' || !(result = atoi(buf+9))) {
1392                 vpn_progress(vpninfo, PRG_ERR,
1393                              _("Failed to parse proxy response '%s'\n"), buf);
1394                 return -EINVAL;
1395         }
1396
1397         if (result != 200) {
1398                 vpn_progress(vpninfo, PRG_ERR,
1399                              _("Proxy CONNECT request failed: %s\n"), buf);
1400                 return -EIO;
1401         }
1402
1403         while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1404                 if (buflen < 0) {
1405                         vpn_progress(vpninfo, PRG_ERR,
1406                                      _("Failed to read proxy response\n"));
1407                         return -EIO;
1408                 }
1409                 vpn_progress(vpninfo, PRG_ERR,
1410                              _("Unexpected continuation line after CONNECT response: '%s'\n"),
1411                              buf);
1412         }
1413
1414         return 0;
1415 }
1416
1417 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1418 {
1419         if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1420                 return process_http_proxy(vpninfo, ssl_sock);
1421         
1422         if (!strcmp(vpninfo->proxy_type, "socks") ||
1423             !strcmp(vpninfo->proxy_type, "socks5"))
1424                 return process_socks_proxy(vpninfo, ssl_sock);
1425
1426         vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1427                      vpninfo->proxy_type);
1428         return -EIO;
1429 }
1430
1431 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1432 {
1433         char *url = proxy;
1434         int ret;
1435
1436         if (!url)
1437                 return -ENOMEM;
1438
1439         free(vpninfo->proxy_type);
1440         vpninfo->proxy_type = NULL;
1441         free(vpninfo->proxy);
1442         vpninfo->proxy = NULL;
1443
1444         ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1445                                  &vpninfo->proxy_port, NULL, 80);
1446         if (ret)
1447                 goto out;
1448
1449         if (vpninfo->proxy_type &&
1450             strcmp(vpninfo->proxy_type, "http") &&
1451             strcmp(vpninfo->proxy_type, "socks") &&
1452             strcmp(vpninfo->proxy_type, "socks5")) {
1453                 vpn_progress(vpninfo, PRG_ERR,
1454                              _("Only http or socks(5) proxies supported\n"));
1455                 free(vpninfo->proxy_type);
1456                 vpninfo->proxy_type = NULL;
1457                 free(vpninfo->proxy);
1458                 vpninfo->proxy = NULL;
1459                 return -EINVAL;
1460         }
1461  out:
1462         free(url);
1463         return ret;
1464 }