Package org.lealone.engine

Source Code of org.lealone.engine.DatabaseEngineBase

/*
* Copyright 2011 The Apache Software Foundation
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF 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.lealone.engine;

import java.util.HashMap;

import org.lealone.command.CommandInterface;
import org.lealone.command.Parser;
import org.lealone.constant.Constants;
import org.lealone.constant.DbSettings;
import org.lealone.constant.ErrorCode;
import org.lealone.constant.SetTypes;
import org.lealone.constant.SysProperties;
import org.lealone.dbobject.User;
import org.lealone.message.DbException;
import org.lealone.util.MathUtils;
import org.lealone.util.New;
import org.lealone.util.StringUtils;

/**
* The engine contains a map of all open databases.
* It is also responsible for opening and creating new databases.
* This is a singleton class.
*/
public abstract class DatabaseEngineBase implements DatabaseEngine {

    private static final HashMap<String, Database> DATABASES = New.hashMap();

    private volatile long wrongPasswordDelay = SysProperties.DELAY_WRONG_PASSWORD_MIN;

    /**
     * Open a database connection with the given connection information.
     *
     * @param ci the connection information
     * @return the session
     */
    @Override
    public synchronized Session createSession(ConnectionInfo ci) {
        return createSessionAndValidate(ci);
    }

    /**
     * Called after a database has been closed, to remove the object from the
     * list of open databases.
     *
     * @param dbName the database name
     */
    @Override
    public synchronized void closeDatabase(String dbName) {
        unregisterMBean(dbName);
        DATABASES.remove(dbName);
    }

    protected Session createSessionAndValidate(ConnectionInfo ci) {
        try {
            Session session = openSession(ci);
            validateUserAndPassword(true);
            return session;
        } catch (DbException e) {
            if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) {
                validateUserAndPassword(false);
            }
            throw e;
        }
    }

    protected Session openSession(ConnectionInfo ci) {
        boolean ifExists = ci.removeProperty("IFEXISTS", false);
        boolean ignoreUnknownSetting = ci.removeProperty("IGNORE_UNKNOWN_SETTINGS", false);
        String cipher = ci.removeProperty("CIPHER", null);
        String init = ci.removeProperty("INIT", null);
        Session session;
        while (true) {
            session = openSession(ci, ifExists, cipher);
            if (session != null) {
                break;
            }
            // we found a database that is currently closing
            // wait a bit to avoid a busy loop (the method is synchronized)
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // ignore
            }
        }

        ci.removeProperty("SERVER_TYPE", false);
        session.setAllowLiterals(true);
        DbSettings defaultSettings = DbSettings.getInstance();
        for (String setting : ci.getKeys()) {
            if (defaultSettings.containsKey(setting)) {
                // database setting are only used when opening the database
                continue;
            }
            String value = ci.getProperty(setting);
            try {
                CommandInterface command = session.prepareLocal("SET " + Parser.quoteIdentifier(setting) + " " + value);
                command.executeUpdate();
            } catch (DbException e) {
                if (!ignoreUnknownSetting) {
                    session.close();
                    throw e;
                }
            }
        }
        if (init != null) {
            try {
                CommandInterface command = session.prepareCommand(init, Integer.MAX_VALUE);
                command.executeUpdate();
            } catch (DbException e) {
                if (!ignoreUnknownSetting) {
                    session.close();
                    throw e;
                }
            }
        }
        session.setAllowLiterals(false);
        session.commit(true);
        return session;
    }

    protected Session openSession(ConnectionInfo ci, boolean ifExists, String cipher) {
        String name = ci.getDatabaseName();
        Database database;
        ci.removeProperty("NO_UPGRADE", false);
        boolean openNew = ci.getProperty("OPEN_NEW", false);
        if (openNew) {
            database = null;
        } else {
            database = DATABASES.get(name);
        }
        User user = null;
        boolean opened = false;
        if (database == null) {
            if (ifExists && !Database.exists(name)) {
                throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, name);
            }
            database = createDatabase();
            database.init(ci, cipher);
            opened = true;
            if (database.getAllUsers().size() == 0) {
                // users is the last thing we add, so if no user is around,
                // the database is new (or not initialized correctly)
                user = new User(database, database.allocateObjectId(), ci.getUserName(), false);
                user.setAdmin(true);
                user.setUserPasswordHash(ci.getUserPasswordHash());
                database.setMasterUser(user);
            }
            DATABASES.put(name, database);
        }
        synchronized (database) {
            if (opened) {
                // start the thread when already synchronizing on the database
                // otherwise a deadlock can occur when the writer thread
                // opens a new database (as in recovery testing)
                database.opened();
            }
            if (database.isClosing()) {
                return null;
            }
            if (user == null) {
                if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
                    user = database.findUser(ci.getUserName());
                    if (user != null) {
                        if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
                            user = null;
                        }
                    }
                }
                if (opened && (user == null || !user.isAdmin())) {
                    // reset - because the user is not an admin, and has no
                    // right to listen to exceptions
                    database.setEventListener(null);
                }
            }
            if (user == null) {
                database.removeSession(null);
                throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
            }
            checkClustering(ci, database);
            Session session = database.createSession(user);
            registerMBean(ci, database, session);
            return session;
        }
    }

    protected void registerMBean(ConnectionInfo ci, Database database, Session session) {
    }

    protected void unregisterMBean(String dbName) {
    }

    private static void checkClustering(ConnectionInfo ci, Database database) {
        String clusterSession = ci.getProperty(SetTypes.CLUSTER, null);
        if (Constants.CLUSTERING_DISABLED.equals(clusterSession)) {
            // in this case, no checking is made
            // (so that a connection can be made to disable/change clustering)
            return;
        }
        String clusterDb = database.getCluster();
        if (!Constants.CLUSTERING_DISABLED.equals(clusterDb)) {
            if (!Constants.CLUSTERING_ENABLED.equals(clusterSession)) {
                if (!StringUtils.equals(clusterSession, clusterDb)) {
                    if (clusterDb.equals(Constants.CLUSTERING_DISABLED)) {
                        throw DbException.get(ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_ALONE);
                    }
                    throw DbException.get(ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1, clusterDb);
                }
            }
        }
    }

    /**
     * This method is called after validating user name and password. If user
     * name and password were correct, the sleep time is reset, otherwise this
     * method waits some time (to make brute force / rainbow table attacks
     * harder) and then throws a 'wrong user or password' exception. The delay
     * is a bit randomized to protect against timing attacks. Also the delay
     * doubles after each unsuccessful logins, to make brute force attacks
     * harder.
     *
     * There is only one exception message both for wrong user and for
     * wrong password, to make it harder to get the list of user names. This
     * method must only be called from one place, so it is not possible from the
     * stack trace to see if the user name was wrong or the password.
     *
     * @param correct if the user name or the password was correct
     * @throws DbException the exception 'wrong user or password'
     */
    protected void validateUserAndPassword(boolean correct) {
        int min = SysProperties.DELAY_WRONG_PASSWORD_MIN;
        if (correct) {
            long delay = wrongPasswordDelay;
            if (delay > min && delay > 0) {
                // the first correct password must be blocked,
                // otherwise parallel attacks are possible
                synchronized (this) {
                    // delay up to the last delay
                    // an attacker can't know how long it will be
                    delay = MathUtils.secureRandomInt((int) delay);
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                    wrongPasswordDelay = min;
                }
            }
        } else {
            // this method is not synchronized on the Engine, so that
            // regular successful attempts are not blocked
            synchronized (this) {
                long delay = wrongPasswordDelay;
                int max = SysProperties.DELAY_WRONG_PASSWORD_MAX;
                if (max <= 0) {
                    max = Integer.MAX_VALUE;
                }
                wrongPasswordDelay += wrongPasswordDelay;
                if (wrongPasswordDelay > max || wrongPasswordDelay < 0) {
                    wrongPasswordDelay = max;
                }
                if (min > 0) {
                    // a bit more to protect against timing attacks
                    delay += Math.abs(MathUtils.secureRandomLong() % 100);
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                }
                throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
            }
        }
    }
}
TOP

Related Classes of org.lealone.engine.DatabaseEngineBase

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.