Changeset 4a9f7b7


Ignore:
Timestamp:
Dec 5, 2010 7:04:33 PM (10 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
653a68b8
Parents:
86de251
Message:
  • Console:
    • Add SSL support - To enable, change clients.config. Examples:

## Change to SSL only - just add a '-s'
clientApp.0.args=-s 7657 ::1,127.0.0.1 ./webapps/
## Use both non-SSL and SSL - add '-s port interface'
clientApp.0.args=7657 ::1,127.0.0.1 -s 7667 ::1,127.0.0.1 ./webapps/
## …and change URLLauncher args further down for the browser to open https:// at startup if you like.

Files:
3 edited

Legend:

Unmodified
Added
Removed
  • apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java

    r86de251 r4a9f7b7  
    1111import net.i2p.I2PAppContext;
    1212import net.i2p.apps.systray.SysTray;
     13import net.i2p.data.Base32;
    1314import net.i2p.data.DataHelper;
    1415import net.i2p.router.RouterContext;
     
    1617import net.i2p.util.I2PAppThread;
    1718import net.i2p.util.SecureDirectory;
     19import net.i2p.util.SecureFileOutputStream;
     20import net.i2p.util.ShellCommand;
    1821
    1922import org.mortbay.http.DigestAuthenticator;
    2023import org.mortbay.http.HashUserRealm;
    2124import org.mortbay.http.SecurityConstraint;
     25import org.mortbay.http.SslListener;
    2226import org.mortbay.http.handler.SecurityHandler;
    2327import org.mortbay.jetty.Server;
    2428import org.mortbay.jetty.servlet.WebApplicationContext;
    2529import org.mortbay.jetty.servlet.WebApplicationHandler;
     30import org.mortbay.util.InetAddrPort;
    2631
    2732public class RouterConsoleRunner {
    2833    private Server _server;
    29     private String _listenPort = "7657";
    30     private String _listenHost = "127.0.0.1";
    31     private String _webAppsDir = "./webapps/";
     34    private String _listenPort;
     35    private String _listenHost;
     36    private String _sslListenPort;
     37    private String _sslListenHost;
     38    private String _webAppsDir;
    3239    private static final String PROP_WEBAPP_CONFIG_FILENAME = "router.webappsConfigFile";
    3340    private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config";
     
    3643    public static final String PREFIX = "webapps.";
    3744    public static final String ENABLED = ".startOnLoad";
     45    private static final String PROP_KEYSTORE_PASSWORD = "routerconsole.keystorePassword";
     46    private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
     47    private static final String PROP_KEY_PASSWORD = "routerconsole.keyPassword";
     48    private static final String DEFAULT_LISTEN_PORT = "7657";
     49    private static final String DEFAULT_LISTEN_HOST = "127.0.0.1";
     50    private static final String DEFAULT_WEBAPPS_DIR = "./webapps/";
     51    private static final String USAGE = "Bad RouterConsoleRunner arguments, check clientApp.0.args in your clients.config file! " +
     52                                        "Usage: [[port host[,host]] [-s sslPort [host[,host]]] [webAppsDir]]";
    3853   
    3954    static {
     
    4358   
    4459    /**
     60     *  <pre>
     61     *  non-SSL:
     62     *  RouterConsoleRunner
     63     *  RouterConsoleRunner 7657
     64     *  RouterConsoleRunner 7657 127.0.0.1
     65     *  RouterConsoleRunner 7657 127.0.0.1,::1
     66     *  RouterConsoleRunner 7657 127.0.0.1,::1 ./webapps/
     67     *
     68     *  SSL:
     69     *  RouterConsoleRunner -s 7657
     70     *  RouterConsoleRunner -s 7657 127.0.0.1
     71     *  RouterConsoleRunner -s 7657 127.0.0.1,::1
     72     *  RouterConsoleRunner -s 7657 127.0.0.1,::1 ./webapps/
     73     *
     74     *  If using both, non-SSL must be first:
     75     *  RouterConsoleRunner 7657 127.0.0.1 -s 7667
     76     *  RouterConsoleRunner 7657 127.0.0.1 -s 7667 127.0.0.1
     77     *  RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1
     78     *  RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1 ./webapps/
     79     *  </pre>
     80     *
    4581     *  @param args second arg may be a comma-separated list of bind addresses,
    4682     *              for example ::1,127.0.0.1
     
    5187     */
    5288    public RouterConsoleRunner(String args[]) {
    53         if (args.length == 3) {
    54             _listenPort = args[0].trim();
    55             _listenHost = args[1].trim();
    56             _webAppsDir = args[2].trim();
     89        if (args.length == 0) {
     90            // _listenHost and _webAppsDir are defaulted above
     91            _listenPort = DEFAULT_LISTEN_PORT;
     92        } else {
     93            boolean ssl = false;
     94            for (int i = 0; i < args.length; i++) {
     95                if (args[i].equals("-s"))
     96                    ssl = true;
     97                else if ((!ssl) && _listenPort == null)
     98                    _listenPort = args[i];
     99                else if ((!ssl) && _listenHost == null)
     100                    _listenHost = args[i];
     101                else if (ssl && _sslListenPort == null)
     102                    _sslListenPort = args[i];
     103                else if (ssl && _sslListenHost == null)
     104                    _sslListenHost = args[i];
     105                else if (_webAppsDir == null)
     106                    _webAppsDir = args[i];
     107                else {
     108                    System.err.println(USAGE);
     109                    throw new IllegalArgumentException(USAGE);
     110                }
     111            }
     112        }
     113        if (_listenHost == null)
     114           _listenHost = DEFAULT_LISTEN_HOST;
     115        if (_sslListenHost == null)
     116           _sslListenHost = _listenHost;
     117        if (_webAppsDir == null)
     118           _webAppsDir = DEFAULT_WEBAPPS_DIR;
     119        // _listenPort and _sslListenPort are not defaulted, if one or the other is null, do not enable
     120        if (_listenPort == null && _sslListenPort == null) {
     121            System.err.println(USAGE);
     122            throw new IllegalArgumentException(USAGE);
    57123        }
    58124    }
     
    97163        WebApplicationHandler baseHandler = null;
    98164        try {
    99             StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
    100165            int boundAddresses = 0;
    101             while (tok.hasMoreTokens()) {
    102                 String host = tok.nextToken().trim();
     166
     167            // add standard listeners
     168            if (_listenPort != null) {
     169                StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
     170                while (tok.hasMoreTokens()) {
     171                    String host = tok.nextToken().trim();
     172                    try {
     173                        if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5
     174                            _server.addListener('[' + host + "]:" + _listenPort);
     175                        else
     176                            _server.addListener(host + ':' + _listenPort);
     177                        boundAddresses++;
     178                    } catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below
     179                        System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
     180                    }
     181                }
     182            }
     183
     184            // add SSL listeners
     185            int sslPort = 0;
     186            if (_sslListenPort != null) {
    103187                try {
    104                     if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5
    105                         _server.addListener('[' + host + "]:" + _listenPort);
    106                     else
    107                         _server.addListener(host + ':' + _listenPort);
    108                     boundAddresses++;
    109                 } catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below
    110                     System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
    111                 }
    112             }
     188                    sslPort = Integer.parseInt(_sslListenPort);
     189                } catch (NumberFormatException nfe) {}
     190                if (sslPort <= 0)
     191                    System.err.println("Bad routerconsole SSL port " + _sslListenPort);
     192            }
     193            if (sslPort > 0) {
     194                I2PAppContext ctx = I2PAppContext.getGlobalContext();
     195                File keyStore = new File(ctx.getConfigDir(), "keystore/console.ks");
     196                if (verifyKeyStore(keyStore)) {
     197                    StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
     198                    while (tok.hasMoreTokens()) {
     199                        String host = tok.nextToken().trim();
     200                        // doing it this way means we don't have to escape an IPv6 host with []
     201                        InetAddrPort iap = new InetAddrPort(host, sslPort);
     202                        try {
     203                            SslListener ssll = new SslListener(iap);
     204                            // the keystore path and password
     205                            ssll.setKeystore(keyStore.getAbsolutePath());
     206                            ssll.setPassword(ctx.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD));
     207                            // the X.509 cert password (if not present, verifyKeyStore() returned false)
     208                            ssll.setKeyPassword(ctx.getProperty(PROP_KEY_PASSWORD, "thisWontWork"));
     209                            _server.addListener(ssll);
     210                            boundAddresses++;
     211                        } catch (Exception e) {   // probably no exceptions at this point
     212                            System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + " for SSL: " + e);
     213                        }
     214                    }
     215                } else {
     216                    System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
     217                }
     218            }
     219
    113220            if (boundAddresses <= 0) {
    114                 System.err.println("Unable to bind routerconsole to any address on port " + _listenPort);
     221                System.err.println("Unable to bind routerconsole to any address on port " + _listenPort + (sslPort > 0 ? (" or SSL port " + sslPort) : ""));
    115222                return;
    116223            }
     
    202309    }
    203310   
     311    /**
     312     * @return success if it exists and we have a password, or it was created successfully.
     313     * @since 0.8.3
     314     */
     315    private static boolean verifyKeyStore(File ks) {
     316        if (ks.exists()) {
     317            I2PAppContext ctx = I2PAppContext.getGlobalContext();
     318            boolean rv = ctx.getProperty(PROP_KEY_PASSWORD) != null;
     319            if (!rv)
     320                System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath());
     321            return rv;
     322        }
     323        File dir = ks.getParentFile();
     324        if (!dir.exists()) {
     325            File sdir = new SecureDirectory(dir.getAbsolutePath());
     326            if (!sdir.mkdir())
     327                return false;
     328        }
     329        return createKeyStore(ks);
     330    }
     331
     332
     333    /**
     334     * Call out to keytool to create a new keystore with a keypair in it.
     335     * Trying to do this programatically is a nightmare, requiring either BouncyCastle
     336     * libs or using proprietary Sun libs, and it's a huge mess.
     337     *
     338     * @return success
     339     * @since 0.8.3
     340     */
     341    private static boolean createKeyStore(File ks) {
     342        I2PAppContext ctx = I2PAppContext.getGlobalContext();
     343        // make a random 48 character password (30 * 8 / 5)
     344        byte[] rand = new byte[30];
     345        ctx.random().nextBytes(rand);
     346        String keyPassword = Base32.encode(rand);
     347        // and one for the cname
     348        ctx.random().nextBytes(rand);
     349        String cname = Base32.encode(rand) + ".console.i2p.net";
     350
     351        String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
     352        String[] args = new String[] {
     353                   keytool,
     354                   "-genkey",            // -genkeypair preferred in newer keytools, but this works with more
     355                   "-storetype", "JKS",
     356                   "-keystore", ks.getAbsolutePath(),
     357                   "-storepass", DEFAULT_KEYSTORE_PASSWORD,
     358                   "-alias", "console",
     359                   "-dname", "CN=" + cname + ",OU=Console,O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
     360                   "-validity", "3652",  // 10 years
     361                   "-keyalg", "DSA",
     362                   "-keysize", "1024",
     363                   "-keypass", keyPassword};
     364        boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30);  // 30 secs
     365        if (success) {
     366            success = ks.exists();
     367            if (success) {
     368                SecureFileOutputStream.setPerms(ks);
     369                try {
     370                    RouterContext rctx = (RouterContext) ctx;
     371                    rctx.router().setConfigSetting(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
     372                    rctx.router().setConfigSetting(PROP_KEY_PASSWORD, keyPassword);
     373                    rctx.router().saveConfig();
     374                } catch (Exception e) {}  // class cast exception
     375            }
     376        }
     377        if (success) {
     378            System.err.println("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
     379                               "The certificate name was generated randomly, and is not associated with your " +
     380                               "IP address, host name, router identity, or destination keys.");
     381        } else {
     382            System.err.println("Failed to create console SSL keystore using command line:");
     383            StringBuilder buf = new StringBuilder(256);
     384            for (int i = 0;  i < args.length; i++) {
     385                buf.append('"').append(args[i]).append("\" ");
     386            }
     387            System.err.println(buf.toString());
     388            System.err.println("This is for the Sun/Oracle keytool, others may be incompatible.\n" +
     389                               "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
     390                               " to " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath());
     391        }
     392        return success;
     393    }
     394
    204395    static void initialize(WebApplicationContext context) {
    205396        String password = getPassword();
  • core/java/src/net/i2p/util/ShellCommand.java

    r86de251 r4a9f7b7  
    5252    private class CommandThread extends Thread {
    5353
    54         final Object  caller;
    55         boolean consumeOutput;
    56         String  shellCommand;
    57 
    58         CommandThread(Object caller, String shellCommand, boolean consumeOutput) {
     54        private final Object  caller;
     55        private final boolean consumeOutput;
     56        private final Object shellCommand;
     57
     58        /**
     59         *  @param shellCommand either a String or a String[] (since 0.8.3)
     60         */
     61        CommandThread(Object caller, Object shellCommand, boolean consumeOutput) {
    5962            super("CommandThread");
    6063            this.caller = caller;
    6164            this.shellCommand = shellCommand;
    6265            this.consumeOutput = consumeOutput;
    63             _commandSuccessful = false;
    64             _commandCompleted = false;
    6566        }
    6667
     
    201202     * <code>STDIN</code> of the shell process via {@link #getInputStream()}.
    202203     *
     204     * Warning, no good way to quote or escape spaces in arguments with this method.
     205     * @deprecated unused
     206     *
    203207     * @param shellCommand The command for the shell to execute.
    204208     */
     
    216220     * {@link #getInputStream()}.
    217221     *
     222     * Warning, no good way to quote or escape spaces in arguments with this method.
     223     * @deprecated unused
     224     *
    218225     * @param  shellCommand The command for the shell to execute.
    219226     * @return              <code>true</code> if the spawned shell process
     
    237244     * {@link #getErrorStream()}, respectively. Input can be passed to the
    238245     * <code>STDIN</code> of the shell process via {@link #getInputStream()}.
     246     *
     247     * Warning, no good way to quote or escape spaces in arguments with this method.
     248     * @deprecated unused
    239249     *
    240250     * @param  shellCommand The command for the shell to execute.
     
    277287     * command will not be displayed.
    278288     *
     289     * Warning, no good way to quote or escape spaces in arguments with this method.
     290     * @deprecated unused
     291     *
    279292     * @param  shellCommand The command for the shell to execute.
    280293     * @throws IOException
     
    288301     * all of the command's resulting shell processes have completed. Any output
    289302     * produced by the executed command will not be displayed.
     303     *
     304     * Warning, no good way to quote or escape spaces in arguments with this method.
    290305     *
    291306     * @param  shellCommand The command for the shell to execute.
     
    308323     * executed command will not be displayed.
    309324     *
    310      * @param  shellCommand The command for the shell to execute.
     325     * Warning, no good way to quote or escape spaces in arguments when shellCommand is a String.
     326     * Use a String array for best results, especially on Windows.
     327     *
     328     * @param  shellCommand The command for the shell to execute, as a String.
     329     *                      You can't quote arguments successfully.
     330     *                      See Runtime.exec(String) for more info.
    311331     * @param  seconds      The method will return <code>true</code> if this
    312332     *                      number of seconds elapses without the process
     
    318338     */
    319339    public synchronized boolean executeSilentAndWaitTimed(String shellCommand, int seconds) {
    320 
     340        return executeSAWT(shellCommand, seconds);
     341    }
     342
     343    /**
     344     * Passes a command to the shell for execution. This method blocks until
     345     * all of the command's resulting shell processes have completed unless a
     346     * specified number of seconds has elapsed first. Any output produced by the
     347     * executed command will not be displayed.
     348     *
     349     * @param  commandArray The command for the shell to execute,
     350     *                      as a String[].
     351     *                      See Runtime.exec(String[]) for more info.
     352     * @param  seconds      The method will return <code>true</code> if this
     353     *                      number of seconds elapses without the process
     354     *                      returning an exit status. A value of <code>0</code>
     355     *                      here disables waiting.
     356     * @return              <code>true</code> if the spawned shell process
     357     *                      returns an exit status of 0 (indicating success),
     358     *                      else <code>false</code>.
     359     * @since 0.8.3
     360     */
     361    public synchronized boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) {
     362        return executeSAWT(commandArray, seconds);
     363    }
     364
     365    /** @since 0.8.3 */
     366    private boolean executeSAWT(Object shellCommand, int seconds) {
    321367        _commandThread = new CommandThread(this, shellCommand, CONSUME_OUTPUT);
    322368        _commandThread.start();
     
    365411    }
    366412   
    367     private boolean execute(String shellCommand, boolean consumeOutput, boolean waitForExitStatus) {
     413    /**
     414     *  @param shellCommand either a String or a String[] (since 0.8.3) - quick hack
     415     */
     416    private boolean execute(Object shellCommand, boolean consumeOutput, boolean waitForExitStatus) {
    368417
    369418        StreamConsumer processStderrConsumer;
     
    375424
    376425        try {
    377             _process = Runtime.getRuntime().exec(shellCommand, null);
     426            // easy way so we don't have to copy this whole method
     427            if (shellCommand instanceof String)
     428                _process = Runtime.getRuntime().exec((String)shellCommand);
     429            else if (shellCommand instanceof String[])
     430                _process = Runtime.getRuntime().exec((String[])shellCommand);
     431            else
     432               throw new ClassCastException("shell command must be a String or a String[]");
    378433            if (consumeOutput) {
    379434                processStderrConsumer = new StreamConsumer(_process.getErrorStream());
  • installer/resources/clients.config

    r86de251 r4a9f7b7  
    77
    88# fire up the web console
     9## There are several choices, here are some examples:
     10## non-SSL, bind to local IPv4 only
     11#clientApp.0.args=7657 127.0.0.1 ./webapps/
     12## non-SSL, bind to local IPv6 only
     13#clientApp.0.args=7657 ::1 ./webapps/
     14## non-SSL, bind to all IPv4 addresses
     15#clientApp.0.args=7657 0.0.0.0 ./webapps/
     16## non-SSL, bind to all IPv6 addresses
     17#clientApp.0.args=7657 :: ./webapps/
     18## For SSL only, change clientApp.4.args below to https://
     19## SSL only
     20#clientApp.0.args=-s 7657 ::1,127.0.0.1 ./webapps/
     21## non-SSL and SSL
     22#clientApp.0.args=7657 ::1,127.0.0.1 -s 7667 ::1,127.0.0.1 ./webapps/
     23## non-SSL only, both IPv6 and IPv4 local interfaces
    924clientApp.0.args=7657 ::1,127.0.0.1 ./webapps/
    1025clientApp.0.main=net.i2p.router.web.RouterConsoleRunner
Note: See TracChangeset for help on using the changeset viewer.