Package org.syncany.cli

Source Code of org.syncany.cli.InitCommand

/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* 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/>.
*/
package org.syncany.cli;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import org.syncany.chunk.Chunker;
import org.syncany.chunk.CipherTransformer;
import org.syncany.chunk.FixedChunker;
import org.syncany.chunk.GzipTransformer;
import org.syncany.chunk.MultiChunker;
import org.syncany.chunk.ZipMultiChunker;
import org.syncany.config.to.ConfigTO;
import org.syncany.config.to.RepoTO;
import org.syncany.config.to.RepoTO.ChunkerTO;
import org.syncany.config.to.RepoTO.MultiChunkerTO;
import org.syncany.config.to.RepoTO.TransformerTO;
import org.syncany.crypto.CipherSpec;
import org.syncany.crypto.CipherSpecs;
import org.syncany.crypto.CipherUtil;
import org.syncany.operations.OperationResult;
import org.syncany.operations.init.GenlinkOperationOptions;
import org.syncany.operations.init.InitOperationOptions;
import org.syncany.operations.init.InitOperationResult;
import org.syncany.operations.init.InitOperationResult.InitResultCode;
import org.syncany.plugins.transfer.StorageTestResult;
import org.syncany.plugins.transfer.TransferSettings;
import org.syncany.util.StringUtil;
import org.syncany.util.StringUtil.StringJoinListener;

import static java.util.Arrays.asList;

public class InitCommand extends AbstractInitCommand {
  public static final int REPO_ID_LENGTH = 32;

  private InitOperationOptions operationOptions;

  @Override
  public CommandScope getRequiredCommandScope() {
    return CommandScope.UNINITIALIZED_LOCALDIR;
  }

  @Override
  public boolean canExecuteInDaemonScope() {
    return false;
  }

  @Override
  public int execute(String[] operationArgs) throws Exception {
    boolean retryNeeded = true;
    boolean performOperation = true;

    operationOptions = parseOptions(operationArgs);

    while (retryNeeded && performOperation) {
      InitOperationResult operationResult = client.init(operationOptions, this);
      printResults(operationResult);

      retryNeeded = operationResult.getResultCode() != InitResultCode.OK;

      if (retryNeeded) {
        performOperation = isInteractive && askRetryConnection();

        if (performOperation) {
          updateTransferSettings(operationOptions.getConfigTO().getTransferSettings());
        }
      }
    }

    return 0;
  }

  @Override
  public InitOperationOptions parseOptions(String[] operationArguments) throws Exception {
    InitOperationOptions operationOptions = new InitOperationOptions();

    OptionParser parser = new OptionParser();
    OptionSpec<Void> optionCreateTargetPath = parser.acceptsAll(asList("t", "create-target"));
    OptionSpec<Void> optionAdvanced = parser.acceptsAll(asList("a", "advanced"));
    OptionSpec<Void> optionNoCompression = parser.acceptsAll(asList("G", "no-compression"));
    OptionSpec<Void> optionNoEncryption = parser.acceptsAll(asList("E", "no-encryption"));
    OptionSpec<String> optionPlugin = parser.acceptsAll(asList("P", "plugin")).withRequiredArg();
    OptionSpec<String> optionPluginOpts = parser.acceptsAll(asList("o", "plugin-option")).withRequiredArg();
    OptionSpec<Void> optionAddDaemon = parser.acceptsAll(asList("n", "add-daemon"));
    OptionSpec<Void> optionShortUrl = parser.acceptsAll(asList("s", "short"));   
    OptionSpec<String> optionPassword = parser.acceptsAll(asList("password")).withRequiredArg()

    OptionSet options = parser.parse(operationArguments);

    // Set interactivity mode 
    isInteractive = !options.has(optionPlugin);
     
    // Ask or set transfer settings
    TransferSettings transferSettings = createTransferSettingsFromOptions(options, optionPlugin, optionPluginOpts);

    // Some misc settings
    boolean createTargetPath = options.has(optionCreateTargetPath);
    boolean advancedModeEnabled = options.has(optionAdvanced);
    boolean encryptionEnabled = !options.has(optionNoEncryption);
    boolean compressionEnabled = !options.has(optionNoCompression);
   
    // Cipher specs: --no-encryption, --advanced
    List<CipherSpec> cipherSpecs = getCipherSpecs(encryptionEnabled, advancedModeEnabled);

    // Chunkers (not configurable)
    ChunkerTO chunkerTO = getDefaultChunkerTO();
    MultiChunkerTO multiChunkerTO = getDefaultMultiChunkerTO();

    // Compression: --no-compression
    List<TransformerTO> transformersTO = getTransformersTO(compressionEnabled, cipherSpecs);

    // Genlink options: --short
    GenlinkOperationOptions genlinkOptions = new GenlinkOperationOptions();
    genlinkOptions.setShortUrl(options.has(optionShortUrl));
       
    // Set repo password
    String password = validateAndGetPassword(options, optionNoEncryption, optionPassword);
    operationOptions.setPassword(password);
   
    // Create configTO and repoTO
    ConfigTO configTO = createConfigTO(transferSettings);
    RepoTO repoTO = createRepoTO(chunkerTO, multiChunkerTO, transformersTO);

    operationOptions.setLocalDir(localDir);
    operationOptions.setConfigTO(configTO);
    operationOptions.setRepoTO(repoTO);

    operationOptions.setCreateTarget(createTargetPath);
    operationOptions.setEncryptionEnabled(encryptionEnabled);
    operationOptions.setCipherSpecs(cipherSpecs);   
    operationOptions.setDaemon(options.has(optionAddDaemon));
    operationOptions.setGenlinkOptions(genlinkOptions);
   
    return operationOptions;
  }
 
  private String validateAndGetPassword(OptionSet options, OptionSpec<Void> optionNoEncryption, OptionSpec<String> optionPassword) {   
    if (!isInteractive) {
      if (options.has(optionPassword) && options.has(optionNoEncryption)) {
        throw new IllegalArgumentException("Cannot provide --password and --no-encryption. Conflicting options.");
      }
      else if (!options.has(optionPassword) && !options.has(optionNoEncryption)) {
        throw new IllegalArgumentException("Non-interactive must either provide --no-encryption or --password.");
      }
      else if (options.has(optionPassword) && !options.has(optionNoEncryption)) {
        String password = options.valueOf(optionPassword);
       
        if (password.length() < PASSWORD_MIN_LENGTH) {
          throw new IllegalArgumentException("This password is not allowed (too short, min. " + PASSWORD_MIN_LENGTH + " chars)");
        }
       
        return options.valueOf(optionPassword);
      }     
      else {
        return null; // No encryption, no password.
      }
    } 
    else {
      return null; // Will be set in callback!
    }
  }

  @Override
  public void printResults(OperationResult operationResult) {
    InitOperationResult concreteOperationResult = (InitOperationResult) operationResult;

    if (concreteOperationResult.getResultCode() == InitResultCode.OK) {
      out.println();
      out.println("Repository created, and local folder initialized. To share the same repository");
      out.println("with others, you can share this link:");

      printLink(concreteOperationResult.getGenLinkResult(), false);

      if (concreteOperationResult.isAddedToDaemon()) {
        out.println("To automatically sync this folder, simply restart the daemon with 'sy daemon restart'.");
        out.println();
      }
    }
    else if (concreteOperationResult.getResultCode() == InitResultCode.NOK_TEST_FAILED) {
      StorageTestResult testResult = concreteOperationResult.getTestResult();
      out.println();

      if (testResult.isRepoFileExists()) {
        out.println("ERROR: Repository cannot be initialized, because it already exists ('syncany' file");
        out.println("       exists). Are you sure that you want to create a new repo?  Use 'sy connect'");
        out.println("       to connect to an existing repository.");
      }
      else if (!testResult.isTargetCanConnect()) {
        out.println("ERROR: Repository cannot be initialized, because the connection to the storage backend failed.");
        out.println("       Possible reasons for this could be connectivity issues (are you connect to the Internet?),");
        out.println("       or invalid user credentials (are username/password valid?).");
      }
      else if (!testResult.isTargetExists()) {
        if (!operationOptions.isCreateTarget()) {
          out.println("ERROR: Repository cannot be initialized, because the target does not exist and");
          out.println("       the --create-target/-t option has not been enabled. Either create the target");
          out.println("       manually or retry with the --create-target/-t option.");
        }
        else {
          out.println("ERROR: Repository cannot be initialized, because the target does not exist and");
          out.println("       it cannot be created. Please check your permissions or create the target manually.");
        }
      }
      else if (!testResult.isTargetCanWrite()) {
        out.println("ERROR: Repository cannot be initialized, because the target is not writable. This is probably");
        out.println("       a permission issue (does the user have write permissions to the target?).");
      }
      else {
        out.println("ERROR: Repository cannot be initialized.");
      }

      out.println();
      printTestResult(testResult);
    }
    else {
      out.println();
      out.println("ERROR: Cannot connect to repository. Unknown error code: " + concreteOperationResult.getResultCode());
      out.println();
    }
  }

  private List<TransformerTO> getTransformersTO(boolean gzipEnabled, List<CipherSpec> cipherSpecs) {
    List<TransformerTO> transformersTO = new ArrayList<TransformerTO>();

    if (gzipEnabled) {
      transformersTO.add(getGzipTransformerTO());
    }

    if (cipherSpecs.size() > 0) {
      TransformerTO cipherTransformerTO = getCipherTransformerTO(cipherSpecs);
      transformersTO.add(cipherTransformerTO);
    }

    return transformersTO;
  }

  private List<CipherSpec> getCipherSpecs(boolean encryptionEnabled, boolean advancedModeEnabled) throws Exception {
    List<CipherSpec> cipherSpecs = new ArrayList<CipherSpec>();

    if (encryptionEnabled) {
      if (advancedModeEnabled) {
        cipherSpecs = askCipherSpecs();
      }
      else { // Default
        cipherSpecs = CipherSpecs.getDefaultCipherSpecs();
      }
    }

    return cipherSpecs;
  }

  private List<CipherSpec> askCipherSpecs() throws Exception {
    List<CipherSpec> cipherSpecs = new ArrayList<CipherSpec>();
    Map<Integer, CipherSpec> availableCipherSpecs = CipherSpecs.getAvailableCipherSpecs();

    out.println();
    out.println("Please choose your encryption settings. If you're paranoid,");
    out.println("you can choose multiple cipher suites by separating with a comma.");
    out.println();
    out.println("Options:");

    for (CipherSpec cipherSuite : availableCipherSpecs.values()) {
      out.println(" [" + cipherSuite.getId() + "] " + cipherSuite);
    }

    out.println();

    boolean continueLoop = true;
    boolean unlimitedStrengthNeeded = false;

    while (continueLoop) {
      String commaSeparatedCipherIdStr = console.readLine("Cipher(s): ");
      String[] cipherSpecIdStrs = commaSeparatedCipherIdStr.split(",");

      // Choose cipher
      try {
        // Add cipher suites
        for (String cipherSpecIdStr : cipherSpecIdStrs) {
          Integer cipherSpecId = Integer.parseInt(cipherSpecIdStr);
          CipherSpec cipherSpec = availableCipherSpecs.get(cipherSpecId);

          if (cipherSpec == null) {
            throw new Exception();
          }

          if (cipherSpec.needsUnlimitedStrength()) {
            unlimitedStrengthNeeded = true;
          }

          cipherSpecs.add(cipherSpec);
        }

        // Unlimited strength
        if (unlimitedStrengthNeeded) {
          out.println();
          out.println("At least one of the chosen ciphers or key sizes might");
          out.println("not be allowed in your country.");
          out.println();

          String yesno = console.readLine("Are you sure you want to use it (y/n)? ");

          if (yesno.toLowerCase().startsWith("y")) {
            try {
              CipherUtil.enableUnlimitedStrength();
            }
            catch (Exception e) {
              throw new Exception(
                  "Unable to enable unlimited crypto. Check out: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html");
            }
          }
          else {
            continue;
          }
        }

        continueLoop = false;
        break;
      }
      catch (Exception e) {
        out.println("ERROR: Please choose at least one valid option.");
        out.println();

        continue;
      }
    }

    return cipherSpecs;
  }

  protected RepoTO createRepoTO(ChunkerTO chunkerTO, MultiChunkerTO multiChunkerTO, List<TransformerTO> transformersTO) throws Exception {
    // Make transfer object
    RepoTO repoTO = new RepoTO();

    // Create random repo identifier
    byte[] newRepoId = new byte[REPO_ID_LENGTH];
    new SecureRandom().nextBytes(newRepoId);

    repoTO.setRepoId(newRepoId);

    // Add to repo transfer object
    repoTO.setChunkerTO(chunkerTO);
    repoTO.setMultiChunker(multiChunkerTO);
    repoTO.setTransformers(transformersTO);

    return repoTO;
  }

  protected ChunkerTO getDefaultChunkerTO() {
    ChunkerTO chunkerTO = new ChunkerTO();

    chunkerTO.setType(FixedChunker.TYPE);
    chunkerTO.setSettings(new HashMap<String, String>());
    chunkerTO.getSettings().put(Chunker.PROPERTY_SIZE, "16");

    return chunkerTO;
  }

  protected MultiChunkerTO getDefaultMultiChunkerTO() {
    MultiChunkerTO multichunkerTO = new MultiChunkerTO();

    multichunkerTO.setType(ZipMultiChunker.TYPE);
    multichunkerTO.setSettings(new HashMap<String, String>());
    multichunkerTO.getSettings().put(MultiChunker.PROPERTY_SIZE, "4096");

    return multichunkerTO;
  }

  protected TransformerTO getGzipTransformerTO() {
    TransformerTO gzipTransformerTO = new TransformerTO();
    gzipTransformerTO.setType(GzipTransformer.TYPE);

    return gzipTransformerTO;
  }

  private TransformerTO getCipherTransformerTO(List<CipherSpec> cipherSpec) {
    String cipherSuitesIdStr = StringUtil.join(cipherSpec, ",", new StringJoinListener<CipherSpec>() {
      @Override
      public String getString(CipherSpec cipherSpec) {
        return "" + cipherSpec.getId();
      }
    });

    Map<String, String> cipherTransformerSettings = new HashMap<String, String>();
    cipherTransformerSettings.put(CipherTransformer.PROPERTY_CIPHER_SPECS, cipherSuitesIdStr);
    // Note: Property 'password' is added dynamically by CommandLineClient

    TransformerTO cipherTransformerTO = new TransformerTO();
    cipherTransformerTO.setType(CipherTransformer.TYPE);
    cipherTransformerTO.setSettings(cipherTransformerSettings);

    return cipherTransformerTO;
  }
}
TOP

Related Classes of org.syncany.cli.InitCommand

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.