Package de.anomic.server

Source Code of de.anomic.server.serverCore

// serverCore.java
// -------------------------------------------
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2002-2004
//
// $LastChangedDate: 2011-05-24 23:08:01 +0200 (Di, 24. Mai 2011) $
// $LastChangedRevision: 7737 $
// $LastChangedBy: orbiter $
//
// ThreadPool
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package de.anomic.server;

// standard server
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.ClosedByInterruptException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import net.yacy.cora.document.UTF8;
import net.yacy.cora.protocol.Domains;
import net.yacy.kelondro.logging.Log;
import net.yacy.kelondro.util.ByteBuffer;
import net.yacy.kelondro.workflow.AbstractBusyThread;
import net.yacy.kelondro.workflow.BusyThread;

import de.anomic.tools.PKCS12Tool;

public final class serverCore extends AbstractBusyThread implements BusyThread {

    // special ASCII codes used for protocol handling
    /**
     * Horizontal Tab
     */
    public static final byte HT = 9;
    /**
     * Line Feed
     */
    public static final byte LF = 10;
    /**
     * Carriage Return
     */
    public static final byte CR = 13;
    /**
     * Space
     */
    public static final byte SP = 32;
    /**
     * Line End of HTTP/ICAP headers
     */
    public  static final byte[] CRLF = {CR, LF};
    public  static final String CRLF_STRING = UTF8.String(CRLF);
    public  static final String LF_STRING = UTF8.String(new byte[]{LF});
    public  static final Class<?>[] sessionCallType = {String.class, Session.class}; //  set up some reflection
    public  static final long startupTime = System.currentTimeMillis();
    private static final ThreadGroup sessionThreadGroup = new ThreadGroup("sessionThreadGroup");
    private static final Map<String, Method> commandObjMethodCache = new ConcurrentHashMap<String, Method>(5);
   
    /**
     * will be increased with each session and is used to return a hash code
     */
    static int sessionCounter = 0;
   
    // static variables
    private static final long    keepAliveTimeout     = 60000; // time that a connection is kept alive if requested with a keepAlive statement
    public  static final Boolean TERMINATE_CONNECTION = Boolean.FALSE;
    public  static final Boolean RESUME_CONNECTION    = Boolean.TRUE;
   
    /**
     * for brute-force prevention
     */
    public static final Map<String, Integer> bfHost = new ConcurrentHashMap<String, Integer>();
   
    // class variables
    /**
     * the port, which is visible from outside (in most cases bind-port)
     */
    private String extendedPort;
    /**
     * if set, yacy will bind to this port, but set extendedPort in the seed
     */
    private String bindPort;
    /**
     * specifies if the server should try to do a restart
     */
    boolean forceRestart = false;
   
    public static boolean useStaticIP = false;
    protected Log log;
    private SSLSocketFactory sslSocketFactory = null;
    private ServerSocket socket;           // listener
    private final int timeout;             // connection time-out of the socket
    serverHandler handlerPrototype;        // the command class (a serverHandler)

    private final serverSwitch switchboard;   // the command class switchboard
    private Map<String, String> denyHost;
    int commandMaxLength;
    private int maxBusySessions;
    private long lastAutoTermination;
   
    public final void terminateOldSessions(long minage) {
        if (System.currentTimeMillis() - lastAutoTermination < 3000) return;
        this.lastAutoTermination = System.currentTimeMillis();
        //if (serverCore.getJobCount() < maxBusySessions - 10) return; // don't panic
       
        for (Session s: getJobList()) {
            if (!s.isAlive()) continue;
            if (s.getTime() < minage) continue;
           
            // stop thread
            this.log.logInfo("check for " + s.getName() + ": " + s.getTime() + " ms alive, stopping thread");
           
            // trying to stop session
            s.setStopped(true);
            try { Thread.sleep(10); } catch (final InterruptedException ex) {}
           
            // trying to interrupt session
            synchronized (s) {
                s.notify();
            }
            s.interrupt();
           
            // trying to close socket
            if (s.isAlive()) {
                s.close();
            }
        }
    }
   
    public static String clientAddress(final Socket s) {
        final InetAddress uAddr = s.getInetAddress();
        if (uAddr.isAnyLocalAddress()) return "127.0.0.1";
        String cIP = uAddr.getHostAddress();
        if (Domains.isLocal(cIP)) cIP = "127.0.0.1";
        return cIP;
    }

    // class initializer
    public serverCore(
            final int timeout,
            final boolean blockAttack,
            final serverHandler handlerPrototype,
            final serverSwitch switchboard,
            final int commandMaxLength
    ) {
        super(Long.MIN_VALUE, Long.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE);
        this.timeout = timeout;
       
        this.commandMaxLength = commandMaxLength;
        this.denyHost = (blockAttack) ? new ConcurrentHashMap<String, String>() : null;
        this.handlerPrototype = handlerPrototype;
        this.switchboard = switchboard;
       
        // initialize logger
        this.log = new Log("SERVER");

        // init the ssl socket factory
        this.sslSocketFactory = initSSLFactory();

        // init session parameter
        this.maxBusySessions = Math.max(1, Integer.parseInt(switchboard.getConfig("httpdMaxBusySessions","100")));
       
        this.lastAutoTermination = System.currentTimeMillis();
       
        // init servercore
        init();
    }
   
    public boolean withSSL() {
        return this.sslSocketFactory != null;
    }
   
    public synchronized void init() {
        this.log.logInfo("Initializing serverCore ...");
       
        // read some config values
        this.extendedPort = this.switchboard.getConfig("port", "8090").trim();
        this.bindPort = this.switchboard.getConfig("bindPort", "").trim();
       
        // Open a new server-socket channel
        try {
            // bind the ServerSocket to a specific address
            // InetSocketAddress bindAddress = null;
            this.socket = new ServerSocket();
            if (bindPort == null || bindPort.equals("")) {
                this.log.logInfo("Trying to bind server to port " + extendedPort);
                this.socket.bind(/*bindAddress = */generateSocketAddress(extendedPort));
            } else { //bindPort set, use another port to bind than the port reachable from outside
                this.log.logInfo("Trying to bind server to port " + bindPort+ " with "+ extendedPort + "as seedPort.");
                this.socket.bind(/*bindAddress = */generateSocketAddress(bindPort));
            }
            // updating the port information
            //yacyCore.seedDB.mySeed.put(yacySeed.PORT,Integer.toString(bindAddress.getPort()));   
            //yacyCore.seedDB.mySeed().put(yacySeed.PORT, extendedPort);
        } catch (final Exception e) {
            final String errorMsg = "FATAL ERROR: " + e.getMessage() + " - probably root access rights needed. check port number";
            this.log.logSevere(errorMsg);
            System.out.println(errorMsg);            
            System.exit(0);
        }
    }
   
    public static int getPortNr(String extendedPortString) {
        int pos = -1;
        if ((pos = extendedPortString.indexOf(':'))!= -1) {
            extendedPortString = extendedPortString.substring(pos+1);
        }
        return Integer.parseInt(extendedPortString);        
    }
   
    public InetSocketAddress generateSocketAddress(String extendedPortString) throws SocketException {
       
        // parsing the port configuration
        String bindIP = null;
        int bindPort;
       
        int pos = -1;
        if ((pos = extendedPortString.indexOf(':'))!= -1) {
            bindIP = extendedPortString.substring(0,pos).trim();
            extendedPortString = extendedPortString.substring(pos+1);
           
            if (bindIP.length() > 0 && bindIP.charAt(0) == '#') {
                final String interfaceName = bindIP.substring(1);
                String hostName = null;
                if (this.log.isFine()) this.log.logFine("Trying to determine IP address of interface '" + interfaceName + "'.");                   

                final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
                if (interfaces != null) {
                    while (interfaces.hasMoreElements()) {
                        final NetworkInterface interf = interfaces.nextElement();
                        if (interf.getName().equalsIgnoreCase(interfaceName)) {
                            final Enumeration<InetAddress> addresses = interf.getInetAddresses();
                            if (addresses != null) {
                                while (addresses.hasMoreElements()) {
                                    final InetAddress address = addresses.nextElement();
                                    if (address instanceof Inet4Address) {
                                        hostName = address.getHostAddress();
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                if (hostName == null) {
                    this.log.logWarning("Unable to find interface with name '" + interfaceName + "'. Binding server to all interfaces");
                    bindIP = null;
                } else {
                    this.log.logInfo("Binding server to interface '" + interfaceName + "' with IP '" + hostName + "'.");
                    bindIP = hostName;
                }
            }
        }
        bindPort = Integer.parseInt(extendedPortString);   
       
        return (bindIP == null)
        ? new InetSocketAddress(bindPort)
        : new InetSocketAddress(bindIP, bindPort);
    }
   
    public void open() {
        this.log.logConfig("* server started on " + Domains.myPublicLocalIP() + ":" + this.extendedPort);
    }
   
    public void freemem() {
        // FIXME: can we something here to flush memory? Idea: Reduce the size of some of our various caches.
    }
   
    // class body
    public boolean job() throws Exception {
        try {
            // prepare for new connection
            // idleThreadCheck();
            int jobCount = getJobCount();
            this.switchboard.handleBusyState(jobCount);
            if (this.log.isFinest()) this.log.logFinest("* waiting for connections, " + jobCount + " sessions running");
           
            announceThreadBlockApply();
           
            // wait for new connection
            Socket controlSocket = this.socket.accept();
           
            announceThreadBlockRelease();
           
            if (jobCount >= this.maxBusySessions) {
                terminateOldSessions(3000);
                this.log.logInfo("termination of old sessions: before = " + jobCount + ", after = " + getJobCount());
                //if (getJobCount() < this.maxBusySessions) break;
                //if (trycount++ > 5) break;
                //Thread.sleep(1000); // lets try again after a short break
            }
            /*
            if (getJobCount() >= this.maxBusySessions) {
                // immediately close connection if too much sessions are still running
                this.log.logWarning("* connections (" + getJobCount() + ") exceeding limit (" + this.maxBusySessions + ")" + ", closing new incoming connection from "+ controlSocket.getRemoteSocketAddress());
               
                controlSocket.close();
                return false;
               
            }
            */
           
            final String cIP = clientAddress(controlSocket);
            //System.out.println("server bfHosts=" + bfHost.toString());
            /*
            if (bfHost.get(cIP) != null) {
                Integer attempts = bfHost.get(cIP);
                if (attempts == null) attempts = Integer.valueOf(1); else attempts = Integer.valueOf(attempts.intValue() + 1);
                bfHost.put(cIP, attempts);
                this.log.logWarning("SLOWING DOWN ACCESS FOR BRUTE-FORCE PREVENTION FROM " + cIP + ", ATTEMPT " + attempts.intValue());
                // add a delay to make brute-force harder
                announceThreadBlockApply();
                try {Thread.sleep(attempts.intValue());} catch (final InterruptedException e) {}
                announceThreadBlockRelease();
                if ((attempts.intValue() >= 10) && (this.denyHost != null)) {
                    this.denyHost.put(cIP, "deny");
                }
            }
            */
           
            if ((this.denyHost == null) || (this.denyHost.get(cIP) == null)) {
                // setting the timeout properly
                assert this.timeout >= 1000;
                controlSocket.setSoTimeout(this.timeout);
               
                // wrap this socket
                if (this.sslSocketFactory != null) {
                    controlSocket = new serverCoreSocket(controlSocket);

                    // if the current connection is SSL we need to do a handshake
                    if (((serverCoreSocket)controlSocket).isSSL()) {               
                        controlSocket = negotiateSSL(controlSocket);   
                    }           
                }
                // keep-alive: if set to true, the server frequently sends keep-alive packets to the client which the client must respond to
                // we set this to false to prevent that a missing ack from the client forces the server to close the connection
                controlSocket.setKeepAlive(false);
               
                // disable Nagle's algorithm (waiting for more data until packet is full)
                // controlSocket.setTcpNoDelay(true);
               
                // set a non-zero linger, that means that a socket.close() blocks until all data is written
                controlSocket.setSoLinger(false, this.timeout);
               
                // ensure that MTU-48 is not exceeded to prevent that routers cannot handle large data packets
                // read http://www.cisco.com/warp/public/105/38.shtml for explanation
                //controlSocket.setSendBufferSize(1440);
                //controlSocket.setReceiveBufferSize(1440);
               
                // create session
                final Session connection = new Session(sessionThreadGroup, controlSocket, this.timeout);
                //terminateOldSessions(60000);
                connection.start();
            } else {
                this.log.logWarning("ACCESS FROM " + cIP + " DENIED");
            }
           
            return true;
        } catch (final SocketException e) {
            if (this.forceRestart) {
                // reinitialize serverCore
                init();
                this.forceRestart = false;
                return true;
            }
            throw e;
        }
    }

    public synchronized void close() {
        // consuming the isInterrupted Flag. Otherwise we could not properly close the session pool
        Thread.interrupted();
       
        // shut down all busySessions
        for (final Session session: getJobList()) {
            if (session == null) continue;
            try {
                session.interrupt();
            } catch (final SecurityException e ) {
                Log.logException(e);
            } catch (final ConcurrentModificationException e) {
                Log.logException(e);
            }
        }
       
        // close the serverchannel and socket
        try {
            this.log.logInfo("Closing server socket ...");
            this.socket.close();
        } catch (final Exception e) {
            this.log.logWarning("Unable to close the server socket.");
        }

        // close all sessions
        this.log.logInfo("Closing server sessions ...");
        for (final Session session: getJobList()) {
            session.interrupt();
            //session.close();
        }
       
        this.log.logConfig("* terminated");
    }
   
    public List<Session> getJobList() {
        final Thread[] threadList = new Thread[sessionThreadGroup.activeCount()];    
        serverCore.sessionThreadGroup.enumerate(threadList, false);
        ArrayList<Session> l = new ArrayList<Session>();
        for (Thread t: threadList) {
            if (t == null) continue;
            if (!(t instanceof Session)) {
                //log.logSevere("serverCore.getJobList - thread is not Session: " + t.getClass().getName());
                continue;
            }
            l.add((Session) t);
        }
        return l;
    }
   
    public int getJobCount() {
        final Thread[] threadList = new Thread[sessionThreadGroup.activeCount()];    
        serverCore.sessionThreadGroup.enumerate(threadList, false);
        int c = 0;
        for (Thread t: threadList) {
            if (t == null) continue;
            if (!(t instanceof Session)) {
                //log.logSevere("serverCore.getJobList - thread is not Session: " + t.getClass().getName());
                continue;
            }
            c++;
        }
        return c;
    }

    // idle sensor: the thread is idle if there are no sessions running
    public boolean idle() {
        // idleThreadCheck();
        final Thread[] threadList = new Thread[sessionThreadGroup.activeCount()];    
        serverCore.sessionThreadGroup.enumerate(threadList, false);
        for (Thread t: threadList) {
            if (t == null) continue;
            if (!(t instanceof Session)) {
                //log.logSevere("serverCore.getJobList - thread is not Session: " + t.getClass().getName());
                continue;
            }
            return false;
        }
        return true;
        //return (getJobCount() == 0);
    }
   
    public int getMaxSessionCount() {
        return this.maxBusySessions;
    }
   
    public void setMaxSessionCount(final int count) {
        this.maxBusySessions = count;
    }

    public final class Session extends Thread {

        //boolean destroyed = false;
        private boolean runningsession = false;
        private boolean stopped = false;
       
        private long start;                // startup time
        private serverHandler commandObj;
       
      private String request;            // current command line
      private int commandCounter;        // for logging: number of commands in this session
      private String identity;           // a string that identifies the client (i.e. ftp: account name)
      //private boolean promiscuous;       // if true, no lines are read and streams are only passed
       
      public  Socket controlSocket;      // dialog socket
      public  InetAddress userAddress;   // the address of the client
        public  int userPort;              // the ip port used by the client
      public  PushbackInputStream in;    // on control input stream
      public  OutputStream out;          // on control output stream, auto-flush
        public  int socketTimeout;
        public  int hashIndex;

      public Session(final ThreadGroup theThreadGroup, final Socket controlSocket, final int socketTimeout) {
            super(theThreadGroup, controlSocket.getInetAddress().toString() + "@" + Long.toString(System.currentTimeMillis()));
            this.setPriority(Thread.MAX_PRIORITY);
            this.socketTimeout = socketTimeout;
            this.controlSocket = controlSocket;
            this.hashIndex = sessionCounter;
            sessionCounter++;
        }

        public int hashCode() {
            // return a hash code so it is possible to store objects of httpc objects in a HashSet
            return this.hashIndex;
        }
     
        public int getCommandCount() {
            return this.commandCounter;
        }
       
        public String getCommandLine() {
            return this.request;
        }
       
        public serverHandler getCommandObj() {
            return this.commandObj;
        }
       
        public InetAddress getUserAddress() {
            return this.userAddress;
        }
       
        public int getUserPort() {
            return this.userPort;
        }
       
        public void setStopped(final boolean stopped) {
            this.stopped = stopped;           
        }
       
        public boolean isStopped() {
            return this.stopped;
        }
       
        public void close() {
            // closing the socket to the client
            if (this.controlSocket != null) try {
                this.controlSocket.close();
                log.logInfo("Closing main socket of thread '" + this.getName() + "'");
                this.controlSocket = null;
            } catch (final Exception e) {}
        }
       
        public long getRequestStartTime() {
            return this.start;
        }
       
        public long getTime() {
            return System.currentTimeMillis() - this.start;
        }
           
      public void setIdentity(final String id) {
          this.identity = id;
      }
   
      public void log(final boolean outgoing, final String request) {
          if (log.isFine()) log.logFine(this.userAddress.getHostAddress() + "/" + this.identity + " " +
             "[" + getJobCount() + ", " + this.commandCounter +
             ((outgoing) ? "] > " : "] < ") +
             request);
      }
   
      public void writeLine(final String messg) throws IOException {
          send(this.out, messg);
          log(true, messg);
      }
   
      public byte[] readLine() {
          return receive(this.in, commandMaxLength, true);
      }
   
        /**
         * reads a line from the input socket
         * this function is provided by the server through a passed method on initialization
         * @return the next requestline as string
         */
        public String readLineAsString() {
            final byte[] l = readLine();
            return (l == null) ? null: UTF8.String(l);
        }
   
        /**
         * @return whether the {@link Thread} is currently running
         */
        public boolean isRunning() {
            return this.runningsession;
        }
       
        /**
         *
         *
         * @see java.lang.Thread#run()
         */
        public void run()  {
            this.runningsession = true;
           
            try {
                // setting the session startup time
                this.start = System.currentTimeMillis();                
               
                // set the session identity
                this.identity = "-";
               
                // getting some client information
                this.userAddress = this.controlSocket.getInetAddress();
                this.userPort = this.controlSocket.getPort();
                this.setName("Session_" + this.userAddress.getHostAddress() + ":" + this.controlSocket.getPort());
               
                // TODO: check if we want to allow this socket to connect us
               
                // getting input and output stream for communication with client
                if (this.controlSocket.getInputStream() instanceof PushbackInputStream) {
                    this.in = (PushbackInputStream) this.controlSocket.getInputStream();
                } else {
                    this.in = new PushbackInputStream(this.controlSocket.getInputStream());
                }
                this.out = this.controlSocket.getOutputStream();

                // reseting the command counter
                this.commandCounter = 0;
               
                // listen for commands
                listen();
            } catch (final IOException e) {
                Log.logException(e);
            } catch (final Exception e) {
                Log.logException(e);
            } finally {
                try {
                    if ((this.controlSocket != null) && (! this.controlSocket.isClosed())) {
                        // flush data
                        this.out.flush();
                   
                        // maybe this doesn't work for all SSL socket implementations
                        if (!(this.controlSocket instanceof SSLSocket)) {
                            this.controlSocket.shutdownInput();
                            this.controlSocket.shutdownOutput();
                        }
                   
                        // close streams
                        this.in.close();                   
                        this.out.close();                  
                   
                        // close everything                   
                        this.controlSocket.close();
                    }
                } catch (final IOException e) {
                    Log.logException(e);
                } finally {
                    this.controlSocket = null;
                }
            }
           
        }
       
        private void listen() {
            try {
                // start dialog
                byte[] requestBytes = null;
                boolean terminate = false;
                String reqCmd;
                String reqProtocol = "HTTP";
                long situationDependentKeepAliveTimeout = keepAliveTimeout;
                while (this.in != null &&
                       this.controlSocket != null &&
                       this.controlSocket.isConnected() &&
                       (this.commandCounter == 0 || System.currentTimeMillis() - this.start < situationDependentKeepAliveTimeout) &&
                       (requestBytes = readLine()) != null) {
                    this.setName("Session_" + this.userAddress.getHostAddress() +
                            ":" + this.controlSocket.getPort() +
                            "#" + this.commandCounter);
                   
                    this.request = UTF8.String(requestBytes);
                    //this.log.logDebug("* session " + handle + " received command '" + request + "'. time = " + (System.currentTimeMillis() - handle));
                    log(false, this.request);
                    try {                       
                        // if we can not determine the proper command string we try to call function emptyRequest
                        // of the commandObject
                        if (this.request.trim().length() == 0) this.request = "EMPTY";
                       
                        final Object[] parameter = new Object[2];
                       
                        // get the rest of the request parameters
                        final int pos = this.request.indexOf(' ');
                        if (pos < 0) {
                            reqCmd = this.request.trim().toUpperCase();
                            parameter[0] = "";
                        } else {
                            reqCmd = this.request.substring(0, pos).trim().toUpperCase();
                            parameter[0] = this.request.substring(pos).trim();
                        }
                        parameter[1] = this;
                       
                        // now we need to initialize the session
                        if (this.commandCounter == 0) {
                            // first we need to determine the proper protocol handler
                            if (this.request.indexOf("HTTP") >= 0)          reqProtocol = "HTTP";
                            else                                            reqProtocol = null;                           
                           
                            if (this.request == null) break;
                            if (reqProtocol != null && reqProtocol.equals("HTTP")) {
                                this.commandObj = handlerPrototype.clone();
                            }
                        }
                       
                        // count the amount of requests that were processed by this session until yet
                        this.commandCounter++;
                       
                        // setting the socket timeout for reading of the request content
                        this.controlSocket.setSoTimeout(this.socketTimeout);
                       
                        // exec command and return value
                        Method commandMethod = commandObjMethodCache.get(reqProtocol + "_" + reqCmd);
                        if (commandMethod == null) {
                            try {
                                commandMethod = this.commandObj.getClass().getMethod(reqCmd, sessionCallType);
                                commandObjMethodCache.put(reqProtocol + "_" + reqCmd, commandMethod);
                            } catch (final NoSuchMethodException noMethod) {
                                commandMethod = this.commandObj.getClass().getMethod("UNKNOWN", sessionCallType);
                                parameter[0] = this.request.trim();
                            }
                        }
                       
                        Object result = null;
                        try {
                            result = commandMethod.invoke(this.commandObj, parameter);
                        } catch (OutOfMemoryError e) {
                            log.logWarning("commandMethod.invoke: OutOfMemoryError / 1 (retry1 follows)");
                            // try again
                            terminateOldSessions(2000);
                            try {
                                result = commandMethod.invoke(this.commandObj, parameter);
                            } catch (OutOfMemoryError e2) {
                                log.logWarning("commandMethod.invoke: OutOfMemoryError / 2 (retry2 follows)");
                                // try again
                                Thread.sleep(1000);
                                result = commandMethod.invoke(this.commandObj, parameter);
                            }
                        }
                       
                        //announceMoreExecTime(commandStart - System.currentTimeMillis()); // shall be negative!
                        //this.log.logDebug("* session " + handle + " completed command '" + request + "'. time = " + (System.currentTimeMillis() - handle));
                        this.out.flush();
                        if (result != null) {
                            if (result instanceof Boolean) {
                                if (((Boolean) result).equals(TERMINATE_CONNECTION)) break;
                               
                                /*
                                 * setting timeout to a very high level.
                                 * this is needed because of persistent connection
                                 * support.
                                 */
                                if (!this.controlSocket.isClosed()) this.controlSocket.setSoTimeout(30*60*1000);
                            } else if (result instanceof String) {
                                if (((String) result).startsWith("!")) {
                                    result = ((String) result).substring(1);
                                    terminate = true;
                                }
                                writeLine((String) result);
                            } else if (result instanceof InputStream) {
                                String tmp = send(this.out, (InputStream) result);
                                if ((tmp.length() > 4) && (tmp.toUpperCase().startsWith("PASS"))) {
                                    log(true, "PASS ********");
                                } else {
                                    log(true, tmp);
                                }
                                tmp = null;
                            }
                        }
                        if (terminate) break;
                       
                       
                    } catch (final InvocationTargetException e) {
                        log.logSevere("command execution, target exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
                        // we extract a target exception
                        writeLine(this.commandObj.error(e.getTargetException()));
                        break;
                    } catch (final NoSuchMethodException e) {
                        log.logSevere("command execution, method exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
                        if (!this.userAddress.isSiteLocalAddress()) {
                            if (denyHost != null) {
                                denyHost.put(this.userAddress.getHostAddress(), "deny"); // block client: hacker attempt
                            }
                        }
                        break;
                    } catch (final IllegalAccessException e) {
                        log.logSevere("command execution, illegal access exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
                        // wrong parameters: this can only be an internal problem
                        writeLine(this.commandObj.error(e));
                        break;
                    } catch (final ClassCastException e) {
                        log.logSevere("command execution, cast exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
                        // ??
                        writeLine(this.commandObj.error(e));
                        break;
                    } catch (final Exception e) {
                        log.logSevere("command execution, generic exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
                        // whatever happens: the thread has to survive!
                        writeLine("UNKNOWN REASON:" + ((this.commandObj == null) ? "no command object" : this.commandObj.error(e)));
                        break;
                    }
                    // check if we should still keep this alive:
                    break; // no more keep-alive, not needed for speed and causes only trouble
                    /*
                    if (getJobCount() > maxBusySessions / 2) break;
                    // the more connections are alive, the shorter the keep alive timeout
                    situationDependentKeepAliveTimeout = keepAliveTimeout / Math.max(1, getJobCount() - 20);
                    */
                } // end of while
            } catch (final IOException e) {
                log.logSevere("command execution, IO exception " + e.getMessage() + " for client " + this.userAddress.getHostAddress(), e);
            }
            //announceMoreExecTime(System.currentTimeMillis() - this.start);
        }

        public boolean isSSL() {
            return this.controlSocket != null && this.controlSocket instanceof SSLSocket;
        }
       
    }

    /**
     * Read a line from a protocol stream (HTTP/ICAP) and do some
     * pre-processing (check validity, strip line endings).
     * <br>
     * Illegal control characters will be stripped from the result.
     * Besides the valid line ending CRLF a single LF is treated as a
     * line ending as well to avoid errors with buggy server.
     *
     * @param pbis    The stream to read from.
     * @param maxSize maximum number of bytes to read in one run.
     * @param logerr  log error messages if true, be silent otherwise.
     *
     * @return A byte array representing one line of the input or
     *         <code>null</null> if EOS reached.
     */
    public static byte[] receive(final PushbackInputStream pbis, final int maxSize, final boolean logerr) {

        // reuse an existing linebuffer
        final ByteBuffer readLineBuffer = new ByteBuffer(80);

        int bufferSize = 0, b = 0;
        try {
            // catch bytes until line end or illegal character reached or buffer full
            // resulting readLineBuffer doesn't include CRLF or illegal control chars
            while (bufferSize < maxSize) {
                b = pbis.read();
           
                if ((b > 31 && b != 127) || b == HT) {
                    // add legal chars to the result
                    readLineBuffer.append(b);
                    bufferSize++;
                } else if (b == CR) {
                    // possible beginning of CRLF, check following byte
                    b = pbis.read();
                    if (b == LF) {
                        // line end caught: break the loop
                        break;
                    } else if (b >= 0) {
                        // no line end: push back the byte, ignore the CR
                        pbis.unread(b);
                    }
                } else if (b == LF || b < 0) {
                    // LF without precedent CR: treat as line end of broken servers
                    // b < 0: EOS
                    break;
                }
            }

            // EOS
            if (bufferSize == 0 && b == -1) return null;
            return readLineBuffer.getBytes();
        } catch (final ClosedByInterruptException e) {
            if (logerr) Log.logWarning("SERVER", "receive interrupted");
            return null;           
        } catch (final IOException e) {
            String message = e.getMessage();
            if (logerr && !message.equals("Socket closed") && !message.equals("Connection reset") && !message.equals("Read timed out")) Log.logWarning("SERVER", "receive closed by IOException: " + e.getMessage());
            return null;
        } finally {
          try {
        readLineBuffer.close();
      } catch (IOException e) {
          Log.logException(e);
      }
        }
    }

    public static void send(final OutputStream os, final String buf) throws IOException {
      os.write(UTF8.getBytes(buf));
      os.write(CRLF);
      os.flush();
    }
   
    public static void send(final OutputStream os, final byte[] buf) throws IOException {
      os.write(buf);
      os.write(CRLF);
      os.flush();
    }
       
    public static String send(final OutputStream os, final InputStream is) throws IOException {
      final int bufferSize = is.available();
      final byte[] buffer = new byte[((bufferSize < 1) || (bufferSize > 4096)) ? 4096 : bufferSize];
      int l;
      while ((l = is.read(buffer)) > 0) {os.write(buffer, 0, l);}
      os.write(CRLF);
      os.flush();
      if (bufferSize > 80) return "<LONG STREAM>";
      return UTF8.String(buffer);
    }
   
    public static final void checkInterruption() throws InterruptedException {
        final Thread currentThread = Thread.currentThread();
        if (currentThread.isInterrupted()) throw new InterruptedException()
        if ((currentThread instanceof serverCore.Session) && ((serverCore.Session)currentThread).isStopped()) throw new InterruptedException();
    }
   
    public void reconnect(final int delay) {
        final Thread restart = new Restarter(delay);
        restart.start();
    }
   
    // restarting the serverCore
    public class Restarter extends Thread {
        public serverCore theServerCore = null;
        public int delay = 5000;
        public Restarter(final int delay) {
            this.delay = delay;
        }
        public void run() {
            // waiting for a while
            try {
                Thread.sleep(delay);
            } catch (final InterruptedException e) {
                Log.logException(e);
            } catch (final Exception e) {
                Log.logException(e);
            }
           
            // signaling restart
            forceRestart = true;
           
            // closing socket to notify the thread
            close();
        }
    }
   
    private SSLSocketFactory initSSLFactory() {
       
        // getting the keystore file name
        String keyStoreFileName = this.switchboard.getConfig("keyStore", "").trim();       
       
        // getting the keystore pwd
        final String keyStorePwd = this.switchboard.getConfig("keyStorePassword", "").trim();
       
        // take a look if we have something to import
        final String pkcs12ImportFile = this.switchboard.getConfig("pkcs12ImportFile", "").trim();
        if (pkcs12ImportFile.length() > 0) {
            this.log.logInfo("Import certificates from import file '" + pkcs12ImportFile + "'.");
           
            try {
                // getting the password
                final String pkcs12ImportPwd = this.switchboard.getConfig("pkcs12ImportPwd", "").trim();

                // creating tool to import cert
                final PKCS12Tool pkcsTool = new PKCS12Tool(pkcs12ImportFile,pkcs12ImportPwd);

                // creating a new keystore file
                if (keyStoreFileName.length() == 0) {
                    // using the default keystore name
                    keyStoreFileName = "DATA/SETTINGS/myPeerKeystore";
                   
                    // creating an empty java keystore
                    final KeyStore ks = KeyStore.getInstance("JKS");
                    ks.load(null,keyStorePwd.toCharArray());
                    final FileOutputStream ksOut = new FileOutputStream(keyStoreFileName);
                    ks.store(ksOut, keyStorePwd.toCharArray());
                    ksOut.close();
                   
                    // storing path to keystore into config file
                    this.switchboard.setConfig("keyStore", keyStoreFileName);
                }

                // importing certificate
                pkcsTool.importToJKS(keyStoreFileName, keyStorePwd);
               
                // removing entries from config file
                this.switchboard.setConfig("pkcs12ImportFile", "");
                this.switchboard.setConfig("keyStorePassword", "");
               
                // deleting original import file
                // TODO: should we do this
               
            } catch (final Exception e) {
                this.log.logSevere("Unable to import certificate from import file '" + pkcs12ImportFile + "'.",e);
            }
        } else if (keyStoreFileName.length() == 0) return null;
       
       
        // get the ssl context
        try {
            this.log.logInfo("Initializing SSL support ...");
           
            // creating a new keystore instance of type (java key store)
            if (this.log.isFine()) this.log.logFine("Initializing keystore ...");
            final KeyStore ks = KeyStore.getInstance("JKS");
           
            // loading keystore data from file
            if (this.log.isFine()) this.log.logFine("Loading keystore file " + keyStoreFileName);
            final FileInputStream stream = new FileInputStream(keyStoreFileName);           
            ks.load(stream, keyStorePwd.toCharArray());
            stream.close();
           
            // creating a keystore factory
            if (this.log.isFine()) this.log.logFine("Initializing key manager factory ...");
            final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks,keyStorePwd.toCharArray());
           
            // initializing the ssl context
            if (this.log.isFine()) this.log.logFine("Initializing SSL context ...");
            final SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(kmf.getKeyManagers(), null, null);
           
            final SSLSocketFactory factory = sslcontext.getSocketFactory();
            this.log.logInfo("SSL support initialized successfully");
            return factory;
        } catch (final Exception e) {
            final String errorMsg = "FATAL ERROR: Unable to initialize the SSL Socket factory. " + e.getMessage();
            this.log.logSevere(errorMsg);
            System.out.println(errorMsg);            
            System.exit(0);
            return null;
        }
    }
   
    public Socket negotiateSSL(final Socket sock) throws Exception {

        SSLSocket sslsock;
       
        try {
            sslsock=(SSLSocket)this.sslSocketFactory.createSocket(
                    sock,
                    sock.getInetAddress().getHostAddress(),
                    sock.getPort(),
                    true);

            sslsock.addHandshakeCompletedListener(
                    new HandshakeCompletedListener() {
                       public void handshakeCompleted(
                          final HandshakeCompletedEvent event) {
                          System.out.println("Handshake finished!");
                          System.out.println(
                          "\t CipherSuite:" + event.getCipherSuite());
                          System.out.println(
                          "\t SessionId " + event.getSession());
                          System.out.println(
                          "\t PeerHost " + event.getSession().getPeerHost());
                       }
                    }
                 );            
           
            sslsock.setUseClientMode(false);
            final String[] suites = sslsock.getSupportedCipherSuites();
            sslsock.setEnabledCipherSuites(suites);
//            start handshake
            sslsock.startHandshake();
           
            //String cipherSuite = sslsock.getSession().getCipherSuite();
           
            return sslsock;
        } catch (final Exception e) {
            throw e;
        }
    }   
}
TOP

Related Classes of de.anomic.server.serverCore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.