/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.integration.security.loginmodules.negotiation; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import javax.security.auth.Subject; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.MessageProp; import org.jboss.as.arquillian.api.ServerSetupTask; import org.jboss.as.arquillian.container.ManagementClient; import org.jboss.as.test.integration.security.common.Krb5LoginConfiguration; import org.jboss.as.test.integration.security.common.Utils; import org.jboss.as.test.integration.security.common.negotiation.KerberosTestUtils; import org.jboss.as.test.shared.TimeoutUtil; import org.jboss.logging.Logger; /** * A sample server application for testing Kerberos identity propagation. * * @author Josef Cacek */ public class GSSTestServer implements ServerSetupTask, Runnable { private static Logger LOGGER = Logger.getLogger(GSSTestServer.class); private static final boolean SKIP_TASK; private static final int ADJUSTED_SECOND = TimeoutUtil.adjust(1000); private volatile boolean serverStarted = false; // Public methods -------------------------------------------------------- /** * Starts instance of this {@link GSSTestServer} in the new Thread. * * @param managementClient * @param containerId * @throws Exception * @see org.jboss.as.arquillian.api.ServerSetupTask#setup(org.jboss.as.arquillian.container.ManagementClient, * java.lang.String) */ public void setup(ManagementClient managementClient, String containerId) throws Exception { // skip server initialization if Kerberos is not able to work correctly. // JUnit's AssumptionViolationException is not handled correctly in ServerSetupTask instances if (SKIP_TASK) { return; } new Thread(this).start(); int i = 0; while (!serverStarted && i < 20) { i++; try { Thread.sleep(ADJUSTED_SECOND); } catch (InterruptedException e) { LOGGER.trace("Interrupted", e); } } final Socket socket = new Socket(); try { LOGGER.debug("Waiting for the GSSTestServer."); socket.connect(new InetSocketAddress(InetAddress.getByName(null), GSSTestConstants.PORT), 20 * ADJUSTED_SECOND); LOGGER.debug("GSSTestServer is up"); } finally { try { socket.close(); } catch (IOException e) { LOGGER.error("Problem occurred during closing socket", e); } } } /** * Stops instance on this {@link GSSTestServer}. * * @param managementClient * @param containerId * @throws Exception * @see org.jboss.as.arquillian.api.ServerSetupTask#tearDown(org.jboss.as.arquillian.container.ManagementClient, * java.lang.String) */ public void tearDown(ManagementClient managementClient, String containerId) throws Exception { if (SKIP_TASK) { return; } stop(); } /** * @see java.lang.Runnable#run() */ public void run() { try { start(); } catch (LoginException e) { LOGGER.error("LoginException: ", e); throw new RuntimeException(e); } catch (PrivilegedActionException e) { LOGGER.error("PrivilegedActionException: ", e); throw new RuntimeException(e); } catch (IOException e) { LOGGER.error("IOException: ", e); throw new RuntimeException(e); } } /** * The Main. It sends stop command to a running {@link GSSTestServer} instance when the first argument provided is "stop", * otherwise it starts a new server instance. * * @param args */ public static void main(String[] args) { if (args.length > 0 && "stop".equals(args[0])) { stop(); } else { final GSSTestServer gssTestServer = new GSSTestServer(); try { gssTestServer.start(); } catch (Exception e) { LOGGER.error("Problem occurred", e); System.exit(1); } } } // Private methods ------------------------------------------------------- /** * Sends STOP ({@link GSSTestConstants#CMD_STOP}) command to a running server. */ private static void stop() { LOGGER.debug("Sending STOP command GSSTestServer."); // Create an unbound socket final Socket socket = new Socket(); try { socket.connect(new InetSocketAddress(InetAddress.getByName(null), GSSTestConstants.PORT), GSSTestConstants.SOCKET_TIMEOUT); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(GSSTestConstants.CMD_STOP); dos.flush(); LOGGER.debug("STOP command sent."); // wait for the GSSServer cleanup Thread.sleep(1000L); } catch (IOException e) { LOGGER.error("Problem occurred during sending stop command", e); } catch (InterruptedException e) { LOGGER.trace("Thread.sleep() interrupted", e); } finally { try { socket.close(); } catch (IOException e) { LOGGER.error("Problem occurred during closing socket", e); } } } /** * Authenticates the server in Kerberos KDC and starts the {@link ServerAction} instance as the authenticated subject. * * @throws LoginException * @throws PrivilegedActionException * @throws IOException */ private void start() throws LoginException, PrivilegedActionException, IOException { LOGGER.debug("Starting GSSTestServer - login"); // Use our custom configuration to avoid reliance on external config final Krb5LoginConfiguration krb5configuration = new Krb5LoginConfiguration(null, null, true, Utils.getLoginConfiguration()); Configuration.setConfiguration(krb5configuration); // 1. Authenticate to Kerberos. final LoginContext lc = Utils.loginWithKerberos(krb5configuration, GSSTestConstants.PRINCIPAL, GSSTestConstants.PASSWORD); LOGGER.debug("Authentication succeed"); // 2. Perform the work as authenticated Subject. final String finishMsg = Subject.doAs(lc.getSubject(), new ServerAction()); LOGGER.trace("Server stopped with result: " + (finishMsg == null ? "OK" : finishMsg)); lc.logout(); krb5configuration.resetConfiguration(); } // Embedded classes ------------------------------------------------------ /** * A ServerAction which creates a ServerSocket and waits for clients. It sends back the authenticated client name. * * @author Josef Cacek */ private class ServerAction implements PrivilegedAction<String> { public String run() { final GSSManager gssManager = GSSManager.getInstance(); ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(GSSTestConstants.PORT); LOGGER.trace("Server started on port " + GSSTestConstants.PORT); int command = GSSTestConstants.CMD_NOOP; serverStarted = true; do { Socket socket = null; GSSContext gssContext = null; try { LOGGER.debug("Waiting for client connection"); socket = serverSocket.accept(); LOGGER.debug("Client connected"); gssContext = gssManager.createContext((GSSCredential) null); final DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); final DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); command = dataInputStream.readInt(); LOGGER.debug("Command code: " + command); if (command == GSSTestConstants.CMD_NAME) { while (!gssContext.isEstablished()) { final byte[] inToken = new byte[dataInputStream.readInt()]; dataInputStream.readFully(inToken); final byte[] outToken = gssContext.acceptSecContext(inToken, 0, inToken.length); if (outToken != null) { dataOutputStream.writeInt(outToken.length); dataOutputStream.write(outToken); dataOutputStream.flush(); } } final String clientName = gssContext.getSrcName().toString(); LOGGER.trace("Context Established with Client " + clientName); // encrypt final MessageProp msgProp = new MessageProp(true); final byte[] clientNameBytes = clientName.getBytes(GSSTestConstants.CHAR_ENC); final byte[] outToken = gssContext.wrap(clientNameBytes, 0, clientNameBytes.length, msgProp); dataOutputStream.writeInt(outToken.length); dataOutputStream.write(outToken); dataOutputStream.flush(); LOGGER.trace("Client name was returned as the token value."); } } catch (EOFException e) { LOGGER.trace("Client didn't send a correct message."); } catch (IOException e) { LOGGER.error("IOException occurred", e); } catch (GSSException e) { LOGGER.error("GSSException occurred", e); } finally { if (gssContext != null) { try { gssContext.dispose(); } catch (GSSException e) { LOGGER.error("Problem occurred during disposing GSS context", e); } } if (socket != null) { try { socket.close(); } catch (IOException e) { LOGGER.error("Problem occurred during closing a Socket", e); } } } } while (command != GSSTestConstants.CMD_STOP); LOGGER.trace("Stop command received."); } catch (IOException e) { LOGGER.error("IOException occurred", e); return e.getMessage(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { LOGGER.error("Problem occurred during closing a ServerSocket", e); } } } return null; } } static { boolean skipTask = false; try { KerberosTestUtils.assumeKerberosAuthenticationSupported(); } catch (Exception e) { skipTask = true; } SKIP_TASK = skipTask; } }