2019-05-18 13:08:09 -07:00
|
|
|
/*
|
|
|
|
|
* JobServClient
|
|
|
|
|
*
|
|
|
|
|
* v1.0
|
|
|
|
|
*
|
|
|
|
|
* May 18, 2019
|
|
|
|
|
*/
|
|
|
|
|
|
2019-05-17 01:28:26 -07:00
|
|
|
package JobServ;
|
|
|
|
|
|
|
|
|
|
import io.grpc.ManagedChannel;
|
|
|
|
|
import io.grpc.ManagedChannelBuilder;
|
|
|
|
|
import io.grpc.StatusRuntimeException;
|
2019-05-19 12:21:00 -07:00
|
|
|
import io.grpc.netty.GrpcSslContexts;
|
|
|
|
|
import io.grpc.netty.NettyChannelBuilder;
|
|
|
|
|
import io.netty.handler.ssl.SslContext;
|
|
|
|
|
import io.netty.handler.ssl.SslContextBuilder;
|
|
|
|
|
|
|
|
|
|
import javax.net.ssl.SSLException;
|
|
|
|
|
import java.io.File;
|
2019-05-18 12:20:12 -07:00
|
|
|
import java.util.InputMismatchException;
|
2019-05-17 01:28:26 -07:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.logging.Level;
|
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
import java.util.Scanner;
|
|
|
|
|
|
2019-05-18 13:08:09 -07:00
|
|
|
/*
|
|
|
|
|
* The JobServClient class extends the gRPC stub code
|
|
|
|
|
* Additionally, it plugs a command line interface into the API code.
|
|
|
|
|
*/
|
2019-05-17 01:28:26 -07:00
|
|
|
public class JobServClient {
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* The client should not use the same logging module as the server.
|
2019-05-17 01:28:26 -07:00
|
|
|
* In a more robust product the server logging module will take advantage of system level
|
|
|
|
|
* log aggregators such as journalctl, which the client should not be writing to on the users system
|
|
|
|
|
*/
|
|
|
|
|
private static final Logger logger = Logger.getLogger(JobServClient.class.getName());
|
|
|
|
|
|
|
|
|
|
private final ManagedChannel channel;
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* blockingStub is used when the client needs to block until the server responds
|
2019-05-17 01:28:26 -07:00
|
|
|
* the client doesnt nessesarily need to support asynchronously firing off commands
|
2019-05-19 12:21:00 -07:00
|
|
|
* in this shell-like interface it would be disconcerting to get multiple returns out of order
|
2019-05-17 01:28:26 -07:00
|
|
|
*/
|
|
|
|
|
private final ShellServerGrpc.ShellServerBlockingStub blockingStub;
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* Constructor
|
|
|
|
|
* Creates an SslContext from cert, key, and trust store
|
|
|
|
|
* Creates a ManagedChannel object from SSL Parameters
|
|
|
|
|
* Spawns a new blockingStub for network operations with the server
|
|
|
|
|
*/
|
|
|
|
|
public JobServClient(String host,
|
|
|
|
|
int port,
|
|
|
|
|
String trustStore,
|
|
|
|
|
String clientCert,
|
|
|
|
|
String clientPrivateKey) throws SSLException {
|
|
|
|
|
SslContextBuilder builder = GrpcSslContexts.forClient();
|
|
|
|
|
builder.trustManager(new File(trustStore));
|
|
|
|
|
builder.keyManager(new File(clientCert), new File(clientPrivateKey));
|
|
|
|
|
|
|
|
|
|
this.channel = NettyChannelBuilder.forAddress(host, port)
|
|
|
|
|
.sslContext(builder.build())
|
|
|
|
|
.build();
|
2019-05-17 01:28:26 -07:00
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
blockingStub = ShellServerGrpc.newBlockingStub(this.channel);
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* shutdown()
|
|
|
|
|
* Gets called when you press cntrl+c
|
|
|
|
|
* takes at most 5 seconds to close its connection
|
|
|
|
|
*/
|
2019-05-17 01:28:26 -07:00
|
|
|
public void shutdown() throws InterruptedException {
|
|
|
|
|
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* getProcessInfo()
|
|
|
|
|
* sends the server a request for output from the process identified by 'pid'
|
|
|
|
|
* returns process output as string
|
|
|
|
|
*/
|
2019-05-17 01:28:26 -07:00
|
|
|
public String getProcessOutput(int pid) {
|
|
|
|
|
logger.info("[+] requesting output");
|
|
|
|
|
|
2019-05-18 12:20:12 -07:00
|
|
|
PIDMessage request = PIDMessage.newBuilder()
|
2019-05-17 18:22:38 -07:00
|
|
|
.setPid(pid)
|
2019-05-17 01:28:26 -07:00
|
|
|
.build();
|
|
|
|
|
OutputMessage response;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// blocking network operation
|
2019-05-17 18:22:38 -07:00
|
|
|
response = blockingStub.getOutput(request);
|
2019-05-17 01:28:26 -07:00
|
|
|
} catch (StatusRuntimeException e) {
|
2019-05-19 14:16:09 -07:00
|
|
|
logger.log(Level.WARNING, "(API Failure) Request for output failed: " + e.getStatus());
|
2019-05-18 12:20:12 -07:00
|
|
|
return "<Error connecting to API>";
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-18 12:20:12 -07:00
|
|
|
return response.getOutput();
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* sendNewJobMessage()
|
|
|
|
|
* sends a shell command to the api server
|
|
|
|
|
* returns new pid of job
|
2019-05-21 13:25:50 -07:00
|
|
|
* or -1 if server failed to create job
|
|
|
|
|
* or -2 if failed to connect to API
|
2019-05-19 12:21:00 -07:00
|
|
|
*/
|
2019-05-17 01:28:26 -07:00
|
|
|
public int sendNewJobMessage(String command) {
|
|
|
|
|
// thought of escaping this, but the vulnerability is only client side, from client user input.
|
|
|
|
|
logger.info("[+] Sending command to server");
|
|
|
|
|
|
|
|
|
|
NewJobMessage request = NewJobMessage.newBuilder()
|
|
|
|
|
.setCommand(command)
|
|
|
|
|
.build();
|
2019-05-17 18:22:38 -07:00
|
|
|
PIDMessage response;
|
2019-05-17 01:28:26 -07:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// blocking network operation
|
2019-05-17 18:22:38 -07:00
|
|
|
response = blockingStub.newJob(request);
|
2019-05-17 01:28:26 -07:00
|
|
|
} catch (StatusRuntimeException e) {
|
2019-05-19 14:16:09 -07:00
|
|
|
logger.log(Level.WARNING, "(API Failure) Request for new job failed: " + e.getStatus());
|
2019-05-21 13:25:50 -07:00
|
|
|
return -2;
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-18 12:20:12 -07:00
|
|
|
if(response.getPid() == -1) {
|
2019-05-17 01:28:26 -07:00
|
|
|
logger.log(Level.WARNING, "New job creation failed server side!");
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
return response.getPid();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* getProcessStatus()
|
|
|
|
|
* requests running status of process pid
|
|
|
|
|
* returns true if process still running else false
|
|
|
|
|
*/
|
2019-05-18 12:20:12 -07:00
|
|
|
public Boolean getProcessStatus(int pid) {
|
2019-05-17 18:22:38 -07:00
|
|
|
logger.info("[+] Requesting status of a job");
|
|
|
|
|
|
|
|
|
|
PIDMessage request = PIDMessage.newBuilder()
|
|
|
|
|
.setPid(pid)
|
|
|
|
|
.build();
|
|
|
|
|
StatusMessage response;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// blocking network operation
|
|
|
|
|
response = blockingStub.getStatus(request);
|
|
|
|
|
} catch (StatusRuntimeException e) {
|
2019-05-19 14:16:09 -07:00
|
|
|
logger.log(Level.WARNING, "(API Failure) Request for status failed: " + e.getStatus());
|
2019-05-18 12:20:12 -07:00
|
|
|
return false;
|
2019-05-17 18:22:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.getIsRunning();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* sends PID to server
|
|
|
|
|
* returns process exit code
|
|
|
|
|
* returns a 0-255 return code or 277 if still running
|
|
|
|
|
* or 278 if error in API
|
|
|
|
|
*/
|
2019-05-17 18:22:38 -07:00
|
|
|
public int getProcessReturn(int pid) {
|
|
|
|
|
logger.info("[+] Requesting return code of a job");
|
|
|
|
|
|
|
|
|
|
PIDMessage request = PIDMessage.newBuilder()
|
|
|
|
|
.setPid(pid)
|
|
|
|
|
.build();
|
|
|
|
|
ReturnMessage response;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// blocking network operation
|
|
|
|
|
response = blockingStub.getReturn(request);
|
|
|
|
|
} catch (StatusRuntimeException e) {
|
2019-05-19 14:16:09 -07:00
|
|
|
logger.log(Level.WARNING, "(API Failure) Failed to get return code: " + e.getStatus());
|
2019-05-17 18:22:38 -07:00
|
|
|
return 278;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.getProcessReturnCode();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* killProcess()
|
|
|
|
|
* send a PID to be killed, function returns nothing
|
|
|
|
|
* logs warning if job status comes back still running
|
|
|
|
|
*/
|
2019-05-17 18:22:38 -07:00
|
|
|
public void killProcess(int pid) {
|
|
|
|
|
logger.info("[+] Killing a job");
|
|
|
|
|
|
|
|
|
|
PIDMessage request = PIDMessage.newBuilder()
|
|
|
|
|
.setPid(pid)
|
|
|
|
|
.build();
|
|
|
|
|
StatusMessage response;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// blocking network operation
|
|
|
|
|
response = blockingStub.killJob(request);
|
|
|
|
|
} catch (StatusRuntimeException e) {
|
2019-05-19 14:16:09 -07:00
|
|
|
logger.log(Level.WARNING, "(API Failure) Failed to send request: " + e.getStatus());
|
2019-05-17 18:22:38 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.getIsRunning()) {
|
|
|
|
|
logger.log(Level.WARNING, "[-] Server failed to kill job!");
|
|
|
|
|
}
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
/*
|
|
|
|
|
* main()
|
|
|
|
|
* Client entrypoint
|
|
|
|
|
* Parses arguments and calls the correct function
|
|
|
|
|
*/
|
2019-05-17 01:28:26 -07:00
|
|
|
public static void main(String[] args) throws Exception {
|
2019-05-17 18:22:38 -07:00
|
|
|
|
2019-05-17 01:28:26 -07:00
|
|
|
// check args
|
2019-05-19 12:21:00 -07:00
|
|
|
if (args.length < 7) {
|
|
|
|
|
System.out.println("Usage: $ ./jobserv-client privatekey, cert, truststore, host, port, command, args");
|
|
|
|
|
System.out.println("Or try $ ./jobserv-client help");
|
2019-05-19 14:39:41 -07:00
|
|
|
outputHelp();
|
2019-05-17 01:28:26 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JobServClient client;
|
|
|
|
|
try {
|
2019-05-19 13:03:53 -07:00
|
|
|
client = new JobServClient(args[3], Integer.parseInt(args[4]), args[2], args[1], args[0]);
|
2019-05-17 18:22:38 -07:00
|
|
|
|
2019-05-21 01:37:41 -07:00
|
|
|
// Likely bad port
|
2019-05-17 01:28:26 -07:00
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
System.out.println("Invalid Port");
|
|
|
|
|
return;
|
2019-05-21 01:37:41 -07:00
|
|
|
|
|
|
|
|
// bad cert or key format
|
2019-05-19 12:21:00 -07:00
|
|
|
} catch (SSLException e) {
|
|
|
|
|
System.out.println(e.getMessage());
|
|
|
|
|
return;
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-19 12:21:00 -07:00
|
|
|
// declare pid up here so that multiple switch cases can use it
|
2019-05-17 18:22:38 -07:00
|
|
|
int candidatePid;
|
2019-05-19 12:21:00 -07:00
|
|
|
// parse remaining args
|
2019-05-19 13:03:53 -07:00
|
|
|
switch (args[5]) {
|
2019-05-17 18:22:38 -07:00
|
|
|
case "new":
|
2019-05-19 12:21:00 -07:00
|
|
|
if (args.length < 7) {
|
2019-05-17 18:22:38 -07:00
|
|
|
System.out.println("Improper formatting, try client --help");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-17 01:28:26 -07:00
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
String command = "";
|
2019-05-19 12:21:00 -07:00
|
|
|
for (int token = 6; token < args.length; token++) {
|
2019-05-17 18:22:38 -07:00
|
|
|
command += " " + args[token];
|
|
|
|
|
}
|
2019-05-17 01:28:26 -07:00
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
int newProcess = client.sendNewJobMessage(command);
|
2019-05-18 12:20:12 -07:00
|
|
|
System.out.printf("Process started, assigned pid is %d\n", newProcess);
|
2019-05-17 01:28:26 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "output":
|
2019-05-21 01:37:41 -07:00
|
|
|
candidatePid = getPidArg(args, 7);
|
|
|
|
|
if (candidatePid < 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-17 18:22:38 -07:00
|
|
|
|
|
|
|
|
String processOutput = client.getProcessOutput(candidatePid);
|
|
|
|
|
System.out.println(processOutput);
|
2019-05-17 01:28:26 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "status":
|
2019-05-21 01:37:41 -07:00
|
|
|
candidatePid = getPidArg(args, 7);
|
|
|
|
|
if (candidatePid < 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-17 18:22:38 -07:00
|
|
|
|
|
|
|
|
Boolean processStatus = client.getProcessStatus(candidatePid);
|
2019-05-18 12:20:12 -07:00
|
|
|
System.out.printf("Process is currently running? %b\n", processStatus);
|
2019-05-17 01:28:26 -07:00
|
|
|
break;
|
|
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
case "kill":
|
2019-05-21 01:37:41 -07:00
|
|
|
candidatePid = getPidArg(args, 7);
|
|
|
|
|
if (candidatePid < 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-17 18:22:38 -07:00
|
|
|
|
2019-05-18 12:20:12 -07:00
|
|
|
client.killProcess(candidatePid);
|
2019-05-17 18:22:38 -07:00
|
|
|
System.out.println("End process request recieved!");
|
2019-05-17 01:28:26 -07:00
|
|
|
break;
|
2019-05-17 12:19:51 -07:00
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
case "return":
|
2019-05-21 01:37:41 -07:00
|
|
|
candidatePid = getPidArg(args, 7);
|
|
|
|
|
if (candidatePid < 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-17 18:22:38 -07:00
|
|
|
|
2019-05-18 12:20:12 -07:00
|
|
|
int returnCode = client.getProcessReturn(candidatePid);
|
2019-05-17 18:22:38 -07:00
|
|
|
|
|
|
|
|
if (returnCode == 277) {
|
|
|
|
|
System.out.println("Process is still running");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
} else if (returnCode == 278) {
|
|
|
|
|
System.out.println("RPC Call error!");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
} else {
|
2019-05-18 12:20:12 -07:00
|
|
|
System.out.printf("Process Exit Code: %d\n", returnCode);
|
2019-05-17 12:19:51 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-17 18:22:38 -07:00
|
|
|
default:
|
|
|
|
|
System.out.println("Improper command, try 'help'");
|
2019-05-17 12:19:51 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-17 18:22:38 -07:00
|
|
|
|
2019-05-21 01:37:41 -07:00
|
|
|
/*
|
|
|
|
|
* getPidArg()
|
|
|
|
|
* reentrant code was found in all commands except newjob
|
|
|
|
|
* this function pulls the pid argument and wraps around the integer case
|
|
|
|
|
* returns -1 (an invalid PID) if bad index or unparsable int
|
|
|
|
|
*/
|
|
|
|
|
private static int getPidArg(String[] args, int index) {
|
|
|
|
|
if (args.length < index) {
|
|
|
|
|
System.out.println("Improper formatting, try client --help");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return Integer.parseInt(args[6]);
|
|
|
|
|
|
|
|
|
|
} catch (InputMismatchException e) {
|
|
|
|
|
System.out.println(args[6] + " is not a valid int, much less a valid pid");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 14:39:41 -07:00
|
|
|
/*
|
|
|
|
|
* outputHelp()
|
|
|
|
|
* writes help information about all commands in the shell to screen
|
|
|
|
|
*/
|
2019-05-17 18:22:38 -07:00
|
|
|
public static void outputHelp() {
|
|
|
|
|
System.out.println("... new (command)");
|
2019-05-18 12:20:12 -07:00
|
|
|
System.out.println("Starts a new process on the server");
|
2019-05-17 18:22:38 -07:00
|
|
|
System.out.println("... output (pid)");
|
|
|
|
|
System.out.println("Garners output from process on server");
|
|
|
|
|
System.out.println("... status (pid)");
|
|
|
|
|
System.out.println("Returns whether process on server is running");
|
|
|
|
|
System.out.println("... return (pid)");
|
|
|
|
|
System.out.println("Collects return code from remote process");
|
|
|
|
|
System.out.println("... kill (pid)");
|
|
|
|
|
System.out.println("Immediately destroys remote process");
|
2019-05-17 01:28:26 -07:00
|
|
|
}
|
|
|
|
|
}
|