org.panbox.desktop.common.sharemgmt.ShareManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.panbox.desktop.common.sharemgmt.ShareManagerImpl.java

Source

/*
 * 
 *               Panbox - encryption for cloud storage 
 *      Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Additonally, third party code may be provided with notices and open source
 * licenses from communities and third parties that govern the use of those
 * portions, and any licenses granted hereunder do not alter any rights and
 * obligations you may have under such open source licenses, however, the
 * disclaimer of warranty and limitation of liability provisions of the GPLv3 
 * will apply to all the product.
 * 
 */
package org.panbox.desktop.common.sharemgmt;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;
import org.panbox.PanboxConstants;
import org.panbox.Settings;
import org.panbox.core.Utils;
import org.panbox.core.crypto.KeyConstants;
import org.panbox.core.csp.StorageBackendType;
import org.panbox.core.exception.DeviceListException;
import org.panbox.core.exception.ObfuscationException;
import org.panbox.core.exception.ShareMetaDataException;
import org.panbox.core.identitymgmt.AbstractIdentity;
import org.panbox.core.identitymgmt.IPerson;
import org.panbox.core.identitymgmt.PanboxContact;
import org.panbox.core.keymgmt.VolumeParams;
import org.panbox.core.keymgmt.VolumeParams.VolumeParamsFactory;
import org.panbox.desktop.common.ex.DeviceKeyException;
import org.panbox.desktop.common.gui.PasswordEnterDialog;
import org.panbox.desktop.common.gui.PasswordEnterDialog.PermissionType;
import org.panbox.desktop.common.gui.shares.PanboxShare;
import org.panbox.desktop.common.utils.FileUtils;

public class ShareManagerImpl implements IShareManager {

    private static final Logger logger = Logger.getLogger("org.panbox");

    private final String SHARESDB = Settings.getInstance().getSharesDBPath();
    private final String SHARESDB_CONNECT_STRING = "jdbc:sqlite:" + SHARESDB;
    private static final String TABLE_SHARES = "shares";

    private static ShareManagerImpl instance = null;
    private Connection connection = null;

    private AbstractIdentity identity;

    private final VolumeParamsFactory paramsFactory = VolumeParamsFactory.getFactory();

    public final IPanboxService service;

    private ShareManagerImpl(IPanboxService service) throws ShareManagerException {
        this.service = service;

        // create a database connection
        try {
            connection = DriverManager.getConnection(SHARESDB_CONNECT_STRING);
        } catch (SQLException ex) {
            throw new ShareManagerException("Could not get connection for SQL DB: " + SHARESDB_CONNECT_STRING, ex);
        }
        init();
    }

    public static ShareManagerImpl getInstance() throws ShareManagerException {
        if (instance == null) {
            throw new RuntimeException("Service implementation has not been set yet!");
        }
        return instance;
    }

    public static ShareManagerImpl getInstance(IPanboxService service) throws ShareManagerException {
        if (instance == null) {
            instance = new ShareManagerImpl(service);
            // init();
        }
        return instance;
    }

    private void init() throws ShareManagerException {
        Connection con = null;
        try {
            con = DriverManager.getConnection(SHARESDB_CONNECT_STRING);
            Statement s = con.createStatement();
            ResultSet rs = s.executeQuery(
                    "SELECT name FROM sqlite_master WHERE type='table' AND name='" + TABLE_SHARES + "';");
            if (!rs.next()) {
                logger.debug("ShareManager database did not exist. Creating a new one now...");
                System.out.println("new ShareManager, creating table...");
                createTables(s);
            } else {
                logger.debug("ShareManager database exists. Will use that one...");
            }
        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run SQL command init: ", ex);
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException ex) {
                    throw new ShareManagerException("Failed to run SQL command init: ", ex);
                }
            }
        }
    }

    private static void createTables(Statement statement) throws ShareManagerException {
        logger.debug("ShareManager : createTables");
        try {
            statement.executeUpdate("drop table if exists " + TABLE_SHARES);
            statement.executeUpdate("create table " + TABLE_SHARES
                    + " (id INTEGER PRIMARY KEY AUTOINCREMENT, uuid string, name string, type string, backendURL string)");
        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run SQL command during createTables: ", ex);
        }
    }

    @Override
    public List<String> getInstalledShares() throws ShareManagerException {
        logger.debug("ShareManager : getInstalledShares");

        List<String> shares = new ArrayList<String>();
        ResultSet rs = null;
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.setQueryTimeout(30); // set timeout to 30 sec.

            rs = statement.executeQuery("select * from " + TABLE_SHARES);

            while (rs.next()) {
                String shareName = rs.getString("name");
                shares.add(shareName);
            }
        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run getInstalledShares: ", ex);
        } finally {
            try {
                if (rs != null)
                    rs.close();
            } catch (Exception e) {
            }

            try {
                if (statement != null)
                    statement.close();
            } catch (Exception e) {
            }
        }
        return shares;
    }

    private StorageBackendType getShareType(String shareType) {
        if (shareType.equals(StorageBackendType.DROPBOX.toString())) {
            return StorageBackendType.DROPBOX;
        } else if (shareType.equals(StorageBackendType.FOLDER.toString())) {
            return StorageBackendType.FOLDER;
        }
        // should not have gotten here
        logger.warn("Could not find correct StorageBackenType for type description: " + shareType);
        logger.warn("Returning StorageBackendType FOLDER as fallback!");
        return StorageBackendType.FOLDER;
    }

    @Override
    public boolean shareNameAvailable(String shareName)
            throws ShareManagerException, UnrecoverableKeyException, ShareMetaDataException {
        logger.debug("ShareManager : isShareNameAvailable(" + shareName + ")");
        ResultSet rs = null;
        String sql = "select * from " + TABLE_SHARES + " where name=?";
        PreparedStatement pstatement = null;
        try {
            pstatement = connection.prepareStatement(sql);
            pstatement.setString(1, shareName);
            pstatement.setQueryTimeout(30); // set timeout to 30 sec.
            rs = pstatement.executeQuery();

            if (rs.next()) {
                return false;
            } else {
                return true;
            }
        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run shareNameAvailable: ", ex);
        } finally {
            try {
                if (rs != null)
                    rs.close();
            } catch (Exception e) {
            }

            try {
                if (pstatement != null)
                    pstatement.close();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public PanboxShare getShareForName(String shareName) throws ShareDoesNotExistException, ShareManagerException,
            UnrecoverableKeyException, ShareMetaDataException {
        logger.debug("ShareManager : getShareForName(" + shareName + ")");
        ResultSet rs = null;
        String sql = "select * from " + TABLE_SHARES + " where name=?";
        PreparedStatement pstatement = null;
        try {
            pstatement = connection.prepareStatement(sql);
            pstatement.setString(1, shareName);
            pstatement.setQueryTimeout(30); // set timeout to 30 sec.
            rs = pstatement.executeQuery();

            if (rs.next()) {
                String shareType = rs.getString("type");
                String shareURL = rs.getString("backendURL");
                String shareUUID = rs.getString("uuid");
                try {
                    PanboxShare share = nameTypeUrlToVolumeData(shareName, shareURL, getShareType(shareType),
                            UUID.fromString(shareUUID));
                    return share;
                } catch (FileNotFoundException ex) {
                    throw new ShareInaccessibleException("A share with the specified share name (" + shareName
                            + ") does exist, but is not accessible anymore!", ex);
                }
            } else {
                throw new ShareDoesNotExistException(
                        "A share with the specified share name (" + shareName + ") does not exist.");
            }
        } catch (SQLException | IOException ex) {
            throw new ShareManagerException("Failed to run getShareForName: ", ex);
        } finally {
            try {
                if (rs != null)
                    rs.close();
            } catch (Exception e) {
            }

            try {
                if (pstatement != null)
                    pstatement.close();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public PanboxShare getShareForPath(String sharePath)
            throws UnrecoverableKeyException, ShareManagerException, ShareMetaDataException {
        logger.debug("ShareManager: getShareForPath(" + sharePath + ")");
        ResultSet rs = null;
        String sql = "select * from " + TABLE_SHARES + " where backendURL=?";
        PreparedStatement pstatement = null;
        try {
            pstatement = connection.prepareStatement(sql);
            pstatement.setString(1, sharePath);
            pstatement.setQueryTimeout(30); // set timeout to 30 sec.
            rs = pstatement.executeQuery();

            if (rs.next()) {
                String shareName = rs.getString("name");
                String shareType = rs.getString("type");
                String shareUUID = rs.getString("uuid");
                try {
                    PanboxShare share = nameTypeUrlToVolumeData(shareName, sharePath, getShareType(shareType),
                            UUID.fromString(shareUUID));
                    return share;
                } catch (FileNotFoundException ex) {
                    throw new ShareInaccessibleException("A share with the specified share path (" + sharePath
                            + ") does exist, but is not accessible anymore!", ex);
                }
            } else {
                logger.warn("A share with the specified share path (" + sharePath + ") does not exist.");
                return null;
            }
        } catch (SQLException | IOException ex) {
            throw new ShareManagerException("Failed to run getShareForPath: ", ex);
        } finally {
            try {
                if (rs != null)
                    rs.close();
            } catch (Exception e) {
            }

            try {
                if (pstatement != null)
                    pstatement.close();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public boolean sharePathAvailable(String path)
            throws UnrecoverableKeyException, ShareManagerException, ShareMetaDataException {
        logger.debug("ShareManager : isSharePathAvailable(" + path + ")");

        ResultSet rs = null;
        String sql = "select * from " + TABLE_SHARES + " where backendURL=?";
        PreparedStatement pstatement = null;
        try {
            pstatement = connection.prepareStatement(sql);
            pstatement.setString(1, path);
            pstatement.setQueryTimeout(30); // set timeout to 30 sec.
            rs = pstatement.executeQuery();

            if (rs.next()) {
                return false;
            } else {
                return true;
            }
        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run sharePathAvailable: ", ex);
        } finally {
            try {
                if (rs != null)
                    rs.close();
            } catch (Exception e) {
            }

            try {
                if (pstatement != null)
                    pstatement.close();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public PanboxShare editShare(String shareName, String newShareName, StorageBackendType newShareType,
            String newSharePath, char[] password) throws ShareManagerException, ShareNameAlreadyExistsException,
            SharePathAlreadyExistsException, UnrecoverableKeyException, ShareMetaDataException {
        logger.debug("ShareManager : editShare(" + shareName + "," + newShareName + "," + newShareType + ","
                + newSharePath + ")");
        String sql = "UPDATE " + TABLE_SHARES + " SET name = ?, type = ?, backendURL = ?" + "WHERE name = ?;";
        // String sqluuid = "SELECT FROM " + TABLE_SHARES + " uuid " +
        // "WHERE name = ?;";

        boolean nameChanged = !shareName.equals(newShareName);
        boolean pathChanged = false;

        try {
            pathChanged = !getShareForName(shareName).getPath().equals(newSharePath);
        } catch (ShareDoesNotExistException e) {
            e.printStackTrace();
        }

        if ((!nameChanged || shareNameAvailable(newShareName))
                && (!pathChanged || sharePathAvailable(newSharePath))) {
            PreparedStatement pStatement = null;
            ResultSet rs = null;
            try {
                pStatement = connection.prepareStatement(sql);

                pStatement.setString(1, newShareName);
                pStatement.setString(2, newShareType.toString());
                pStatement.setString(3, newSharePath);
                pStatement.setString(4, shareName);
                pStatement.executeUpdate();
                pStatement.close();

                return getShareForName(newShareName);
            } catch (SQLException | ShareDoesNotExistException ex) {
                // FIXME: consider reverting DB changes if something went wrong
                throw new ShareManagerException("Failed to run SQL command editShare: ", ex);
            } finally {
                try {
                    if (rs != null)
                        rs.close();
                } catch (Exception e) {
                }

                try {
                    if (pStatement != null)
                        pStatement.close();
                } catch (Exception e) {
                }
            }
        } else {
            if (nameChanged && !shareNameAvailable(newShareName)) {
                throw new ShareNameAlreadyExistsException(
                        "A share with the name " + newShareName + " does already exist.");
            } else {
                throw new SharePathAlreadyExistsException(
                        "A share with the url " + newSharePath + " does already exist.");
            }
        }

    }

    @Override
    public void removeShareFromDB(String shareName) throws ShareManagerException {
        logger.debug("ShareManager : removeShareFromDB(" + shareName + ")");
        PreparedStatement pstatement = null;
        String sql = "DELETE from " + TABLE_SHARES + " WHERE name=?;";

        try {
            pstatement = connection.prepareStatement(sql);

            pstatement.setString(1, shareName);
            pstatement.setQueryTimeout(30); // set timeout to 30 sec.
            pstatement.executeUpdate();

        } catch (SQLException ex) {
            throw new ShareManagerException("Failed to run SQL command removeShareFromDB: ", ex);
        } finally {
            try {
                if (pstatement != null)
                    pstatement.close();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public void removeShare(String shareName, String sharePath, StorageBackendType type)
            throws ShareManagerException {
        logger.debug("ShareManager : removeShare(" + shareName + ")");

        try {
            removeShareFromDB(shareName);
            service.removeShare(
                    paramsFactory.createVolumeParams().setShareName(shareName).setPath(sharePath).setType(type));

        } catch (RemoteException ex) {
            throw new ShareManagerException("Failed to run command removeShare: ", ex);
        }
    }

    @Override
    public PanboxShare addDevicePermission(PanboxShare share, String deviceName, char[] password)
            throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException,
            ShareMetaDataException {

        try {
            VolumeParams p = paramsFactory.createVolumeParams().setKeys(identity, password)
                    .setUserAlias(identity.getEmail()).setDeviceAlias(deviceName)
                    .setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName)).setShareName(share.getName())
                    .setPath(share.getPath()).setType(share.getType());
            PanboxShare pbShare = service.addDevice(p);
            return pbShare;
        } catch (IllegalArgumentException | RemoteException e) {
            throw new ShareManagerException("Could not add Device to ShareMetadata", e);
        }
    }

    @Override
    public PanboxShare removeDevicePermission(PanboxShare share, String deviceName, char[] password)
            throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException,
            ShareMetaDataException {

        try {
            VolumeParams p = paramsFactory.createVolumeParams().setUserAlias(identity.getEmail())
                    .setPublicSignatureKey(identity.getPublicKeySign())
                    .setPrivateSignatureKey(identity.getPrivateKeySign(password)).setDeviceAlias(deviceName)
                    .setShareName(share.getName()).setPath(share.getPath()).setType(share.getType());

            PanboxShare pbShare = service.removeDevice(p);
            return pbShare;
        } catch (IllegalArgumentException | RemoteException e) {
            throw new ShareManagerException("Could not add Device to ShareMetadata", e);
        }

    }

    @Override
    public PanboxShare addContactPermission(PanboxShare share, String email, char[] password)
            throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException,
            ShareMetaDataException {
        PanboxContact contact = identity.getAddressbook().contactExists(email);
        if (contact == null) {
            throw new ShareManagerException("Contact " + email + " is not in addressbook.");
        }
        try {

            VolumeParams p = paramsFactory.createVolumeParams().setKeys(identity, password)
                    .setOwnerAlias(identity.getEmail()).setOtherSignatureKey(contact.getPublicKeySign())
                    .setOtherEncryptionKey(contact.getPublicKeyEnc()).setUserAlias(contact.getEmail())
                    .setShareName(share.getName()).setPath(share.getPath()).setType(share.getType());

            // Add Invitation fingerPrint so invited user can detect
            // invitation
            File invitationFolder = new File(
                    share.getPath() + File.separator + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY
                            + File.separator + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY);
            if (invitationFolder.isFile()) {
                // invitationFolder is not a folder
                throw new RuntimeException("invitation folder is a file, not a folder!");
            }
            if (!invitationFolder.exists()) {
                invitationFolder.mkdir();
            }
            String fingerPrint = DigestUtils.md5Hex(contact.getCertSign().getPublicKey().getEncoded());
            File fpFile = new File(invitationFolder.getAbsolutePath() + File.separator + fingerPrint);
            fpFile.createNewFile();

            PanboxShare pbShare = service.inviteUser(p);
            pbShare.generatePermissionsModel(identity);
            return pbShare;
        } catch (IOException e) {
            throw new ShareManagerException("Could not invite Contact to ShareMetadata", e);
        }
    }

    public void setIdentity(AbstractIdentity identity) {
        this.identity = identity;
    }

    @Override
    public PanboxShare addNewShare(PanboxShare share, char[] password)
            throws ShareManagerException, ShareNameAlreadyExistsException, SharePathAlreadyExistsException,
            UnrecoverableKeyException, ShareMetaDataException {

        logger.debug("ShareManager : addNewShare(" + share.getName() + "," + share.getType() + "," + share.getPath()
                + ")");
        boolean shareDirCreated = false;

        if (shareNameAvailable(share.getName()) && sharePathAvailable(share.getPath())) {

            File shareFolder = new File(share.getPath());

            String sql = "insert into " + TABLE_SHARES + " VALUES (NULL, (?), (?), (?), (?))";

            PreparedStatement pStatement = null;
            try {
                if (!shareFolder.exists()) {
                    shareDirCreated = shareFolder.mkdirs();
                }

                PanboxShare resultShare = nameTypeUrlToVolumeData(share, password);

                pStatement = connection.prepareStatement(sql);
                pStatement.setString(1, resultShare.getUuid().toString());
                pStatement.setString(2, share.getName());
                pStatement.setString(3, share.getType().toString());
                pStatement.setString(4, share.getPath());
                pStatement.executeUpdate();

                return resultShare;
            } catch (SQLException | NoSuchAlgorithmException | IOException e) {
                logger.error("ShareManagerImpl : addNewShare : Already logging exception: ", e);

                try {
                    // in case of error we need to remove the share again
                    removeShare(share.getName(), share.getPath(), share.getType());

                    if (shareFolder.exists() && shareDirCreated) {
                        FileUtils.deleteDirectoryTree(shareFolder);
                    }
                } catch (Exception e1) {
                }

                throw new ShareManagerException("Failed to run addNewShare: ", e);

            } catch (ShareMetaDataException | UnrecoverableKeyException | UnknownOwnerException e) {
                logger.error("ShareManagerImpl : addNewShare : Already logging exception: ", e);

                try {
                    // in case of error we need to remove the share again
                    removeShare(share.getName(), share.getPath(), share.getType());

                    if (shareFolder.exists() && shareDirCreated) {
                        FileUtils.deleteDirectoryTree(shareFolder);
                    }
                } catch (Exception e1) {
                }

                throw e;
            } finally {
                try {

                    if (pStatement != null) {
                        pStatement.close();
                    }

                } catch (Exception e) {
                }
            }
        } else {
            if (!shareNameAvailable(share.getName())) {
                throw new ShareNameAlreadyExistsException(
                        "A share with the name " + share.getName() + " does already exist.");
            } else {
                throw new SharePathAlreadyExistsException(
                        "A share with the url " + share.getPath() + " does already exist.");
            }
        }
    }

    @Override
    public Collection<PanboxContact> checkShareDeviceListIntegrity(PanboxShare share) {
        Exception e = null;
        LinkedList<PanboxContact> corruptedDLContacts = new LinkedList<PanboxContact>();
        if ((e = share.getException()) != null) {
            logger.warn("One or more device lists in share " + share.getName() + " seem to be corrupt... ");
            if (e instanceof DeviceListException) {
                DeviceListException ex = (DeviceListException) e;
                Collection<PublicKey> coll = ex.getUserKeys();
                for (Iterator<PublicKey> it = coll.iterator(); it.hasNext();) {
                    PublicKey publicKey = (PublicKey) it.next();
                    logger.warn("Device list " + Utils.getPubKeyFingerprint(publicKey) + ".db in share "
                            + share.getName() + " seem to be corrupt.");
                    PanboxContact c = identity.getAddressbook().getContactBySignaturePubKey(publicKey);
                    corruptedDLContacts.add(c);
                }
            }
        }
        return corruptedDLContacts;
    }

    @Override
    public PanboxShare resetShareInvitation(PanboxShare share, String email, char[] password)
            throws UnrecoverableKeyException, ShareDoesNotExistException, ShareManagerException,
            ShareMetaDataException {
        // TODO: check if corrupt device list file has to be removed physically
        // first
        return addContactPermission(share, email, password);
    }

    private PanboxShare nameTypeUrlToVolumeData(String shareName, String sharePath, StorageBackendType type,
            UUID uuid, char[] password)
            throws IOException, ShareManagerException, ShareMetaDataException, UnrecoverableKeyException {

        if (!new File(sharePath).exists()) {
            throw new ShareManagerException(
                    "The specified share path (" + sharePath + ") does not exist or is inaccessible!");
        }

        String metaDataDir = sharePath + File.separator + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY
                + File.separator;
        File metaDataFile = new File(metaDataDir);
        File ownerFile = new File(metaDataDir + PanboxConstants.PANBOX_SHARE_OWNER_FILE);

        String deviceName = Settings.getInstance().getDeviceName();

        PanboxShare pbShare = null;

        // create new share
        if (!metaDataFile.exists()) {
            if (password != null) {
                pbShare = createNewShare(shareName, sharePath, type, password, metaDataFile, ownerFile, deviceName);
            } else {
                // empty password field indicates we were trying to load a share
                // from the DB, but the metadata file was missing
                throw new ShareInaccessibleException(
                        "Metadatafile for share " + shareName + " could not be found!");
            }
        } else {

            if (!ownerFile.exists() || !ownerFile.canRead()) {
                throw new ShareManagerException("Can't access owner file at " + metaDataFile.getAbsolutePath());
            }

            MessageDigest md = getMessageDigestForOwnerFile();
            byte[] ownerFp = getOwnerFpFromMessageDigest(ownerFile, md);
            byte[] me = md.digest(identity.getPublicKeySign().getEncoded());
            md.reset();

            if (Settings.getInstance().isProtectedDeviceKey()) {
                password = PasswordEnterDialog.invoke(PasswordEnterDialog.PermissionType.SHARE);
            }

            VolumeParams p = paramsFactory.createVolumeParams().setPublicSignatureKey(identity.getPublicKeySign())
                    .setDeviceAlias(deviceName).setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName))
                    .setShareName(shareName).setPath(sharePath).setType(type);

            if (password != null) {
                // password was entered
                try {
                    p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName));
                } catch (UnrecoverableKeyException e) {
                    p = p.setPrivateDeviceKey(
                            identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                }
            } else {
                // password was not entered! Try default one!
                try {
                    p = p.setPrivateDeviceKey(
                            identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                    // Looks like the configuration of deviceKeyProtection
                    // has been changed! We need to set this option to true
                    // for next startup!
                    Settings.getInstance().setProtectedDeviceKey(false);
                } catch (UnrecoverableKeyException e) {
                    logger.warn(
                            "Could not get device key with standard password, but standard password was configured.");

                    password = PasswordEnterDialog.invoke(PasswordEnterDialog.PermissionType.SHARE);
                    try {
                        p = p.setPrivateDeviceKey(
                                identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                        // Looks like the configuration of deviceKeyProtection
                        // has been changed! We need to set this option to true
                        // for next startup!
                        Settings.getInstance().setProtectedDeviceKey(true);
                    } catch (UnrecoverableKeyException ex) {
                        logger.error("Entered Password was wrong!");
                        throw ex;
                    }
                }
            }

            if (Arrays.equals(me, ownerFp)) {
                // I am the owner
                try {
                    logger.debug("I am the owner of this preinitialized share, loading...");
                    pbShare = service.loadShare(
                            p.setOwnerAlias(identity.getEmail()).setOwnerSignatureKey(identity.getPublicKeySign()));
                } catch (ShareMetaDataException e) {
                    boolean success = false;
                    if (e.getCause() instanceof DeviceKeyException) {
                        // the sharemetadata is ok, but the user's current
                        // device has no keys, possibly because the metadata
                        // state has been reverted due to a file conflict. try
                        // if re-adding the device works, otherwise show error
                        try {
                            logger.warn(
                                    "Detected missing device key. This may be because of the metadata state having been reverted due to a file conflict. Will try re-adding the device ...",
                                    e);
                            if (password == null) {
                                password = PasswordEnterDialog.invoke(PermissionType.SHARE);
                            }
                            VolumeParams ptmp = paramsFactory.createVolumeParams().setKeys(identity, password)
                                    .setUserAlias(identity.getEmail()).setDeviceAlias(deviceName)
                                    .setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName))
                                    .setShareName(shareName).setPath(sharePath).setType(type);
                            pbShare = service.addDevice(ptmp);
                            logger.warn("Successfully re-added device key. Trying to re-run loadShare...");
                            pbShare = service.loadShare(p.setOwnerAlias(identity.getEmail())
                                    .setOwnerSignatureKey(identity.getPublicKeySign()));
                            success = true;
                        } catch (Exception e2) {
                            logger.error("Re-addeding device key failed.", e2);
                        }
                    }
                    // else if (e.getCause() instanceof DeviceListException) {
                    // try {
                    // logger.warn(
                    // "Detected corrupt device list. Will try to reset device list by re-inviting user...",
                    // e);
                    // if (password == null) {
                    // password = PasswordEnterDialog
                    // .invoke(PermissionType.SHARE);
                    // }
                    //
                    // PublicKey pk = ((DeviceListException) e.getCause())
                    // .getUserKey();
                    // PanboxContact c = identity.getAddressbook()
                    // .getContactBySignaturePubKey(pk);
                    //
                    // if (c != null) {
                    // logger.warn("DeviceListException associated caused by contact list of contact \""
                    // + c.getEmail() + "\" ");
                    //
                    //
                    //
                    // VolumeParams pinv = paramsFactory.createVolumeParams()
                    // .setKeys(identity, password)
                    // .setOwnerAlias(identity.getEmail())
                    // .setOtherSignatureKey(c.getPublicKeySign())
                    // .setOtherEncryptionKey(c.getPublicKeyEnc())
                    // .setUserAlias(c.getEmail())
                    // .setShareName(shareName).setPath(sharePath)
                    // .setType(type);
                    //
                    // // Add Invitation fingerPrint so invited user can detect
                    // // invitation
                    // File invitationFolder = new File(sharePath +
                    // File.separator
                    // + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY
                    // + File.separator
                    // + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY);
                    // if (invitationFolder.isFile()) {
                    // // invitationFolder is not a folder
                    // throw new RuntimeException(
                    // "invitation folder is a file, not a folder!");
                    // }
                    // if (!invitationFolder.exists()) {
                    // invitationFolder.mkdir();
                    // }
                    // String fingerPrint = DigestUtils.md5Hex(c.getCertSign()
                    // .getPublicKey().getEncoded());
                    // File fpFile = new File(invitationFolder.getAbsolutePath()
                    // + File.separator + fingerPrint);
                    // fpFile.createNewFile();
                    //
                    // pbShare = service.inviteUser(p);
                    // pbShare.generatePermissionsModel(identity);
                    // pbShare = pbShare = service.loadShare(p.setOwnerAlias(
                    // identity.getEmail()).setOwnerSignatureKey(
                    // identity.getPublicKeySign()));
                    //
                    // success = true;
                    // } else {
                    // logger.warn("DeviceListException could not be attributed to any existing contact. Publiy Key fingerprint: \""
                    // + Utils.getPubKeyFingerprint(pk)
                    // + "\" ");
                    // }
                    //
                    // } catch (Exception e2) {
                    // logger.error("Re-inviting user failed.", e2);
                    // }
                    // }
                    if (!success) {
                        logger.error("Unable to load preinitialized share!", e);
                        throw e;
                    }
                }
            } else {
                // I am not the owner
                IPerson owner = getOwnerForShare(md, ownerFp, p);
                if (owner != null) {
                    // Found the owner, could be previously initialized share or
                    // a new share that i'm invited to

                    // Is there an invitation for me?
                    File invitationFolder = new File(
                            metaDataDir + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY);
                    String fingerPrint = DigestUtils.md5Hex(identity.getPublicKeySign().getEncoded());
                    File fpFile = new File(invitationFolder.getAbsolutePath() + File.separator + fingerPrint);
                    if (invitationFolder.isDirectory() && fpFile.isFile()) {
                        // I have been invited to this share
                        // accept invitation and delete invitational
                        // fingerprint
                        pbShare = acceptInvitation(password, deviceName, p);

                        logger.debug("Accepted invitation, deleting invitational fingerprint...");
                        if (!fpFile.delete()) {
                            logger.warn("Could not delete invitational fingerprint file from invitations folder: "
                                    + fpFile.getAbsolutePath());
                            logger.warn(
                                    "If this share is added again it will try to accept an invitation that has already been accepted.");
                        }
                    } else {
                        // There is no invitation for me lying around, so
                        // i'll just assume that this is a previously
                        // existing and correctly initialized share
                        logger.debug("Found owner, assuming preinitialized Share...");
                        try {
                            pbShare = service.loadShare(p);
                        } catch (ShareMetaDataException e) {
                            boolean success = false;
                            if (e.getCause() instanceof DeviceKeyException) {
                                // the sharemetadata is ok, but the user's
                                // current device has no keys, possibly because
                                // the metadata state has been reverted due to a
                                // file conflict. try if re-adding the device
                                // works, otherwise show error
                                try {
                                    logger.warn(
                                            "Detected missing device key. This may be because of the metadata state having been reverted due to a file conflict. Will try re-adding the device ...",
                                            e);
                                    if (password == null)
                                        password = PasswordEnterDialog.invoke(PermissionType.SHARE);
                                    VolumeParams ptmp = paramsFactory.createVolumeParams()
                                            .setKeys(identity, password).setUserAlias(identity.getEmail())
                                            .setDeviceAlias(deviceName)
                                            .setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName))
                                            .setShareName(shareName).setPath(sharePath).setType(type);
                                    pbShare = service.addDevice(ptmp);
                                    logger.warn("Successfully re-added device key. Trying to re-run loadShare...");
                                    pbShare = service.loadShare(p);
                                    success = true;
                                } catch (Exception e2) {
                                    logger.error("Re-addeding device key failed.", e);
                                }
                            }
                            if (!success) {
                                logger.error("Unable to load preinitialized share!", e);
                                throw e;
                            }
                        }
                    }
                } else {
                    // The Owner Fingerprint in the share did not match
                    // any of my contacts or myself. Either the real owner is
                    // not in my addressbook, or somebody manipulated the
                    // fingerprint in the share

                    // TODO: if somebody messed with the owner fingerprint
                    // in the share, i could just try to load the share with all
                    // my available contacts and myself as owner and see which
                    // initialization actually runs through

                    throw new UnknownOwnerException("The Owner Fingerprint in the share did not match "
                            + "any of my contacts or myself. Either the real owner is "
                            + "not in my addressbook, or somebody manipulated the " + "fingerprint in the share");
                }
            }

            // share was loaded successfully - now, we still need to check if
            // the current users device list has been marked as corrupted.
            // in this case, accessing the share will fail as no keys will be
            // available. Thus, an exception needs to be thrown at this point
            Exception e = null;
            if ((e = pbShare.getException()) != null) {
                logger.warn("One or more device lists in share " + pbShare.getName() + " seem to be corrupt... ");
                if (e instanceof DeviceListException) {
                    DeviceListException ex = (DeviceListException) e;
                    Collection<PublicKey> coll = ex.getUserKeys();
                    for (Iterator<PublicKey> it = coll.iterator(); it.hasNext();) {
                        PublicKey publicKey = (PublicKey) it.next();
                        if (Utils.keysEqual(identity.getPublicKeySign(), publicKey)) {
                            // exception was thrown because our own devicelist
                            // was corrupt. this means no sharekey or
                            // obfuscationkeys will be available.
                            logger.fatal("Own device list " + Utils.getPubKeyFingerprint(publicKey)
                                    + ".db in share " + pbShare.getName()
                                    + " seems to be corrupt. Share will not be available.");
                            throw new ShareMetaDataException("Could not verify signature of device list", ex);
                        }
                        logger.warn("Device list " + Utils.getPubKeyFingerprint(publicKey) + ".db in share "
                                + pbShare.getName() + " seems to be corrupt.");
                    }
                }
            }
        }
        pbShare.generatePermissionsModel(identity);
        return pbShare;
    }

    private PanboxShare nameTypeUrlToVolumeData(String shareName, String sharePath, StorageBackendType type,
            UUID uuid)
            throws IOException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException {
        return nameTypeUrlToVolumeData(shareName, sharePath, type, uuid, null);
    }

    private PanboxShare nameTypeUrlToVolumeData(PanboxShare share, char[] password) throws NoSuchAlgorithmException,
            IOException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException {
        return nameTypeUrlToVolumeData(share.getName(), share.getPath(), share.getType(), share.getUuid(),
                password);
    }

    private PanboxShare acceptInvitation(char[] password, String deviceName, VolumeParams p)
            throws UnrecoverableKeyException, ShareMetaDataException, RemoteException, FileNotFoundException {
        PanboxShare pbShare;
        logger.debug("Detected invitation, accepting it...");

        if (password == null) {
            password = PasswordEnterDialog.invoke(PasswordEnterDialog.PermissionType.SHARE);
        }

        p.setKeys(identity, password).setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName))
                .setUserAlias(identity.getEmail());

        try {
            if (Settings.getInstance().isProtectedDeviceKey()) {
                p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName));
            } else {
                p = p.setPrivateDeviceKey(
                        identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
            }
        } catch (UnrecoverableKeyException e) {
            if (Settings.getInstance().isProtectedDeviceKey()) {
                p.setPrivateDeviceKey(
                        identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                Settings.getInstance().setProtectedDeviceKey(false);
                logger.info("ShareManager : createNewShare : Updated device key protection (disabled)!");
            } else {
                p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName));
                Settings.getInstance().setProtectedDeviceKey(true);
                logger.info("ShareManager : createNewShare : Updated device key protection (enabled)!");
            }
        }

        pbShare = service.acceptInviation(p);
        return pbShare;
    }

    private IPerson getOwnerForShare(MessageDigest md, byte[] ownerFp, VolumeParams p) {
        IPerson owner = null;
        for (PanboxContact contact : identity.getAddressbook().getContacts()) {
            byte[] c = md.digest(contact.getCertSign().getPublicKey().getEncoded());
            md.reset();
            if (Arrays.equals(ownerFp, c)) {
                owner = contact;
                p.setOwnerAlias(owner.getEmail()).setOwnerSignatureKey(owner.getPublicKeySign());
                break;
            }
        }
        return owner;
    }

    private byte[] getOwnerFpFromMessageDigest(File ownerFile, MessageDigest md) throws IOException {
        byte[] ownerFp = new byte[md.getDigestLength()];
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(ownerFile))) {
            bis.read(ownerFp);
        }
        return ownerFp;
    }

    private MessageDigest getMessageDigestForOwnerFile() throws ShareManagerException, IOException {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance(KeyConstants.PUBKEY_FINGERPRINT_DIGEST, KeyConstants.PROV_BC);
        } catch (NoSuchProviderException | NoSuchAlgorithmException e1) {
            throw new ShareManagerException("Error initializing message-digest!", e1);
        }
        return md;
    }

    private PanboxShare createNewShare(String shareName, String sharePath, StorageBackendType type, char[] password,
            File metaDataFile, File ownerFile, String deviceName)
            throws IOException, UnrecoverableKeyException, ShareMetaDataException, ShareManagerException {
        PanboxShare pbShare;// Creating new Share

        if (Settings.getInstance().isSlave()) {
            // Slave clients are not allowed to create a new share!
            throw new CreateShareNotAllowedException("Slave is not allowed to create a new share.");
        }

        try {
            logger.debug("Creating new Share...");
            VolumeParams p = paramsFactory.createVolumeParams().setOwnerAlias(identity.getEmail())
                    .setDeviceAlias(deviceName).setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName))
                    .setKeys(identity, password).setPath(sharePath).setType(type).setShareName(shareName);

            try {
                if (Settings.getInstance().isProtectedDeviceKey()) {
                    p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName));
                } else {
                    p = p.setPrivateDeviceKey(
                            identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                }
            } catch (UnrecoverableKeyException e) {
                if (Settings.getInstance().isProtectedDeviceKey()) {
                    p.setPrivateDeviceKey(
                            identity.getPrivateKeyForDevice(KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName));
                    Settings.getInstance().setProtectedDeviceKey(false);
                    logger.info("ShareManager : createNewShare : Updated device key protection (disabled)!");
                } else {
                    p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName));
                    Settings.getInstance().setProtectedDeviceKey(true);
                    logger.info("ShareManager : createNewShare : Updated device key protection (enabled)!");
                }
            }

            metaDataFile.mkdirs();

            pbShare = service.createShare(p);

            // write uuid
            Files.createFile(Paths.get(metaDataFile.getAbsolutePath() + File.separator
                    + PanboxConstants.PANBOX_SHARE_UUID_PREFIX + pbShare.getUuid()));

            // Owner ID is the SHA256-Hash of the PublicSignKey
            MessageDigest md = MessageDigest.getInstance(KeyConstants.PUBKEY_FINGERPRINT_DIGEST,
                    KeyConstants.PROV_BC);
            try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(ownerFile))) {
                bos.write(md.digest(identity.getPublicKeySign().getEncoded()));
                bos.flush();
            }
        } catch (IllegalArgumentException | NoSuchProviderException | NoSuchAlgorithmException e) {
            throw new ShareManagerException("Could not create share!", e);
        }
        return pbShare;
    }

    public VolumeParamsFactory getParamsFactory() {
        return paramsFactory;
    }

    public String getOnlineFilename(PanboxShare share, String path) throws ShareManagerException {
        VolumeParams p = paramsFactory.createVolumeParams().setShareName(share.getName()).setPath(share.getPath());
        try {
            return service.getOnlineFilename(p, path);
        } catch (RemoteException | FileNotFoundException | ObfuscationException e) {
            throw new ShareManagerException("Failed to resolve online filename for path " + path, e);
        }
    }

    @Override
    public PanboxShare reloadShareMetadata(PanboxShare share) throws ShareManagerException {
        VolumeParams p = paramsFactory.createVolumeParams().setPublicSignatureKey(identity.getPublicKeySign())
                .setShareName(share.getName()).setPath(share.getPath()).setType(share.getType());
        try {
            return service.reloadShareMetaData(p);
        } catch (RemoteException | FileNotFoundException | ShareMetaDataException e) {
            throw new ShareManagerException("Failed to reload share metadata for share " + share.getName(), e);
        }
    }
}