com.google.dataconnector.util.Rfc1929SdcAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dataconnector.util.Rfc1929SdcAuthenticator.java

Source

/* Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */

package com.google.dataconnector.util;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;

import net.sourceforge.jsocks.socks.ProxyMessage;
import net.sourceforge.jsocks.socks.server.ServerAuthenticator;
import net.sourceforge.jsocks.socks.server.ServerAuthenticatorNone;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * This class implements both authentication and Layer 4 IP rule enforcement for incoming SOCKS5 
 * connections.  It uses the RFC1929 specification however the user name is a JSON packet used a 
 * log vehicle, while the password is used a key that only allows access to the specified rule 
 * sets in the configuration.
 * 
 * this extends {@link ServerAuthenticatorNone} because all we really want from the superclass
 * is one single method {@link ServerAuthenticatorNone#selectSocks5Authentication}.
 * 
 * @author rayc@google.com (Ray Colline)
 * @author vnori@google.com (Vasu Nori)
 */
public class Rfc1929SdcAuthenticator extends ServerAuthenticatorNone {

    private static final Logger LOG = Logger.getLogger(Rfc1929SdcAuthenticator.class);

    // RFC1929 Method ID
    static final int METHOD_ID = 2;

    private String passKey; // RFC1929 password used to lookup rule set in the map.
    private String serverMetaData; // Raw JSON string from server

    /** injected dependency */
    private final SdcKeysManager keyManager;

    /** 
     * a constructor solely for Guice injection use and to get {@link SdcKeysManager}
     * initialized.
     */
    @Inject
    public Rfc1929SdcAuthenticator(final SdcKeysManager keyManager) {
        this.keyManager = keyManager;
    }

    /**
     * Used to create instances returned from startSession in JSOCKS server..
     */
    Rfc1929SdcAuthenticator(final InputStream in, final OutputStream out, final String passKey,
            final SdcKeysManager keyManager, final String userLogLine) {
        super(in, out);

        this.passKey = passKey;
        this.keyManager = keyManager;
        this.serverMetaData = userLogLine;
    }

    /**
     * Checks the destination IP and port specified in the {@link ProxyMessage} against the allowed 
     * IP:Port pair for this connection.  If its valid allow the connection to continue.  Reject all
     * non SOCKS 5 requests.  We also log  any cloud provided data if we have any.
     * 
     * @param msg the JSOCKS msg object representing the SOCKS connection parameters.
     * @returns true if request is allowed or false if its prevented by ruleset.
     */
    @Override
    public boolean checkRequest(final ProxyMessage msg) {
        Preconditions.checkNotNull(keyManager);

        // This shouldn't happen but we should check anyways.
        if (msg.version != 5) {
            return false;
        }

        try {
            final JSONObject serverMetadataJson = new JSONObject(serverMetaData);
            final String name = serverMetadataJson.getString("name");
            final String resource = serverMetadataJson.getString("resource");
            final String user = serverMetadataJson.getString("user");
            final String appId = serverMetadataJson.getString("appId");
            LOG.info(msg.getConnectionId() + " Incoming connection for rule id:" + name + " for resource:"
                    + resource + " cloud-user:" + user + " reported-appId:" + appId);
        } catch (JSONException e) {
            LOG.info(msg.getConnectionId() + " Cloud did not report metadata (old cloud clients?)");
        }

        // Is this a valid "secret key"
        final boolean rslt = keyManager.checkKeyIpPort(passKey, msg.host, msg.port);
        if (!rslt) {
            LOG.info("No key found. Rejecting access to " + msg.host + ":" + msg.port);
        }
        return rslt;
    }

    /**
     * Reads the authentication data from the session and validates request
     * 
     * @param s connected socket from incoming SOCKS client.
     * @throws IOException if there are any socket communication issues.
     */
    @Override
    public ServerAuthenticator startSession(final Socket s) throws IOException {
        final InputStream in = s.getInputStream();
        final OutputStream out = s.getOutputStream();

        if (in.read() != 5) {
            // Drop non version 5 messages.
            return null;
        }

        if (!selectSocks5Authentication(in, out, METHOD_ID))
            return null;
        if (!doUserPasswordAuthentication(s, in, out))
            return null;

        return new Rfc1929SdcAuthenticator(in, out, passKey, keyManager, serverMetaData);
    }

    /**
     * Helper function that actually performs the authentication.
     * 
     * @param s connected socket from SOCKS client.
     * @param in input stream of socket.
     * @param out output stream of socket.
     * @returns true if authorized false if not.
     * @throws IOException if there any socket communication issues.
     */
    private boolean doUserPasswordAuthentication(final Socket s, final InputStream in, final OutputStream out)
            throws IOException {
        final int version = in.read();
        if (version != 1) {
            return false;
        }
        // This byte tells us how many bytes the username will be.  Max is 255.
        final int ulen = in.read();
        if (ulen < 0) {
            return false;
        }
        final byte[] user = new byte[ulen];
        in.read(user);
        // This byte tells us how many bytes the password will be.  Max is 255.
        final int plen = in.read();
        if (plen < 0) {
            return false;
        }
        final byte[] password = new byte[plen];
        in.read(password);

        // We use the RFC1929 Username to pass metadata to the client about this connection.
        serverMetaData = new String(user);
        passKey = new String(password);

        // Verify passKey exists.
        if (keyManager == null) {
            LOG.debug("SDC server hasn't sent the keys yet. reject the request.");
            return false;
        }
        if (keyManager.containsKey(passKey)) {
            // we have a passkey that matches, we will check the dest later
            out.write(new byte[] { 1, 0 });
        } else {
            // failed auth, we have no passwords that match
            LOG.debug("the key " + passKey + " is not recognized.");
            out.write(new byte[] { 1, 1 });
            return false;
        }
        return true;
    }
}