package org.openslx.satserver.util; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.net.ssl.KeyManager; import javax.net.ssl.TrustManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.schwering.irc.lib.IRCConfig; import org.schwering.irc.lib.IRCConfigBuilder; import org.schwering.irc.lib.IRCConnection; import org.schwering.irc.lib.IRCConnectionFactory; import org.schwering.irc.lib.IRCEventListener; import org.schwering.irc.lib.IRCExceptionHandler; import org.schwering.irc.lib.IRCSSLSupport; import org.schwering.irc.lib.IRCTrafficLogger; import org.schwering.irc.lib.IRCUser; import org.schwering.irc.lib.util.IRCModeParser; public class IrcClient { private static final Logger LOGGER = LogManager.getLogger( IrcClient.class ); private static final Map connections = new HashMap<>(); static { new Thread() { public void run() { while ( !Thread.interrupted() ) { try { Thread.sleep( 60000 ); } catch ( InterruptedException e ) { return; } List copy; synchronized ( connections ) { copy = new ArrayList( connections.values() ); } for ( Wrapper c : copy ) { c.ping(); } } }; }.start(); } public static void sendMessage( String serverAddress, String channel, String nick, String message, List outErrors ) { String host = serverAddress; Wrapper wrapper = null; int port = -1; boolean ssl = false; synchronized ( connections ) { wrapper = connections.get( serverAddress ); if ( wrapper == null ) { port = 6667; int i = serverAddress.indexOf( ':', serverAddress.lastIndexOf( ']' ) + 1 ); if ( i != -1 ) { host = serverAddress.substring( 0, i ); if ( i + 1 < serverAddress.length() && serverAddress.charAt( i + 1 ) == '+' ) { ssl = true; i++; } port = Util.parseInt( serverAddress.substring( i + 1 ), 0 ); } if ( Util.isEmpty( host ) ) { outErrors.add( "Host is empty after parsing" ); return; } if ( host.charAt( 0 ) == '[' && host.length() > 2 ) { host = host.substring( 1, host.length() - 1 ); } IRCSSLSupport sslSupport = null; if ( ssl ) { sslSupport = new IRCSSLSupport() { // Return null everywhere so java uses the defaults @Override public TrustManager[] getTrustManagers() { return null; } @Override public SecureRandom getSecureRandom() { return null; } @Override public KeyManager[] getKeyManagers() { return null; } }; } wrapper = new Wrapper( serverAddress ); final String finalHorst = host; IRCConfig config = IRCConfigBuilder.newBuilder().autoPong( true ).encoding( "UTF-8" ).host( host ) .port( port ).nick( nick ).timeout( 70000 ).exceptionHandler( wrapper ).trafficLogger( new IRCTrafficLogger() { private long deadline = System.currentTimeMillis() + 30_000; @Override public void out( String line ) { if ( deadline == 0 ) return; if ( deadline > System.currentTimeMillis() ) { LOGGER.info( "< [" + finalHorst + "] " + line ); } else { deadline = 0; } } @Override public void in( String line ) { if ( deadline == 0 ) return; if ( deadline > System.currentTimeMillis() ) { LOGGER.info( "> [" + finalHorst + "] " + line ); } else { deadline = 0; } } } ) .sslSupport( sslSupport ).build(); IRCConnection connection = IRCConnectionFactory.newConnection( config ); wrapper.setConnection( connection ); connections.put( serverAddress, wrapper ); } } // sync end synchronized ( wrapper ) { if ( !wrapper.connection.isConnected() ) { try { wrapper.channels.clear(); wrapper.connection.connect(); } catch ( KeyManagementException | NoSuchAlgorithmException | IOException e ) { LOGGER.warn( "Cannot connect to " + host + " port " + port + " SSL: " + ssl, e ); outErrors.add( "Cannot connect to " + host + " port " + port + " SSL: " + ssl ); return; } try { Thread.sleep( 5000 ); } catch ( InterruptedException e ) { } } if ( wrapper.connection.isConnected() ) { wrapper.sendMessage( channel, message ); } } } static class Wrapper implements IRCExceptionHandler, IRCEventListener { private IRCConnection connection; private final String host; private final Set channels = new HashSet<>(); public Wrapper( String host ) { this.host = host; } public synchronized void ping() { LOGGER.debug( "Pinging " + host ); try { connection.send( "PING keepalive" ); } catch ( Exception e ) { } } public synchronized void sendMessage( String channel, String message ) { try { if ( !channels.contains( channel.toLowerCase() ) ) { LOGGER.info( "Joining " + channel ); connection.doJoin( channel ); } connection.doPrivmsg( channel, message ); } catch ( Exception e ) { } } public void setConnection( IRCConnection connection ) { if ( this.connection != null ) throw new RuntimeException( "Connection already set" ); this.connection = connection; connection.addIRCEventListener( this ); } /* * IRCExceptionHandler */ @Override public void exception( IRCConnection connection, Throwable exception ) { LOGGER.warn( "IRC Exception for " + host, exception ); if ( exception instanceof IOException ) { connection.close(); } } /* * IRCEventListener */ @Override public void onRegistered() { LOGGER.debug( "Registered" ); } @Override public void onDisconnected() { LOGGER.warn( host + " is dead." ); synchronized ( connections ) { connections.remove( this.host ); } } @Override public void onError( String msg ) { } @Override public void onError( int num, String msg ) { } @Override public void onInvite( String chan, IRCUser user, String passiveNick ) { } @Override public synchronized void onJoin( String chan, IRCUser user ) { if ( user.getNick().equalsIgnoreCase( connection.getNick() ) ) { LOGGER.debug( "I joined " + chan ); channels.add( chan.toLowerCase() ); } } @Override public synchronized void onKick( String chan, IRCUser user, String passiveNick, String msg ) { if ( passiveNick.equalsIgnoreCase( connection.getNick() ) ) { LOGGER.debug( "I was kicked from " + chan ); channels.remove( chan.toLowerCase() ); } } @Override public void onMode( String chan, IRCUser user, IRCModeParser modeParser ) { } @Override public void onMode( IRCUser user, String passiveNick, String mode ) { } @Override public void onNick( IRCUser user, String newNick ) { } @Override public void onNotice( String target, IRCUser user, String msg ) { } @Override public synchronized void onPart( String chan, IRCUser user, String msg ) { if ( user.getNick().equalsIgnoreCase( connection.getNick() ) ) { LOGGER.debug( "I left " + chan ); channels.remove( chan.toLowerCase() ); } } @Override public void onPing( String ping ) { } @Override public void onPrivmsg( String target, IRCUser user, String msg ) { } @Override public void onQuit( IRCUser user, String msg ) { } @Override public void onReply( int num, String value, String msg ) { } @Override public void onTopic( String chan, IRCUser user, String topic ) { } @Override public void unknown( String prefix, String command, String middle, String trailing ) { } } }