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 results = new ArrayList<>(); for ( final int port : ports ) { tp.submit( new Callable() { @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 fingerprint = new AtomicReference<>(); final AtomicReference 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; RET=$? ;" + " openssl s_client -connect " + str + " /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 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; } } }