| /* |
| * 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.apache.maven.wagon.providers.ssh.external; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.util.List; |
| import java.util.Locale; |
| |
| import org.apache.maven.wagon.AbstractWagon; |
| import org.apache.maven.wagon.CommandExecutionException; |
| import org.apache.maven.wagon.CommandExecutor; |
| import org.apache.maven.wagon.PathUtils; |
| import org.apache.maven.wagon.PermissionModeUtils; |
| import org.apache.maven.wagon.ResourceDoesNotExistException; |
| import org.apache.maven.wagon.Streams; |
| import org.apache.maven.wagon.TransferFailedException; |
| import org.apache.maven.wagon.WagonConstants; |
| import org.apache.maven.wagon.authentication.AuthenticationException; |
| import org.apache.maven.wagon.authentication.AuthenticationInfo; |
| import org.apache.maven.wagon.authorization.AuthorizationException; |
| import org.apache.maven.wagon.events.TransferEvent; |
| import org.apache.maven.wagon.providers.ssh.ScpHelper; |
| import org.apache.maven.wagon.repository.RepositoryPermissions; |
| import org.apache.maven.wagon.resource.Resource; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.util.cli.CommandLineException; |
| import org.codehaus.plexus.util.cli.CommandLineUtils; |
| import org.codehaus.plexus.util.cli.Commandline; |
| |
| /** |
| * SCP deployer using "external" scp program. To allow for |
| * ssh-agent type behavior, until we can construct a Java SSH Agent and interface for JSch. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| * @todo [BP] add compression flag |
| * @plexus.component role="org.apache.maven.wagon.Wagon" |
| * role-hint="scpexe" |
| * instantiation-strategy="per-lookup" |
| */ |
| public class ScpExternalWagon extends AbstractWagon implements CommandExecutor { |
| /** |
| * The external SCP command to use - default is <code>scp</code>. |
| * |
| * @component.configuration default="scp" |
| */ |
| private String scpExecutable = "scp"; |
| |
| /** |
| * The external SSH command to use - default is <code>ssh</code>. |
| * |
| * @component.configuration default="ssh" |
| */ |
| private String sshExecutable = "ssh"; |
| |
| /** |
| * Arguments to pass to the SCP command. |
| * |
| * @component.configuration |
| */ |
| private String scpArgs; |
| |
| /** |
| * Arguments to pass to the SSH command. |
| * |
| * @component.configuration |
| */ |
| private String sshArgs; |
| |
| private ScpHelper sshTool = new ScpHelper(this); |
| |
| private static final int SSH_FATAL_EXIT_CODE = 255; |
| |
| // ---------------------------------------------------------------------- |
| // |
| // ---------------------------------------------------------------------- |
| |
| protected void openConnectionInternal() throws AuthenticationException { |
| if (authenticationInfo == null) { |
| authenticationInfo = new AuthenticationInfo(); |
| } |
| } |
| |
| public void closeConnection() { |
| // nothing to disconnect |
| } |
| |
| public boolean getIfNewer(String resourceName, File destination, long timestamp) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| fireSessionDebug("getIfNewer in SCP wagon is not supported - performing an unconditional get"); |
| get(resourceName, destination); |
| return true; |
| } |
| |
| /** |
| * @return The hostname of the remote server prefixed with the username, which comes either from the repository URL |
| * or from the authenticationInfo. |
| */ |
| private String buildRemoteHost() { |
| String username = this.getRepository().getUsername(); |
| if (username == null) { |
| username = authenticationInfo.getUserName(); |
| } |
| |
| if (username == null) { |
| return getRepository().getHost(); |
| } else { |
| return username + "@" + getRepository().getHost(); |
| } |
| } |
| |
| public void executeCommand(String command) throws CommandExecutionException { |
| fireTransferDebug("Executing command: " + command); |
| |
| executeCommand(command, false); |
| } |
| |
| public Streams executeCommand(String command, boolean ignoreFailures) throws CommandExecutionException { |
| boolean putty = isPuTTY(); |
| |
| File privateKey; |
| try { |
| privateKey = ScpHelper.getPrivateKey(authenticationInfo); |
| } catch (FileNotFoundException e) { |
| throw new CommandExecutionException(e.getMessage(), e); |
| } |
| Commandline cl = createBaseCommandLine(putty, sshExecutable, privateKey); |
| |
| int port = |
| repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort(); |
| if (port != ScpHelper.DEFAULT_SSH_PORT) { |
| if (putty) { |
| cl.createArg().setLine("-P " + port); |
| } else { |
| cl.createArg().setLine("-p " + port); |
| } |
| } |
| |
| if (sshArgs != null) { |
| cl.createArg().setLine(sshArgs); |
| } |
| |
| String remoteHost = this.buildRemoteHost(); |
| |
| cl.createArg().setValue(remoteHost); |
| |
| cl.createArg().setValue(command); |
| |
| fireSessionDebug("Executing command: " + cl.toString()); |
| |
| try { |
| CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer(); |
| CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer(); |
| int exitCode = CommandLineUtils.executeCommandLine(cl, out, err); |
| Streams streams = new Streams(); |
| streams.setOut(out.getOutput()); |
| streams.setErr(err.getOutput()); |
| fireSessionDebug(streams.getOut()); |
| fireSessionDebug(streams.getErr()); |
| if (exitCode != 0) { |
| if (!ignoreFailures || exitCode == SSH_FATAL_EXIT_CODE) { |
| throw new CommandExecutionException("Exit code " + exitCode + " - " + err.getOutput()); |
| } |
| } |
| return streams; |
| } catch (CommandLineException e) { |
| throw new CommandExecutionException("Error executing command line", e); |
| } |
| } |
| |
| protected boolean isPuTTY() { |
| String exe = sshExecutable.toLowerCase(Locale.ENGLISH); |
| return exe.contains("plink") || exe.contains("klink"); |
| } |
| |
| private Commandline createBaseCommandLine(boolean putty, String executable, File privateKey) { |
| Commandline cl = new Commandline(); |
| |
| cl.setExecutable(executable); |
| |
| if (privateKey != null) { |
| cl.createArg().setValue("-i"); |
| cl.createArg().setFile(privateKey); |
| } |
| |
| String password = authenticationInfo.getPassword(); |
| if (putty && password != null) { |
| cl.createArg().setValue("-pw"); |
| cl.createArg().setValue(password); |
| } |
| |
| // should check interactive flag, but scpexe never works in interactive mode right now due to i/o streams |
| if (putty) { |
| cl.createArg().setValue("-batch"); |
| } else { |
| cl.createArg().setValue("-o"); |
| cl.createArg().setValue("BatchMode yes"); |
| } |
| return cl; |
| } |
| |
| private void executeScpCommand(Resource resource, File localFile, boolean put) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| boolean putty = isPuTTYSCP(); |
| |
| File privateKey; |
| try { |
| privateKey = ScpHelper.getPrivateKey(authenticationInfo); |
| } catch (FileNotFoundException e) { |
| fireSessionConnectionRefused(); |
| |
| throw new AuthorizationException(e.getMessage()); |
| } |
| Commandline cl = createBaseCommandLine(putty, scpExecutable, privateKey); |
| |
| cl.setWorkingDirectory(localFile.getParentFile().getAbsolutePath()); |
| |
| int port = |
| repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort(); |
| if (port != ScpHelper.DEFAULT_SSH_PORT) { |
| cl.createArg().setLine("-P " + port); |
| } |
| |
| if (scpArgs != null) { |
| cl.createArg().setLine(scpArgs); |
| } |
| |
| String resourceName = normalizeResource(resource); |
| String remoteFile = getRepository().getBasedir() + "/" + resourceName; |
| |
| remoteFile = StringUtils.replace(remoteFile, " ", "\\ "); |
| |
| String qualifiedRemoteFile = this.buildRemoteHost() + ":" + remoteFile; |
| if (put) { |
| cl.createArg().setValue(localFile.getName()); |
| cl.createArg().setValue(qualifiedRemoteFile); |
| } else { |
| cl.createArg().setValue(qualifiedRemoteFile); |
| cl.createArg().setValue(localFile.getName()); |
| } |
| |
| fireSessionDebug("Executing command: " + cl.toString()); |
| |
| try { |
| CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer(); |
| int exitCode = CommandLineUtils.executeCommandLine(cl, null, err); |
| if (exitCode != 0) { |
| if (!put && err.getOutput().trim().toLowerCase(Locale.ENGLISH).contains("no such file or directory")) { |
| throw new ResourceDoesNotExistException(err.getOutput()); |
| } else { |
| TransferFailedException e = |
| new TransferFailedException("Exit code: " + exitCode + " - " + err.getOutput()); |
| |
| fireTransferError(resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET); |
| |
| throw e; |
| } |
| } |
| } catch (CommandLineException e) { |
| fireTransferError(resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET); |
| |
| throw new TransferFailedException("Error executing command line", e); |
| } |
| } |
| |
| boolean isPuTTYSCP() { |
| String exe = scpExecutable.toLowerCase(Locale.ENGLISH); |
| return exe.contains("pscp") || exe.contains("kscp"); |
| } |
| |
| private String normalizeResource(Resource resource) { |
| return StringUtils.replace(resource.getName(), "\\", "/"); |
| } |
| |
| public void put(File source, String destination) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| Resource resource = new Resource(destination); |
| |
| firePutInitiated(resource, source); |
| |
| if (!source.exists()) { |
| throw new ResourceDoesNotExistException("Specified source file does not exist: " + source); |
| } |
| |
| String basedir = getRepository().getBasedir(); |
| |
| String resourceName = StringUtils.replace(destination, "\\", "/"); |
| |
| String dir = PathUtils.dirname(resourceName); |
| |
| dir = StringUtils.replace(dir, "\\", "/"); |
| |
| String umaskCmd = null; |
| if (getRepository().getPermissions() != null) { |
| String dirPerms = getRepository().getPermissions().getDirectoryMode(); |
| |
| if (dirPerms != null) { |
| umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor(dirPerms); |
| } |
| } |
| |
| String mkdirCmd = "mkdir -p " + basedir + "/" + dir + "\n"; |
| |
| if (umaskCmd != null) { |
| mkdirCmd = umaskCmd + "; " + mkdirCmd; |
| } |
| |
| try { |
| executeCommand(mkdirCmd); |
| } catch (CommandExecutionException e) { |
| fireTransferError(resource, e, TransferEvent.REQUEST_PUT); |
| |
| throw new TransferFailedException("Error executing command for transfer", e); |
| } |
| |
| resource.setContentLength(source.length()); |
| |
| resource.setLastModified(source.lastModified()); |
| |
| firePutStarted(resource, source); |
| |
| executeScpCommand(resource, source, true); |
| |
| postProcessListeners(resource, source, TransferEvent.REQUEST_PUT); |
| |
| try { |
| RepositoryPermissions permissions = getRepository().getPermissions(); |
| |
| if (permissions != null && permissions.getGroup() != null) { |
| executeCommand("chgrp -f " + permissions.getGroup() + " " + basedir + "/" + resourceName + "\n", true); |
| } |
| |
| if (permissions != null && permissions.getFileMode() != null) { |
| executeCommand( |
| "chmod -f " + permissions.getFileMode() + " " + basedir + "/" + resourceName + "\n", true); |
| } |
| } catch (CommandExecutionException e) { |
| fireTransferError(resource, e, TransferEvent.REQUEST_PUT); |
| |
| throw new TransferFailedException("Error executing command for transfer", e); |
| } |
| firePutCompleted(resource, source); |
| } |
| |
| public void get(String resourceName, File destination) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| String path = StringUtils.replace(resourceName, "\\", "/"); |
| |
| Resource resource = new Resource(path); |
| |
| fireGetInitiated(resource, destination); |
| |
| createParentDirectories(destination); |
| |
| fireGetStarted(resource, destination); |
| |
| executeScpCommand(resource, destination, false); |
| |
| postProcessListeners(resource, destination, TransferEvent.REQUEST_GET); |
| |
| fireGetCompleted(resource, destination); |
| } |
| |
| // |
| // these parameters are user specific, so should not be read from the repository itself. |
| // They can be configured by plexus, or directly on the instantiated object. |
| // Alternatively, we may later accept a generic parameters argument to connect, or some other configure(Properties) |
| // method on a Wagon. |
| // |
| |
| public List<String> getFileList(String destinationDirectory) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| return sshTool.getFileList(destinationDirectory, repository); |
| } |
| |
| public void putDirectory(File sourceDirectory, String destinationDirectory) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { |
| sshTool.putDirectory(this, sourceDirectory, destinationDirectory); |
| } |
| |
| public boolean resourceExists(String resourceName) throws TransferFailedException, AuthorizationException { |
| return sshTool.resourceExists(resourceName, repository); |
| } |
| |
| public boolean supportsDirectoryCopy() { |
| return true; |
| } |
| |
| public String getScpExecutable() { |
| return scpExecutable; |
| } |
| |
| public void setScpExecutable(String scpExecutable) { |
| this.scpExecutable = scpExecutable; |
| } |
| |
| public String getSshExecutable() { |
| return sshExecutable; |
| } |
| |
| public void setSshExecutable(String sshExecutable) { |
| this.sshExecutable = sshExecutable; |
| } |
| |
| public String getScpArgs() { |
| return scpArgs; |
| } |
| |
| public void setScpArgs(String scpArgs) { |
| this.scpArgs = scpArgs; |
| } |
| |
| public String getSshArgs() { |
| return sshArgs; |
| } |
| |
| public void setSshArgs(String sshArgs) { |
| this.sshArgs = sshArgs; |
| } |
| } |