/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.bootstrap;

import com.google.common.io.ByteStreams;

import org.apache.lucene.util.StringHelper;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.env.Environment;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.URIParameter;

/** 
 * Initializes securitymanager with necessary permissions.
 * <p>
 * We use a template file (the one we test with), and add additional 
 * permissions based on the environment (data paths, etc)
 */
class Security {
    
    /** template policy file, the one used in tests */
    static final String POLICY_RESOURCE = "security.policy";
    
    /** 
     * Initializes securitymanager for the environment
     * Can only happen once!
     */
    static void configure(Environment environment) throws IOException {
        ESLogger log = Loggers.getLogger(Security.class);
        //String prop = System.getProperty("java.io.tmpdir");
        //log.trace("java.io.tmpdir {}", prop);
        // init lucene random seed. it will use /dev/urandom where available.
        StringHelper.randomId();
        InputStream config = Security.class.getResourceAsStream(POLICY_RESOURCE);
        if (config == null) {
            throw new NoSuchFileException(POLICY_RESOURCE);
        }
        Path newConfig = processTemplate(config, environment);
        System.setProperty("java.security.policy", newConfig.toString());
        // retrieve the parsed policy we created: its useful if something goes wrong
        Policy policy = null;
        try {
            policy = Policy.getInstance("JavaPolicy", new URIParameter(newConfig.toUri()));
        } catch (NoSuchAlgorithmException impossible) {
            throw new RuntimeException(impossible);
        }
        PermissionCollection permissions = policy.getPermissions(Security.class.getProtectionDomain());
        log.trace("generated permissions: {}", permissions);

        System.setSecurityManager(new SecurityManager());
        try {
            // don't hide securityexception here, it means java.io.tmpdir is not accessible!
            Files.delete(newConfig);
        } catch (SecurityException broken) {
            log.error("unable to properly access temporary files, permissions: {}", permissions);
            throw broken;
        } catch (IOException ignore) {
            // e.g. virus scanner on windows
        }
    }
   
    // package-private for testing
    static Path processTemplate(InputStream template, Environment environment) throws IOException {
        Path processed = Files.createTempFile(null, null);
        try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(processed))) {
            // copy the template as-is.
            try (InputStream in = new BufferedInputStream(template)) {
                ByteStreams.copy(in, output);
            }

            //  all policy files are UTF-8:
            //  https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
            try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
                writer.write(System.lineSeparator());
                writer.write("grant {");
                writer.write(System.lineSeparator());

                // add permissions for all configured paths.
                // TODO: improve test infra so we can reduce permissions where read/write
                // is not really needed...
                addPath(writer, environment.homeFile(), "read,readlink,write,delete");
                addPath(writer, environment.configFile(), "read,readlink,write,delete");
                addPath(writer, environment.logsFile(), "read,readlink,write,delete");
                addPath(writer, environment.pluginsFile(), "read,readlink,write,delete");
                for (Path path : environment.dataFiles()) {
                    addPath(writer, path, "read,readlink,write,delete");
                }
                for (Path path : environment.dataWithClusterFiles()) {
                    addPath(writer, path, "read,readlink,write,delete");
                }

                writer.write("};");
                writer.write(System.lineSeparator());
            }
        }
        return processed;
    }
    
    static void addPath(Writer writer, Path path, String permissions) throws IOException {
        // paths may not exist yet
        Files.createDirectories(path);
        // add each path twice: once for itself, again for files underneath it
        writer.write("permission java.io.FilePermission \"" + encode(path) + "\", \"" + permissions + "\";");
        writer.write(System.lineSeparator());
        writer.write("permission java.io.FilePermission \"" + encode(path) + "${/}-\", \"" + permissions + "\";");
        writer.write(System.lineSeparator());
    }
    
    // Any backslashes in paths must be escaped, because it is the escape character when parsing.
    // See "Note Regarding File Path Specifications on Windows Systems".
    // https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
    static String encode(Path path) {
        return path.toString().replace("\\", "\\\\");
    }
}

@@ -1209,162 +1209,8 @@
 ath;
-%0Aimport java.security.NoSuchAlgorithmException;%0Aimport java.security.PermissionCollection;%0Aimport java.security.Policy;%0Aimport java.security.URIParameter;
 %0A%0A/*
@@ -1788,118 +1788,8 @@
 s);%0A
-        //String prop = System.getProperty(%22java.io.tmpdir%22);%0A        //log.trace(%22java.io.tmpdir %7B%7D%22, prop);%0A
     
@@ -2218,513 +2218,8 @@
 ));%0A
-        // retrieve the parsed policy we created: its useful if something goes wrong%0A        Policy policy = null;%0A        try %7B%0A            policy = Policy.getInstance(%22JavaPolicy%22, new URIParameter(newConfig.toUri()));%0A        %7D catch (NoSuchAlgorithmException impossible) %7B%0A            throw new RuntimeException(impossible);%0A        %7D%0A        PermissionCollection permissions = policy.getPermissions(Security.class.getProtectionDomain());%0A        log.trace(%22generated permissions: %7B%7D%22, permissions);%0A%0A
     
@@ -2531,37 +2531,67 @@
 es, 
-permissions: %7B%7D%22, permissions
+run with -Djava.security.debug=policy for more information%22
 );%0A 
@@ -4013,32 +4013,32 @@
 write,delete%22);%0A
-
                 
@@ -4103,32 +4103,260 @@
 write,delete%22);%0A
+%0A                // generate explicit perms for actual temp dir:%0A                // (in case there is java.io.tmpdir sheistiness on windows)%0A                addPath(writer, processed.getParent(), %22read,readlink,write,delete%22);%0A%0A
                 

add a hack for windows