/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed 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.pentaho.di.core.vfs;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.UserAuthenticationData;
import org.apache.commons.vfs2.provider.GenericFileName;
import org.apache.commons.vfs2.provider.sftp.SftpClientFactory;
import org.apache.commons.vfs2.provider.sftp.SftpFileProvider;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystem;
import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SftpFileSystemWindows extends SftpFileSystem {
private static final LogChannelInterface log = new LogChannel( "SftpFileSystemWindows" );
private static final String WHO_AMI_GROUPS_FO_LIST = "Whoami /GROUPS /FO LIST"; //windows command for getting croups for current user
private static final String WHO_AMI = "Whoami "; ////windows command for getting current user
private static final String ICACLS = "icacls "; //windows command for getting permissions for file
private static final String VER = "ver"; //windows command for getting version OS
private static final String GROUP_NAME = "Group Name:";
private static final String WINDOWS = "WINDOWS";
private static final String N_DELIMITER = "\\n";
private static final String RN_DELIMITER = "\r\n";
private static final String WINDOWS_PATH_DELIMITER = "/";
private Session session;
private List<String> userGroups;
private Boolean windows;
public SftpFileSystemWindows( GenericFileName rootName, Session session, FileSystemOptions fileSystemOptions ) {
super( rootName, session, fileSystemOptions );
this.session = session;
}
/**
* get user group on remote windows host
* @return list of groups + user person
* @throws JSchException
* @throws IOException
*/
public List<String> getUserGroups() throws JSchException, IOException {
if ( userGroups == null ) {
StringBuilder output = new StringBuilder();
int code = this.executeCommand( WHO_AMI_GROUPS_FO_LIST, output );
if ( code != 0 ) {
throw new JSchException( "Could not get the groups of the current user (error code: " + code + ")" );
}
this.userGroups = getUserGroups( output.toString() );
userGroups.add( getUser() );
}
return this.userGroups;
}
/**
* cut user groups from output whoami
*
* @param commandOutput output from whoami
* @return list of user groups
*/
private List<String> getUserGroups( String commandOutput ) {
List<String> result = new ArrayList<>();
int startIndex = 0;
int endIndex;
while ( true ) {
startIndex = StringUtils.indexOfIgnoreCase( commandOutput, GROUP_NAME, startIndex );
if ( startIndex < 0 ) {
return result;
}
startIndex += GROUP_NAME.length();
endIndex = StringUtils.indexOfIgnoreCase( commandOutput, RN_DELIMITER, startIndex );
if ( endIndex < 0 ) {
return result;
}
result.add( commandOutput.substring( startIndex, endIndex ).toUpperCase().trim() );
}
}
/**
* Get current user on remote host
*
* @return name of user on remote host
* @throws JSchException
* @throws IOException
*/
public String getUser() throws JSchException, IOException {
StringBuilder output = new StringBuilder();
int code = this.executeCommand( WHO_AMI, output );
if ( code != 0 ) {
throw new JSchException( "Could not get user name on remote host (error code: " + code + ")" );
}
return output.toString().trim().toUpperCase();
}
/**
*
* @param path path to file or directory
*
* @return Map Windows Group - permissions
* @throws JSchException
* @throws IOException
*/
public Map<String, String> getFilePermission( String path ) throws JSchException, IOException {
String windowsAbsPath;
if ( path.startsWith( WINDOWS_PATH_DELIMITER ) ) {
//cut first "/" windows does not have it
//it mean first letter is name of disk
path = path.substring( WINDOWS_PATH_DELIMITER.length() );
windowsAbsPath = path.substring( 0, 1 ) + ":" + path.substring( 1 );
} else {
windowsAbsPath = path;
}
Map<String, String> result = new HashMap<>();
StringBuilder output = new StringBuilder();
int code = this.executeCommand( ICACLS + windowsAbsPath, output );
if ( code != 0 ) {
return result;
}
String outputString = output.toString();
int indexOf = outputString.indexOf( windowsAbsPath );
if ( indexOf > -1 ) {
outputString = outputString.substring( indexOf + windowsAbsPath.length() );
}
String[] strings = outputString.toUpperCase().split( N_DELIMITER );
for ( String string : strings ) {
int index = string.indexOf( ":" );
if ( index > -1 ) {
result.put( string.substring( 0, index ).trim(), string.substring( index + 1 ).trim() );
}
}
return result;
}
/**
* check is remote host is windows
*
* @return true if host windows
* @throws JSchException
* @throws IOException
*/
public boolean isRemoteHostWindows() throws JSchException, IOException {
if ( this.windows == null ) {
StringBuilder output = new StringBuilder();
int code = this.executeCommand( VER, output );
this.windows = code == 0 && output.toString().toUpperCase().contains( WINDOWS );
}
return this.windows;
}
/**
* {@link org.apache.commons.vfs2.provider.sftp.SftpFileSystem#getChannel() }
*
*/
private void ensureSession() throws FileSystemException {
if ( this.session == null || !this.session.isConnected() ) {
this.doCloseCommunicationLink();
UserAuthenticationData authData = null;
Session session;
try {
GenericFileName e = (GenericFileName) this.getRootName();
authData = UserAuthenticatorUtils.authenticate( this.getFileSystemOptions(), SftpFileProvider.AUTHENTICATOR_TYPES );
session = SftpClientFactory.createConnection( e.getHostName(), e.getPort(), UserAuthenticatorUtils.getData( authData, UserAuthenticationData.USERNAME,
UserAuthenticatorUtils.toChar( e.getUserName() ) ), UserAuthenticatorUtils.getData( authData, UserAuthenticationData.PASSWORD,
UserAuthenticatorUtils.toChar( e.getPassword() ) ), this.getFileSystemOptions() );
} catch ( Exception var7 ) {
throw new FileSystemException( "vfs.provider.sftp/connect.error", this.getRootName(), var7 );
} finally {
UserAuthenticatorUtils.cleanup( authData );
}
this.session = session;
}
}
/**
*
* {@link org.apache.commons.vfs2.provider.sftp.SftpFileSystem#executeCommand(java.lang.String, java.lang.StringBuilder) }
*/
private int executeCommand( String command, StringBuilder output ) throws JSchException, IOException {
this.ensureSession();
ChannelExec channel = (ChannelExec) this.session.openChannel( "exec" );
channel.setCommand( command );
channel.setInputStream( (InputStream) null );
InputStreamReader stream = new InputStreamReader( channel.getInputStream() );
channel.setErrStream( System.err, true );
channel.connect();
char[] buffer = new char[128];
int read;
while ( ( read = stream.read( buffer, 0, buffer.length ) ) >= 0 ) {
output.append( buffer, 0, read );
}
stream.close();
while ( !channel.isClosed() ) {
try {
Thread.sleep( 100L );
} catch ( Exception exc ) {
log.logMinimal( "Warning: Error session closing. " + exc.getMessage() );
}
}
channel.disconnect();
return channel.getExitStatus();
}
}