/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.cloud; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Cipher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.api.config.Settings; import org.structr.api.service.Command; import org.structr.api.service.RunnableService; import org.structr.api.service.StructrServices; import org.structr.cloud.message.AuthenticationRequest; import org.structr.cloud.message.Begin; import org.structr.common.SecurityContext; import org.structr.common.error.FrameworkException; /** * The cloud service handles networking between structr instances * * * */ public class CloudService extends Thread implements RunnableService { private static final Logger logger = LoggerFactory.getLogger(CloudService.class.getName()); private static final int DefaultTcpPort = 54555; /** * The CloudService protocol version. Change this when adding new * fields etc., the protocol only works with the exact same * counterpart. */ public static final int PROTOCOL_VERSION = 4; public static final int CHUNK_SIZE = 65536; public static final int BUFFER_SIZE = CHUNK_SIZE * 4; public static final int LIVE_PACKET_COUNT = 200; public static final long AUTH_TIMEOUT = 10000; public static final long DEFAULT_TIMEOUT = 10000; public static final String STREAM_CIPHER = "RC4"; public static boolean DEBUG = false; private ServerSocket serverSocket = null; private boolean running = false; private int tcpPort = DefaultTcpPort; public CloudService() { super("CloudService"); this.setDaemon(true); } @Override public void injectArguments(Command command) { } @Override public void initialize(final StructrServices services) { tcpPort = Settings.TcpPort.getValue(); DEBUG = Settings.getBooleanSetting("sync.debug").getValue(false); } @Override public void initialized() {} @Override public void shutdown() { try { serverSocket.close(); } catch (Throwable t) {} running = false; } @Override public boolean isRunning() { return running; } @Override public void startService() throws Exception { serverSocket = new ServerSocket(tcpPort); running = true; start(); logger.info("CloudService successfully started."); } @Override public void run() { while (running) { try { // start a new thread for the connection new CloudConnection(SecurityContext.getSuperUserInstance(), serverSocket.accept(), null).start(); } catch (IOException ioex) {} } } @Override public void stopService() { shutdown(); } @Override public boolean runOnStartup() { return true; } @Override public boolean isVital() { return false; } // ----- public static methods ----- public static <T> T doRemote(final SecurityContext securityContext, final CloudTransmission<T> transmission, final CloudHost host, final CloudListener listener) throws FrameworkException { CloudConnection<T> client = null; int maxKeyLen = 128; T remoteResult = null; // obtain max. encryption key length try { maxKeyLen = Cipher.getMaxAllowedKeyLength(CloudService.STREAM_CIPHER); } catch (NoSuchAlgorithmException nsaex) { logger.warn("", nsaex); } try { client = new CloudConnection(securityContext, new Socket(host.getHostName(), host.getPort()), listener); client.start(); // notify listener if (listener != null) { listener.transmissionStarted(); } // mark start of transaction client.send(new Begin()); // store password in client client.setPassword(host.getPassword()); // send authentication container client.send(new AuthenticationRequest(host.getUserName(), maxKeyLen)); client.waitForAuthentication(); // do transmission in an authenticated and encrypted context remoteResult = transmission.doRemote(client); // wait for server to close connection here.. client.waitForClose(2000); client.close(); // notify listener if (listener != null) { listener.transmissionFinished(); } } catch (IOException ioex) { logger.warn("", ioex); throw new FrameworkException(504, "Unable to connect to remote server: " + ioex.getMessage()); } finally { if (client != null) { client.close(); } } return remoteResult; } public static byte[] trimToSize(final byte[] source, final int maxKeyLengthBits) { if (maxKeyLengthBits < Integer.MAX_VALUE) { return Arrays.copyOfRange(source, 0, Math.min(source.length, maxKeyLengthBits / 8)); } return source; } }