Java | Technology
Making the bootstrap loader "just another ClassLoader" Andrew Phillips 24 Feb, 2011
The server's host key is not cached in the registry. You
have no guarantee that the server is the computer you
think it is.
The server's key fingerprint is:
ssh-rsa 1024 7b:e5:6f:a7:f4:f9:81:62:5c:e3:1f:bf:8b:57:6c:5a
If you trust this host, hit Yes to add the key to
PuTTY's cache and carry on connecting.
If you want to carry on connecting just once, without
adding the key to the cache, hit No.
If you do not trust this host, hit Cancel to abandon the
connection.
When connecting with Jsch you can do the same. You have to provide the knownHostFile which contains the fingerprints of servers you ‘know’. On linux you can find the file under ~/.ssh/known_hosts. On Windows PuTTy will store the fingerprints in the registry. I did use Cygwin and connect with ssh to the domain controller and in a subfolder of cygwin the known_hosts file appears.
In my code-example I check if the file is set otherwise don’t check the servers fingerprint when connecting. Jsch channel type shell We want to interact with the console, write a command and check the resultmessage. When using the shell channeltype it is treated as a shell-window with a width and height. The reads from the server contain control-characters for location, enters, colors etc. I didn’t find a way to make it work while setting setPty(false) on the session. With that setting only the first read will work without control characters, but after that no more input is read. To interact with the ActiveDirectory the corresponding module is must be loaded first. I have experience that it will take a while ~1,5 seconds. It needs to happen only once, which you don’t want for every command you execute. To make it perform well, I chose to hold 1 session and 1 channel open and only load the module the first time. 3. Create a user! and remove it. All we need to do is send the 'New-ADUser'-command to the powershell and let AD do the work. We want to check if the command was successful or use the error message. With the command $? you can check the status of the last command. True -> Oke and False -> something happend... If the command was not successful I use the previous message from the server which contains the error. Then a new PowershellException is thrown with the previous message. In the PowershellException the the command and the root cause message are parsed to a readable format, without the control characters.
You can try to execute the command in the Powershell or in PuTTy first (with module ActiveDirectory loaded!): New-ADUser -SamAccountName "abij" -Name "abij" -DisplayName "Alexander" -EmailAddress "Fake@Email.com" -AccountPassword (ConvertTo-SecureString -AsPlainText "Welkcome01" -Force) -ChangePasswordAtLogon $false -PasswordNeverExpires $true -Enabled $true -path "ou=Users"
Remove-ADUser -Identity "abij" -Confirm:$false
You can do a lot with the module ActiveDirectory, check all available options 4. The code Check https://github.com/abij/ad-powershell-over-ssh for the full code example. Use maven to handle the dependencies and than you can use the PowershellServiceIntegrationTest to interact with your AD.
[java title="PowershellService.java"]
public class PowershellService {
private String adUserPath;
private static final String CREATE_USER =
"New-ADUser -SamAccountName \"${samAccountName}\"" +
" -Name \"${name}\"" +
" -DisplayName \"${displayname}\"" +
" -EmailAddress \"${email}\"" +
" -AccountPassword (ConvertTo-SecureString -AsPlainText \"${passwordPlain}\" -Force)" +
" -ChangePasswordAtLogon $false -PasswordNeverExpires $true -Enabled $true" +
" -path \"${adUserPath}\"";
private static final String REMOVE_USER =
"Remove-ADUser -identity \"${samAccountName}\" -Confirm:$false";
public void createUser(String displayname, String email, String username, String password) throws JSchException, IOException {
Map<String, String> model = new LinkedHashMap<String, String>();
model.put("samAccountName", username);
model.put("name", username);
model.put("password", password);
model.put("displayname", displayname);
model.put("email", email);
model.put("passwordPlain", password);
model.put("adUserPath", adUserPath);
PowershellSession.getInstance().execute(commando(CREATE_USER, model));
}
public void removeUser(String username) throws JSchException, IOException {
Map<String, String> model = new LinkedHashMap<String, String>();
model.put("samAccountName", username);
PowershellSession.getInstance().execute(commando(REMOVE_USER, model));
}
/**
* Fill the given String with the model.
* Template "Hi ${foo}", with model ("foo" -> "bar") will be result in: "Hi bar".
*
* @throws IllegalStateException when not all replace-fields are filled.
* @see StringUtils#replaceEach(String, String[], String[])
*/
private String commando(String template, Map<String, String> model) {
List<String> searchList = new ArrayList<String>(model.size());
for (String key : model.keySet()) {
searchList.add("${" + key + "}");
}
String[] replaceList = model.values().toArray(new String[model.size()]);
String command = StringUtils.replaceEach(template, searchList.toArray(new String[model.size()]), replaceList);
//TODO Nicer: pattern matching ${.+}
if (command.contains("${")) {
throw new IllegalStateException("Command contains unfilled parameters: " + command);
}
return command;
}
public void setAdUserPath(String adUserPath) {
this.adUserPath = adUserPath;
}
}
[/java]
And the corresponding session:
[java title="PowershellSession.java"]
/**
* PowerShell session as a Singleton. With synchronization around execute that allows
* this impl to perform 1 command at the time over 1 channel. With Jsch you can
* open multiple channels and execute commands in parallel.
*
* Nothing will stop you to modify this example to your needs.
*/
public class PowershellSession {
private static final Logger LOG = LoggerFactory.getLogger(PowershellSession.class);
// There is only 1 session, with getInstance() you can use it.
private PowershellSession() {}
private static final PowershellSession INSTANCE = new PowershellSession();
public static PowershellSession getInstance() {
return INSTANCE;
}
private static final int DEFAULT_BUFFER_SIZE = 1024;
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final int AD_MODULE_LOADINGTIME = 1500; // 1,5 sec wait for loading AD module.
// required settings
private String username;
private byte[] password;
private String host;
// optional settings with defaults:
private int port = 22;
private String knownHostFile = null; // No knownHostFile -> don't check server fingerprint.
private int socketTimeout = 5 * 1000;// 5s.
private int readTimeout = 3 * 1000; // 3s max time wait & read from server
private int pollTimeout = 20; // 20 ms before read again.
private Session session;
private ChannelShell shell;
private InputStream fromServer;
private OutputStream toServer;
public void init() throws JSchException {
JSch jSch = JSch.getInstance();
SessionConfig config = new SessionConfig();
config.setProperty(SSHConfigConstants.PREFFERED_AUTHS, "password");
if (knownHostFile == null) {
config.setProperty(SSHConfigConstants.STRICT_HOST_KEY_CHECKING, "no");
}
session = jSch.createSession(username, host, port, config);
}
public void disconnect() {
if (shell != null && shell.isConnected()) {
shell.disconnect();
LOG.debug("Channel Shell is disconnected.");
}
if (session != null && session.isConnected()) {
session.disconnect();
LOG.debug("Session is disconnected.");
}
}
public synchronized void execute(String command) throws JSchException, IOException {
checkConnection();
writeToServer(command);
verifyCommandSucceded();
}
// ******* Helper methodes ******************************************************
private void checkConnection() throws JSchException, IOException {
if (!session.isConnected()) {
session.connect(socketTimeout, password);
LOG.debug("Session connected to host: {}", session.getHost());
} else {
LOG.debug("Session is still connected.");
}
if (shell == null || !shell.isConnected()) {
shell = session.openChannel(ChannelType.SHELL);
shell.connect(socketTimeout);
LOG.debug("ChannelShell is connected.");
fromServer = shell.getInputStream();
toServer = shell.getOutputStream();
readFromServer(); // Read initial data: Windows PowerShell ... All rights reserved.
loadModuleActiveDirectory();
} else {
LOG.debug("Channel (shell) still open.");
}
}
private void loadModuleActiveDirectory() throws IOException {
LOG.debug("import-module ActiveDirectory...");
writeToServer("import-module ActiveDirectory");
sleep(AD_MODULE_LOADINGTIME, "Failed to sleep after loading module ActiveDirectory.");
verifyCommandSucceded();
}
private void writeToServer(String command) throws IOException {
String commandWithEnter = command;
if (!command.endsWith("\r\n")) {
commandWithEnter += "\r\n";
}
toServer.write((commandWithEnter).getBytes(UTF8));
toServer.flush();
}
private String readFromServer() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
String linePrompt = "\\" + username + ">"; // indicates console has new-line, stop reading.
long timeout = System.currentTimeMillis() + readTimeout;
while (System.currentTimeMillis() < timeout
&& !Util.byte2str(bos.toByteArray()).contains(linePrompt)) {
while (fromServer.available() > 0) {
int count = fromServer.read(buffer, 0, DEFAULT_BUFFER_SIZE);
if (count >= 0) {
bos.write(buffer, 0, count);
} else {
break;
}
}
if (shell.isClosed()) {
break;
}
// Don't spin like crazy though the while loop
sleep(pollTimeout, "Failed to sleep between reads with pollTimeout: " + pollTimeout);
}
String result = bos.toString("UTF-8");
LOG.debug("read from server:\n{}", result);
return result;
}
/**
* @throws PowershellException If the message was not executed successfully, with details info.
*/
private void verifyCommandSucceded() throws IOException {
String message = readFromServer();
writeToServer("$?"); // Aks powershell status of last command?
if (!readFromServer().contains("True")) {
throw new PowershellException(message);
}
}
private void sleep(long timeout, String msg) {
try {
Thread.sleep(timeout);
} catch (Exception ee) {
LOG.debug(msg);
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
disconnect(); // Always disconnect!
}
// ******* Setters **********************************************************************
public void setUsername(String username) {this.username = username;}
public void setPassword(String password) {this.password = password.getBytes(UTF8);}
public void setHost(String host) {this.host = host;}
public void setPort(int port) {this.port = port;}
public void setKnownHostFile(String knownHostFile) {this.knownHostFile = knownHostFile;}
public void setSocketTimeout(int socketTimeout) {this.socketTimeout = socketTimeout;}
public void setSession(Session session) {this.session = session;}
public void setReadTimeout(int readTimeout) {this.readTimeout = readTimeout;}
public void setPollTimeout(int pollTimeout) {this.pollTimeout = pollTimeout;}
}
[/java]