Skip to content

Commit

Permalink
Add initial sudo response implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Nov 23, 2011
1 parent 702e94e commit a442578
Show file tree
Hide file tree
Showing 4 changed files with 643 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.text.MessageFormat;
import java.util.*;

Expand Down Expand Up @@ -152,8 +155,40 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S
} catch (SSHTaskBuilder.BuilderException e) {
throw new ExecutionException(e);
}

//Sudo support
final ResponderThread thread;
final Responder responder;
//TODO: use node attribute to define sudo command invocation
if ("sudo".equals(command[0]) || command[0].startsWith("sudo ")) {
//TODO: use node attribute to define sudo password secure option name
responder = new SudoResponder(node.getAttributes().get("sudo-password")+"\n");
final PipedInputStream responderInput = new PipedInputStream();
final PipedOutputStream responderOutput = new PipedOutputStream();
final PipedInputStream jschInput = new PipedInputStream();
final PipedOutputStream jschOutput = new PipedOutputStream();
try {
responderInput.connect(jschOutput);
jschInput.connect(responderOutput);
} catch (IOException e) {
throw new ExecutionException(e);
}
final DisconnectResponderResultHandler resultHandler = new DisconnectResponderResultHandler();
thread = new ResponderThread(responder, responderInput, responderOutput, resultHandler);
sshexec.setAllocatePty(true);
sshexec.setInputStream(jschInput);
sshexec.setSecondaryStream(jschOutput);
sshexec.setDisconnectHolder(resultHandler);
} else {
thread = null;
responder=null;
}

String errormsg = null;
try {
if (null != thread) {
thread.start();
}
sshexec.execute();
success = true;
} catch (BuildException e) {
Expand All @@ -180,6 +215,18 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S
}
context.getExecutionListener().log(0, errormsg);
}
if (null != thread) {
if(thread.isAlive()){
thread.stopResponder();
}
try {
thread.join(5000);
} catch (InterruptedException e) {
}
if (!thread.isAlive() && thread.isFailed()) {
context.getExecutionListener().log(0, responder.toString() + " failed: " + thread.getFailureReason());
}
}
final int resultCode = sshexec.getExitStatus();
final boolean status = success;
final String resultmsg = null != errormsg ? errormsg : null;
Expand All @@ -202,6 +249,25 @@ public String toString() {
};
}

/**
* Responder thread result handler which closes the SSH connection on failure
*/
private final static class DisconnectResponderResultHandler implements ResponderThread.ResultHandler,
ExtSSHExec.DisconnectHolder {
ExtSSHExec.Disconnectable disconnectable;

public void setDisconnectable(final ExtSSHExec.Disconnectable disconnectable) {
this.disconnectable = disconnectable;
}

public void handleResult(final boolean success, final String reason) {
if (!success) {
if (null != disconnectable) {
disconnectable.disconnect();
}
}
}
}

final static class NodeSSHConnectionInfo implements SSHTaskBuilder.SSHConnectionInfo {
final INodeEntry node;
Expand Down Expand Up @@ -250,11 +316,11 @@ public String getPrivateKeyfilePath() {
}

private String evaluateOption(final String optionName) {
if(null==optionName) {
if (null == optionName) {
logger.debug("option name was null");
return null;
}
if(null== context.getPrivateDataContext()){
if (null == context.getPrivateDataContext()) {
logger.debug("private context was null");
return null;
}
Expand All @@ -263,11 +329,11 @@ private String evaluateOption(final String optionName) {
final Map<String, String> option = context.getPrivateDataContext().get(opts[0]);
if (null != option) {
final String value = option.get(opts[1]);
if(null==value){
if (null == value) {
logger.debug("private context '" + optionName + "' was null");
}
return value;
}else {
} else {
logger.debug("private context '" + opts[0] + "' was null");
}
}
Expand Down Expand Up @@ -308,4 +374,65 @@ public String getUsername() {
}


/**
* Sudo responder.
*
* TODO: allow patterns to be overridden
*/
private class SudoResponder implements Responder {
private String password;

private SudoResponder(final String password) {
this.password = password;
}

public String getInputSuccessPattern() {
return "^\\[sudo\\] password for .+: .*";
}

public int getInputMaxLines() {
return 12;
}

public String getResponseSuccessPattern() {
return null;
}

public int getResponseMaxLines() {
return 3;
}

public String getResponseFailurePattern() {
return "^.*try again.*";//Sorry, try again
}

public String getInputFailurePattern() {
return null;
}

public long getResponseMaxTimeout() {
return 5000;
}

public long getInputMaxTimeout() {
return 5000;
}

public boolean isFailOnInputThreshold() {
return true;
}

public boolean isFailOnResponseThreshold() {
return false;
}

public String getInputString() {
return password;
}

@Override
public String toString() {
return "Sudo execution password response";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2011 DTO Solutions, Inc. (http://dtosolutions.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.
*/

/*
* Responder.java
*
* User: Greg Schueler <a href="mailto:greg@dtosolutions.com">greg@dtosolutions.com</a>
* Created: 11/21/11 10:16 AM
*
*/
package com.dtolabs.rundeck.core.execution.impl.jsch;

/**
* Responder defines a pattern of response to some input from a stream.
* <p/>
* A Responder defines up to four different regular expressions:
* <p/>
* <ul> <li> input success pattern: pattern to look for before responding that indicates response should proceed</li>
* <li> input failure pattern: pattern to look for before responding that indicates failure</li> <li> response success
* pattern: pattern to look for after responding that indicates success</li> <li> response failure pattern: pattern to
* look for after responding that indicates failure</li> </ul>
* <p/>
* It also defines some other heuristics:
* <p/>
* <ul> <li>inputMaxLines: maximum number of input lines to use to match for input pattern. If exceeded, then "input
* threshhold" breached.</li> <li>inputMaxTimeout: maximum time to wait while detecting new input. If exceeded, then
* "input threshhold" breached.</li> <li>failOnInputThreshold: if true, fail if "input threshold" breached, otherwise,
* ignore it.</li> <li>responseMaxLines: maximum number of input lines to use to match for response pattern. If
* exceeded, then "response threshhold" breached.</li> <li>responseMaxTimeout: maximum time to wait while detecting
* response pattern. If exceeded, then "response threshhold" breached.</li> <li>failOnResponseThreshold: if true, fail
* if "response threshold" breached, otherwise, ignore it.</li> </ul>
*
* @author Greg Schueler <a href="mailto:greg@dtosolutions.com">greg@dtosolutions.com</a>
*/
public interface Responder {
/**
* Return a regex to detect input prompt
*/
public String getInputSuccessPattern();

/**
* Return a regex to detect input prompt failure
*/
public String getInputFailurePattern();

/**
* Return threshold max lines to read detecting input pattern
*/
public int getInputMaxLines();

/**
* Return threshold max timeout detecting input pattern
*/
public long getInputMaxTimeout();

/**
* Return true if input threshold indicates failure
*/
public boolean isFailOnInputThreshold();

/**
* Return a regex to detect response to input was successful
*/
public String getResponseSuccessPattern();

/**
* Return a regex to detect response to input was failure
*/
public String getResponseFailurePattern();

/**
* Return threshold max lines to read detecting response pattern
*/
public int getResponseMaxLines();

/**
* Return threshold max timeout detecting response pattern
*/
public long getResponseMaxTimeout();

/**
* Return true if response threshold indicates failure
*/
public boolean isFailOnResponseThreshold();

/**
* Return input string to send after successful input pattern (including any newline characters as necessary)
*/
public String getInputString();
}
Loading

0 comments on commit a442578

Please sign in to comment.