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