package org.openslx.taskmanager.tasks;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.openslx.satserver.util.Exec;
import org.openslx.taskmanager.api.AbstractTask;
import com.google.gson.annotations.Expose;
public class PortScan extends AbstractTask
{
@Expose
private String host;
@Expose
private int[] ports;
@Expose
private String certificate;
private Output status = new Output();
private String certFile = null;
private static final Pattern verifyPattern = Pattern.compile( "^\\s*verify return code:\\s+(\\d+)(\\D|$)", Pattern.CASE_INSENSITIVE );;
@Override
protected boolean initTask()
{
this.setStatusObject( this.status );
if ( this.host == null || this.host.isEmpty() ) {
status.addMessage( "No host given!" );
return false;
}
if ( this.ports == null || this.ports.length == 0 ) {
status.addMessage( "No ports given!" );
return false;
}
return true;
}
@Override
protected boolean execute()
{
// Create usable cert file if possible
File tmpFile = null;
if ( certificate == null || certificate.isEmpty() || certificate.equals( "default" ) ) {
certFile = CreateLdapConfig.DEFAULT_CA_BUNDLE;
} else {
try {
tmpFile = File.createTempFile( "bwlp-", ".pem" );
FileUtils.writeStringToFile( tmpFile, certificate, StandardCharsets.UTF_8 );
} catch ( IOException e ) {
status.addMessage( "Error creating temporary file for certificate: " + e.getMessage() );
return false;
}
certFile = tmpFile.getAbsolutePath();
}
// Execute scan in parallel (4 tasks) to speed things up
ExecutorService tp = Executors.newFixedThreadPool( ports.length > 4 ? 4 : ports.length );
final List<Result> results = new ArrayList<>();
for ( final int port : ports ) {
tp.submit( new Callable<Object>() {
@Override
public Object call() throws Exception
{
try {
results.add( testPort( port ) );
} catch ( Exception e ) {
status.addMessage( "Exception occured when checking port " + port + ": " + e.toString() );
}
return null;
}
} );
}
tp.shutdown();
try {
tp.awaitTermination( ports.length * 2, TimeUnit.SECONDS );
} catch ( InterruptedException e ) {
// ...
}
if ( tmpFile != null ) {
tmpFile.delete();
}
status.ports = results;
return true;
}
private Result testPort( int port )
{
boolean open = false;
final AtomicReference<String> fingerprint = new AtomicReference<>();
final AtomicReference<String> notAfter = new AtomicReference<>();
final AtomicInteger verifyResult = new AtomicInteger( -1 );
final StringBuffer messages = new StringBuffer();
final StringBuffer certList = new StringBuffer();
try {
Socket sock = new Socket();
sock.connect( new InetSocketAddress( this.host, port ), 1500 );
open = true;
messages.append( "Found open port " + port );
sock.close();
} catch ( Exception e ) {
if ( !open ) {
messages.append( "Found closed port " + port + " (" + e.toString() + ")" );
}
}
if ( open ) {
String str = this.host.replaceAll( "[^a-zA-Z0-9\\.\\-_]", "" ) + ":" + port;
// Is open, see if it is running SSL
int exitCode = Exec.syncAt( 4, new Exec.ExecCallback() {
private boolean inCert = false;
@Override
public void processStdOut( String line )
{
if ( line.startsWith( "notAfter=" ) ) {
notAfter.set( line.substring( 9 ) );
messages.append( "\nCertificate valid until " + notAfter.get() );
}
if ( line.startsWith( "SHA1 Fingerprint=" ) ) {
fingerprint.set( line.substring( 17 ) );
messages.append( "\nCertificate fingerprint: " + fingerprint.get() );
}
if ( !inCert && line.equals( "-----BEGIN CERTIFICATE-----" ) ) {
inCert = true;
}
if ( inCert ) {
if ( line.equals( "-----END CERTIFICATE-----" ) ) {
inCert = false;
}
certList.append( line );
certList.append( '\n' );
}
Matcher m;
if ( verifyResult.get() == -1 && null != ( m = verifyPattern.matcher( line ) ) && m.find() ) {
try {
verifyResult.compareAndSet( -1, Integer.parseInt( m.group( 1 ) ) );
} catch ( Exception e ) {
}
}
}
@Override
public void processStdErr( String line )
{
// Nothing will be here
}
}, "/", "/bin/sh", "-c",
"openssl s_client -CAfile '" + certFile + "' -showcerts -connect " + str + " </dev/null 2> /dev/null; RET=$? ;"
+ " openssl s_client -connect " + str + " </dev/null 2> /dev/null "
+ " | openssl x509 -noout -enddate -fingerprint -sha1 2>&1 ; exit $(( RET + $? ))" );
if ( exitCode != 0 && ( fingerprint.get() == null || fingerprint.get().isEmpty() ) ) {
verifyResult.set( -2 );
}
messages.append( "\nVerify result: " + verifyResult.get() );
}
status.addMessage( messages.toString() );
return new Result( port, open, fingerprint.get(), notAfter.get(), verifyResult.get(), certList.toString() );
}
/**
* Output - contains additional status data of this task
*/
private static class Output
{
protected String messages = null;
@SuppressWarnings( "unused" )
protected List<Result> ports = null;
private synchronized void addMessage( String str )
{
if ( messages == null ) {
messages = str;
} else {
messages += "\n" + str;
}
}
}
@SuppressWarnings( "unused" )
private static class Result
{
protected final int port;
protected final boolean open;
protected final String certFingerprint;
protected final String notAfter;
protected final int verifyResult;
protected final String certificateChain;
public Result( int port, boolean open, String fingerprint, String notAfter, int verifyResult, String certificateChain )
{
this.port = port;
this.open = open;
this.certFingerprint = fingerprint;
this.notAfter = notAfter;
this.verifyResult = verifyResult;
this.certificateChain = certificateChain;
}
}
}