You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by ed...@apache.org on 2008/08/13 02:05:43 UTC
svn commit: r685389 [2/4] - in
/mina/trunk/core/src/main/java/org/apache/mina/proxy: ./ event/ filter/
handlers/ handlers/http/ handlers/http/basic/ handlers/http/digest/
handlers/http/ntlm/ handlers/socks/ session/ utils/
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http;
+
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.handlers.ProxyRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpProxyRequest.java - Wrapper class for HTTP requests.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @author James Furness <a href="mailto:james.furness@lehman.com">james.furness@lehman.com</a>
+ * @version $Id: $
+ */
+public class HttpProxyRequest extends ProxyRequest {
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpProxyRequest.class);
+
+ public final String httpVerb;
+
+ public final String httpURI;
+
+ private String httpVersion;
+
+ private String host;
+
+ private Map<String, List<String>> headers;
+
+ private transient Map<String, String> properties;
+
+ public HttpProxyRequest(final InetSocketAddress endpointAddress) {
+ this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
+ + endpointAddress.getPort(), HttpProxyConstants.HTTP_1_0, null);
+ }
+
+ public HttpProxyRequest(final InetSocketAddress endpointAddress,
+ final String httpVersion) {
+ this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
+ + endpointAddress.getPort(), httpVersion, null);
+ }
+
+ public HttpProxyRequest(final InetSocketAddress endpointAddress,
+ final String httpVersion, final Map<String, List<String>> headers) {
+ this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
+ + endpointAddress.getPort(), httpVersion, headers);
+ }
+
+ public HttpProxyRequest(final String httpURI) {
+ this(HttpProxyConstants.GET, httpURI, HttpProxyConstants.HTTP_1_0, null);
+ }
+
+ public HttpProxyRequest(final String httpURI, final String httpVersion) {
+ this(HttpProxyConstants.GET, httpURI, httpVersion, null);
+ }
+
+ public HttpProxyRequest(final String httpVerb, final String httpURI,
+ final String httpVersion) {
+ this(httpVerb, httpURI, httpVersion, null);
+ }
+
+ public HttpProxyRequest(final String httpVerb, final String httpURI,
+ final String httpVersion, final Map<String, List<String>> headers) {
+ this.httpVerb = httpVerb;
+ this.httpURI = httpURI;
+ this.httpVersion = httpVersion;
+ this.headers = headers;
+ }
+
+ /**
+ * The request verb.
+ */
+ public final String getHttpVerb() {
+ return httpVerb;
+ }
+
+ /**
+ * The HTTP version.
+ */
+ public String getHttpVersion() {
+ return httpVersion;
+ }
+
+ /**
+ * Sets the HTTP version.
+ */
+ public void setHttpVersion(String httpVersion) {
+ this.httpVersion = httpVersion;
+ }
+
+ /**
+ * Returns the host to which we are connecting.
+ */
+ public synchronized final String getHost() {
+ if (host == null) {
+ if (getEndpointAddress() != null) {
+ host = getEndpointAddress().getHostName();
+ }
+
+ if (host == null && httpURI != null) {
+ try {
+ host = (new URL(httpURI)).getHost();
+ } catch (MalformedURLException e) {
+ logger.debug("Malformed URL", e);
+ }
+ }
+ }
+
+ return host;
+ }
+
+ /**
+ * The request URI.
+ */
+ public final String getHttpURI() {
+ return httpURI;
+ }
+
+ /**
+ * HTTP headers.
+ */
+ public final Map<String, List<String>> getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Set the HTTP headers.
+ */
+ public final void setHeaders(Map<String, List<String>> headers) {
+ this.headers = headers;
+ }
+
+ /**
+ * Get the additional properties.
+ */
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ /**
+ * Set the additional properties.
+ */
+ public void setProperties(Map<String, String> properties) {
+ this.properties = properties;
+ }
+
+ /**
+ * Check if the property is set otherwise throw a{@link ProxyAuthException}.
+ */
+ public void checkRequiredProperty(String propertyName)
+ throws ProxyAuthException {
+ if (properties.get(propertyName) == null) {
+ StringBuilder sb = new StringBuilder("'");
+ sb.append(propertyName).append(
+ "' property not provided in the request properties");
+ throw new ProxyAuthException(sb.toString());
+ }
+ }
+
+ /**
+ * Returns the string representation of the HTTP request .
+ */
+ public String toHttpString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(getHttpVerb()).append(' ').append(getHttpURI()).append(' ')
+ .append(getHttpVersion()).append(HttpProxyConstants.CRLF);
+
+ boolean hostHeaderFound = false;
+
+ if (getHeaders() != null) {
+ for (Map.Entry<String, List<String>> header : getHeaders()
+ .entrySet()) {
+ if (!hostHeaderFound) {
+ hostHeaderFound = header.getKey().equalsIgnoreCase("host");
+ }
+
+ for (String value : header.getValue()) {
+ sb.append(header.getKey()).append(": ").append(value)
+ .append(HttpProxyConstants.CRLF);
+ }
+ }
+
+ if (!hostHeaderFound
+ && getHttpVersion() == HttpProxyConstants.HTTP_1_1) {
+ sb.append("Host: ").append(getHost()).append(
+ HttpProxyConstants.CRLF);
+ }
+ }
+
+ sb.append(HttpProxyConstants.CRLF);
+
+ return sb.toString();
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * HttpProxyResponse.java - Wrapper class for HTTP requests.
+ *
+ * @author James Furness <a href="mailto:james.furness@lehman.com">james.furness@lehman.com</a>
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpProxyResponse {
+ public final String httpVersion;
+
+ public final String statusLine;
+
+ public final int statusCode;
+
+ public final Map<String, List<String>> headers;
+
+ public String body;
+
+ protected HttpProxyResponse(final String httpVersion,
+ final String statusLine, final Map<String, List<String>> headers) {
+ this.httpVersion = httpVersion;
+ this.statusLine = statusLine;
+
+ this.statusCode = statusLine.charAt(0) == ' ' ? Integer
+ .parseInt(statusLine.substring(1, 4)) : Integer
+ .parseInt(statusLine.substring(0, 3));
+
+ this.headers = headers;
+ }
+
+ /**
+ * The HTTP version.
+ */
+ public final String getHttpVersion() {
+ return httpVersion;
+ }
+
+ /**
+ * The HTTP status code.
+ */
+ public final int getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * The HTTP status line.
+ */
+ public final String getStatusLine() {
+ return statusLine;
+ }
+
+ /**
+ * The HTTP entity body.
+ */
+ public String getBody() {
+ return body;
+ }
+
+ /**
+ * Sets the HTTP entity body.
+ */
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ /**
+ * HTTP headers.
+ */
+ public final Map<String, List<String>> getHeaders() {
+ return headers;
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.mina.core.filterchain.IoFilter.NextFilter;
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.apache.mina.proxy.utils.StringUtilities;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpSmartProxyHandler.java - HTTP proxy handler that automatically handles forwarding request
+ * to the appropriate authentication mechanism logic handler.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpSmartProxyHandler extends AbstractHttpLogicHandler {
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpSmartProxyHandler.class);
+
+ /**
+ * Has the HTTP proxy request been sent ?
+ */
+ private boolean requestSent = false;
+
+ /**
+ * The automatically selected http authentication logic handler.
+ */
+ private AbstractAuthLogicHandler authHandler;
+
+ public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) {
+ super(proxyIoSession);
+ }
+
+ /**
+ * Perform any handshaking processing.
+ */
+ public void doHandshake(final NextFilter nextFilter)
+ throws ProxyAuthException {
+ logger.debug(" doHandshake()");
+
+ if (authHandler != null) {
+ authHandler.doHandshake(nextFilter);
+ } else {
+ if (requestSent) {
+ throw new ProxyAuthException(
+ "Authentication request already sent");
+ }
+
+ logger.debug(" sending HTTP request");
+
+ // Send request
+ HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession()
+ .getRequest();
+ Map<String, List<String>> headers = req.getHeaders() != null ? req
+ .getHeaders() : new HashMap<String, List<String>>();
+
+ StringUtilities.addValueToHeader(headers, "Keep-Alive",
+ HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
+ StringUtilities.addValueToHeader(headers, "Proxy-Connection",
+ "keep-Alive", true);
+ req.setHeaders(headers);
+
+ writeRequest(nextFilter, req);
+ requestSent = true;
+ }
+ }
+
+ /**
+ * Automatic selection of the authentication algorithm. If <code>preferedOrder</code> is set then
+ * algorithms are selected from the list order otherwise the algorithm tries to select the most
+ * secured algorithm available first.
+ */
+ private void autoSelectAuthHandler(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ // Get the Proxy-Authenticate header
+ List<String> values = response.getHeaders().get("Proxy-Authenticate");
+
+ if (values == null || values.size() == 0) {
+ authHandler = HttpAuthenticationMethods.NO_AUTH
+ .getNewHandler(getProxyIoSession());
+
+ } else if (getProxyIoSession().getPreferedOrder() == null) {
+ for (String proxyAuthHeader : values) {
+ proxyAuthHeader = proxyAuthHeader.toLowerCase();
+
+ try {
+ // Test which auth mechanism to use. First found is the first used that's why we test
+ // in a decreasing security quality order.
+ if (proxyAuthHeader.contains("ntlm")) {
+ authHandler = HttpAuthenticationMethods.NTLM
+ .getNewHandler(getProxyIoSession());
+ break;
+ } else if (proxyAuthHeader.contains("digest")) {
+ authHandler = HttpAuthenticationMethods.DIGEST
+ .getNewHandler(getProxyIoSession());
+ break;
+ } else if (proxyAuthHeader.contains("basic")) {
+ authHandler = HttpAuthenticationMethods.BASIC
+ .getNewHandler(getProxyIoSession());
+ break;
+ }
+ } catch (Exception ex) {
+ logger.debug("Following exception occured:", ex);
+ }
+ }
+
+ if (authHandler == null) {
+ authHandler = HttpAuthenticationMethods.NO_AUTH
+ .getNewHandler(getProxyIoSession());
+ }
+
+ } else {
+ for (HttpAuthenticationMethods method : getProxyIoSession()
+ .getPreferedOrder()) {
+ if (authHandler != null) {
+ break;
+ }
+
+ if (method == HttpAuthenticationMethods.NO_AUTH) {
+ authHandler = HttpAuthenticationMethods.NO_AUTH
+ .getNewHandler(getProxyIoSession());
+ break;
+ }
+
+ for (String proxyAuthHeader : values) {
+ proxyAuthHeader = proxyAuthHeader.toLowerCase();
+
+ try {
+ // test which auth mechanism to use
+ if (proxyAuthHeader.contains("basic")
+ && method == HttpAuthenticationMethods.BASIC) {
+ authHandler = HttpAuthenticationMethods.BASIC
+ .getNewHandler(getProxyIoSession());
+ break;
+ } else if (proxyAuthHeader.contains("digest")
+ && method == HttpAuthenticationMethods.DIGEST) {
+ authHandler = HttpAuthenticationMethods.DIGEST
+ .getNewHandler(getProxyIoSession());
+ break;
+ } else if (proxyAuthHeader.contains("ntlm")
+ && method == HttpAuthenticationMethods.NTLM) {
+ authHandler = HttpAuthenticationMethods.NTLM
+ .getNewHandler(getProxyIoSession());
+ break;
+ }
+ } catch (Exception ex) {
+ logger.debug("Following exception occured:", ex);
+ }
+ }
+ }
+
+ }
+
+ if (authHandler == null) {
+ throw new ProxyAuthException(
+ "Unknown authentication mechanism(s): " + values);
+ }
+ }
+
+ /**
+ * Handle a HTTP response from the proxy server.
+ *
+ * @param response The response.
+ */
+ @Override
+ public void handleResponse(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ if (!isHandshakeComplete()
+ && ("close".equalsIgnoreCase(StringUtilities
+ .getSingleValuedHeader(response.getHeaders(),
+ "Proxy-Connection")) || "close"
+ .equalsIgnoreCase(StringUtilities
+ .getSingleValuedHeader(response.getHeaders(),
+ "Connection")))) {
+ getProxyIoSession().setReconnectionNeeded(true);
+ }
+
+ if (response.getStatusCode() == 407) {
+ if (authHandler == null) {
+ autoSelectAuthHandler(response);
+ }
+ authHandler.handleResponse(response);
+ } else {
+ throw new ProxyAuthException("Received error response code ("
+ + response.getStatusLine() + ").");
+ }
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.basic;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.mina.core.filterchain.IoFilter.NextFilter;
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
+import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
+import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
+import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.apache.mina.proxy.utils.StringUtilities;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpBasicAuthLogicHandler.java - HTTP Basic authentication mechanism logic handler.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpBasicAuthLogicHandler extends AbstractAuthLogicHandler {
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpBasicAuthLogicHandler.class);
+
+ public HttpBasicAuthLogicHandler(final ProxyIoSession proxyIoSession)
+ throws ProxyAuthException {
+ super(proxyIoSession);
+
+ if (request == null || !(request instanceof HttpProxyRequest)) {
+ throw new IllegalArgumentException(
+ "request parameter should be a non null HttpProxyRequest instance");
+ }
+
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
+ req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
+ }
+
+ @Override
+ public void doHandshake(final NextFilter nextFilter)
+ throws ProxyAuthException {
+ logger.debug(" doHandshake()");
+
+ if (step > 0) {
+ throw new ProxyAuthException("Authentication request already sent");
+ }
+
+ // Send request
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ Map<String, List<String>> headers = req.getHeaders() != null ? req
+ .getHeaders() : new HashMap<String, List<String>>();
+
+ String username = req.getProperties().get(
+ HttpProxyConstants.USER_PROPERTY);
+ String password = req.getProperties().get(
+ HttpProxyConstants.PWD_PROPERTY);
+
+ StringUtilities.addValueToHeader(headers, "Proxy-Authorization",
+ "Basic " + createAuthorization(username, password), true);
+
+ StringUtilities.addValueToHeader(headers, "Keep-Alive",
+ HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
+ StringUtilities.addValueToHeader(headers, "Proxy-Connection",
+ "keep-Alive", true);
+ req.setHeaders(headers);
+
+ writeRequest(nextFilter, req);
+ step++;
+ }
+
+ /**
+ * Computes authorization header value.
+ */
+ public static String createAuthorization(final String username,
+ final String password) {
+ return new String(Base64.encodeBase64((username + ":" + password)
+ .getBytes()));
+ }
+
+ @Override
+ public void handleResponse(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ if (response.getStatusCode() != 407) {
+ throw new ProxyAuthException("Received error response code ("
+ + response.getStatusLine() + ").");
+ }
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.basic;
+
+import org.apache.mina.core.filterchain.IoFilter.NextFilter;
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
+import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
+import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpNoAuthLogicHandler.java - HTTP 'no auth' mechanism logic handler.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpNoAuthLogicHandler extends AbstractAuthLogicHandler {
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpNoAuthLogicHandler.class);
+
+ public HttpNoAuthLogicHandler(final ProxyIoSession proxyIoSession)
+ throws ProxyAuthException {
+ super(proxyIoSession);
+ }
+
+ @Override
+ public void doHandshake(final NextFilter nextFilter)
+ throws ProxyAuthException {
+ logger.debug(" doHandshake()");
+
+ // Just send the request, no authentication needed
+ writeRequest(nextFilter, (HttpProxyRequest) request);
+ step++;
+ }
+
+ @Override
+ public void handleResponse(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ // Should never get here !
+ throw new ProxyAuthException("Received error response code ("
+ + response.getStatusLine() + ").");
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.digest;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+
+import javax.security.sasl.AuthenticationException;
+
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.apache.mina.proxy.utils.ByteUtilities;
+import org.apache.mina.proxy.utils.StringUtilities;
+
+/**
+ * DigestUtilities.java - A class supporting the HTTP DIGEST authentication (see RFC 2617).
+ *
+ * Note: Some code has been borrowed from <a href="http://tedorg.free.fr/en/projects.php">Mailster</a>
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class DigestUtilities {
+
+ public final static String SESSION_HA1 = DigestUtilities.class
+ + ".SessionHA1";
+
+ private static MessageDigest md5;
+
+ static {
+ // Initialize secure random generator
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public final static String[] SUPPORTED_QOPS = new String[] { "auth",
+ "auth-int" };
+
+ /**
+ * Computes the response to the DIGEST challenge.
+ */
+ public static String computeResponseValue(IoSession session,
+ HashMap<String, String> map, String method, String pwd,
+ String charsetName, String body) throws AuthenticationException,
+ UnsupportedEncodingException {
+
+ byte[] hA1;
+ StringBuilder sb;
+ boolean isMD5Sess = "md5-sess".equalsIgnoreCase(StringUtilities
+ .getDirectiveValue(map, "algorithm", false));
+
+ if (!!isMD5Sess || session.getAttribute(SESSION_HA1) == null) {
+ // Build A1
+ sb = new StringBuilder();
+ sb.append(
+ StringUtilities.stringTo8859_1(StringUtilities
+ .getDirectiveValue(map, "username", true))).append(
+ ':');
+
+ String realm = StringUtilities.stringTo8859_1(StringUtilities
+ .getDirectiveValue(map, "realm", false));
+ if (realm != null) {
+ sb.append(realm);
+ }
+
+ sb.append(':').append(pwd);
+
+ if (isMD5Sess) {
+ byte[] prehA1;
+ synchronized (md5) {
+ md5.reset();
+ prehA1 = md5.digest(sb.toString().getBytes(charsetName));
+ }
+
+ sb = new StringBuilder();
+ sb.append(ByteUtilities.asHex(prehA1));
+ sb.append(':').append(
+ StringUtilities.stringTo8859_1(StringUtilities
+ .getDirectiveValue(map, "nonce", true)));
+ sb.append(':').append(
+ StringUtilities.stringTo8859_1(StringUtilities
+ .getDirectiveValue(map, "cnonce", true)));
+
+ synchronized (md5) {
+ md5.reset();
+ hA1 = md5.digest(sb.toString().getBytes(charsetName));
+ }
+
+ session.setAttribute(SESSION_HA1, hA1);
+ } else {
+ synchronized (md5) {
+ md5.reset();
+ hA1 = md5.digest(sb.toString().getBytes(charsetName));
+ }
+ }
+ } else {
+ hA1 = (byte[]) session.getAttribute(SESSION_HA1);
+ }
+
+ sb = new StringBuilder(method);
+ sb.append(':');
+ sb.append(StringUtilities.getDirectiveValue(map, "uri", false));
+
+ String qop = StringUtilities.getDirectiveValue(map, "qop", false);
+ if ("auth-int".equalsIgnoreCase(qop)) {
+ ProxyIoSession proxyIoSession = (ProxyIoSession) session
+ .getAttribute(ProxyIoSession.PROXY_SESSION);
+ byte[] hEntity;
+
+ synchronized (md5) {
+ md5.reset();
+ hEntity = md5.digest(body.getBytes(proxyIoSession
+ .getCharsetName()));
+ }
+ sb.append(':').append(hEntity);
+ }
+
+ byte[] hA2;
+ synchronized (md5) {
+ md5.reset();
+ hA2 = md5.digest(sb.toString().getBytes(charsetName));
+ }
+
+ sb = new StringBuilder();
+ sb.append(ByteUtilities.asHex(hA1));
+ sb.append(':').append(
+ StringUtilities.getDirectiveValue(map, "nonce", true));
+ sb.append(":00000001:");
+
+ sb.append(StringUtilities.getDirectiveValue(map, "cnonce", true));
+ sb.append(':').append(qop).append(':');
+ sb.append(ByteUtilities.asHex(hA2));
+
+ byte[] hFinal;
+ synchronized (md5) {
+ md5.reset();
+ hFinal = md5.digest(sb.toString().getBytes(charsetName));
+ }
+
+ return ByteUtilities.asHex(hFinal);
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.digest;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.mina.core.filterchain.IoFilter.NextFilter;
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
+import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
+import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
+import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.apache.mina.proxy.utils.StringUtilities;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpDigestAuthLogicHandler.java - HTTP Digest authentication mechanism logic handler.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpDigestAuthLogicHandler extends AbstractAuthLogicHandler {
+
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpDigestAuthLogicHandler.class);
+
+ /**
+ * The challenge directives provided by the server.
+ */
+ private HashMap<String, String> directives = null;
+
+ /**
+ * The response received to the last request.
+ */
+ private HttpProxyResponse response;
+
+ private static SecureRandom rnd;
+
+ static {
+ // Initialize secure random generator
+ try {
+ rnd = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public HttpDigestAuthLogicHandler(final ProxyIoSession proxyIoSession)
+ throws ProxyAuthException {
+ super(proxyIoSession);
+
+ if (request == null || !(request instanceof HttpProxyRequest)) {
+ throw new IllegalArgumentException(
+ "request parameter should be a non null HttpProxyRequest instance");
+ }
+
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
+ req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
+ }
+
+ @Override
+ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
+ logger.debug(" doHandshake()");
+
+ if (step > 0 && directives == null) {
+ throw new ProxyAuthException(
+ "Authentication challenge not received");
+ } else {
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ Map<String, List<String>> headers = req.getHeaders() != null ? req
+ .getHeaders() : new HashMap<String, List<String>>();
+
+ if (step > 0) {
+ logger.debug(" sending DIGEST challenge response");
+
+ HashMap<String, String> map = new HashMap<String, String>();
+ map.put("username", req.getProperties().get(
+ HttpProxyConstants.USER_PROPERTY));
+ StringUtilities.copyDirective(directives, map, "realm");
+ StringUtilities.copyDirective(directives, map, "uri");
+ StringUtilities.copyDirective(directives, map, "opaque");
+ StringUtilities.copyDirective(directives, map, "nonce");
+ String algorithm = StringUtilities.copyDirective(directives,
+ map, "algorithm");
+
+ // Check for a supported algorithm
+ if (algorithm != null && !"md5".equalsIgnoreCase(algorithm)
+ && !"md5-sess".equalsIgnoreCase(algorithm)) {
+ throw new ProxyAuthException(
+ "Unknown algorithm required by server");
+ }
+
+ // Check for a supported qop
+ String qop = directives.get("qop");
+ if (qop != null) {
+ StringTokenizer st = new StringTokenizer(qop, ",");
+ String token = null;
+
+ while (st.hasMoreTokens()) {
+ String tk = st.nextToken();
+ if ("auth".equalsIgnoreCase(token)) {
+ break;
+ } else {
+ int pos = Arrays.binarySearch(
+ DigestUtilities.SUPPORTED_QOPS, tk);
+ if (pos > -1) {
+ token = tk;
+ }
+ }
+ }
+
+ if (token != null) {
+ map.put("qop", token);
+
+ byte[] nonce = new byte[8];
+ rnd.nextBytes(nonce);
+
+ try {
+ String cnonce = new String(Base64
+ .encodeBase64(nonce), proxyIoSession
+ .getCharsetName());
+ map.put("cnonce", cnonce);
+ } catch (UnsupportedEncodingException e) {
+ throw new ProxyAuthException(
+ "Unable to encode cnonce", e);
+ }
+ } else {
+ throw new ProxyAuthException(
+ "No supported qop option available");
+ }
+ }
+
+ map.put("nc", "00000001");
+ map.put("uri", req.getHttpURI());
+
+ // Compute the response
+ try {
+ map.put("response", DigestUtilities
+ .computeResponseValue(proxyIoSession.getSession(),
+ map, req.getHttpVerb().toUpperCase(),
+ req.getProperties().get(
+ HttpProxyConstants.PWD_PROPERTY),
+ proxyIoSession.getCharsetName(), response
+ .getBody()));
+
+ } catch (Exception e) {
+ throw new ProxyAuthException(
+ "Digest response computing failed", e);
+ }
+
+ // Prepare the challenge response header and add it to the request we will send
+ StringBuilder sb = new StringBuilder("Digest ");
+ boolean addSeparator = false;
+
+ for (String key : map.keySet()) {
+
+ if (addSeparator) {
+ sb.append(", ");
+ } else {
+ addSeparator = true;
+ }
+
+ boolean quotedValue = !"qop".equals(key)
+ && !"nc".equals(key);
+ sb.append(key);
+ if (quotedValue) {
+ sb.append("=\"").append(map.get(key)).append('\"');
+ } else {
+ sb.append('=').append(map.get(key));
+ }
+ }
+
+ StringUtilities.addValueToHeader(headers,
+ "Proxy-Authorization", sb.toString(), true);
+ }
+
+ StringUtilities.addValueToHeader(headers, "Keep-Alive",
+ HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
+ StringUtilities.addValueToHeader(headers, "Proxy-Connection",
+ "keep-Alive", true);
+ req.setHeaders(headers);
+
+ writeRequest(nextFilter, req);
+ step++;
+ }
+ }
+
+ @Override
+ public void handleResponse(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ this.response = response;
+
+ if (step == 0) {
+ if (response.getStatusCode() != 401
+ && response.getStatusCode() != 407) {
+ throw new ProxyAuthException(
+ "Received unexpected response code ("
+ + response.getStatusLine() + ").");
+ }
+
+ // Header should be like this
+ // Proxy-Authenticate: Digest still_some_more_stuff
+ List<String> values = response.getHeaders().get(
+ "Proxy-Authenticate");
+ String challengeResponse = null;
+
+ for (String s : values) {
+ if (s.startsWith("Digest")) {
+ challengeResponse = s;
+ break;
+ }
+ }
+
+ if (challengeResponse == null) {
+ throw new ProxyAuthException(
+ "Server doesn't support digest authentication method !");
+ }
+
+ try {
+ directives = StringUtilities.parseDirectives(challengeResponse
+ .substring(7).getBytes(proxyIoSession.getCharsetName()));
+ } catch (Exception e) {
+ throw new ProxyAuthException(
+ "Parsing of server digest directives failed", e);
+ }
+ step = 1;
+ } else {
+ throw new ProxyAuthException("Received unexpected response code ("
+ + response.getStatusLine() + ").");
+ }
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.ntlm;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.mina.core.filterchain.IoFilter.NextFilter;
+import org.apache.mina.proxy.ProxyAuthException;
+import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
+import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
+import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
+import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
+import org.apache.mina.proxy.session.ProxyIoSession;
+import org.apache.mina.proxy.utils.StringUtilities;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HttpNTLMAuthLogicHandler.java - HTTP NTLM authentication mechanism logic handler.
+ *
+ * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
+ * @version $Id: $
+ */
+public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler {
+
+ private final static Logger logger = LoggerFactory
+ .getLogger(HttpNTLMAuthLogicHandler.class);
+
+ /**
+ * The challenge provided by the server.
+ */
+ private byte[] challengePacket = null;
+
+ public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession)
+ throws ProxyAuthException {
+ super(proxyIoSession);
+
+ if (request == null || !(request instanceof HttpProxyRequest)) {
+ throw new IllegalArgumentException(
+ "request parameter should be a non null HttpProxyRequest instance");
+ }
+
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
+ req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
+ req.checkRequiredProperty(HttpProxyConstants.DOMAIN_PROPERTY);
+ req.checkRequiredProperty(HttpProxyConstants.WORKSTATION_PROPERTY);
+ }
+
+ @Override
+ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
+ logger.debug(" doHandshake()");
+
+ if (step > 0 && challengePacket == null) {
+ throw new IllegalStateException("Challenge packet not received");
+ } else {
+ HttpProxyRequest req = (HttpProxyRequest) request;
+ Map<String, List<String>> headers = req.getHeaders() != null ? req
+ .getHeaders() : new HashMap<String, List<String>>();
+
+ String domain = req.getProperties().get(
+ HttpProxyConstants.DOMAIN_PROPERTY);
+ String workstation = req.getProperties().get(
+ HttpProxyConstants.WORKSTATION_PROPERTY);
+
+ if (step > 0) {
+ logger.debug(" sending NTLM challenge response");
+
+ byte[] challenge = NTLMUtilities
+ .extractChallengeFromType2Message(challengePacket);
+ int serverFlags = NTLMUtilities
+ .extractFlagsFromType2Message(challengePacket);
+
+ String username = req.getProperties().get(
+ HttpProxyConstants.USER_PROPERTY);
+ String password = req.getProperties().get(
+ HttpProxyConstants.PWD_PROPERTY);
+
+ byte[] authenticationPacket = NTLMUtilities.createType3Message(
+ username, password, challenge, domain, workstation,
+ serverFlags, null);
+
+ StringUtilities.addValueToHeader(headers,
+ "Proxy-Authorization", "NTLM "
+ + new String(Base64
+ .encodeBase64(authenticationPacket)),
+ true);
+
+ } else {
+ logger.debug(" sending HTTP request");
+
+ byte[] negotiationPacket = NTLMUtilities.createType1Message(
+ workstation, domain, null, null);
+ StringUtilities
+ .addValueToHeader(
+ headers,
+ "Proxy-Authorization",
+ "NTLM "
+ + new String(
+ Base64
+ .encodeBase64(negotiationPacket)),
+ true);
+ }
+
+ StringUtilities.addValueToHeader(headers, "Keep-Alive",
+ HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
+ StringUtilities.addValueToHeader(headers, "Proxy-Connection",
+ "keep-Alive", true);
+ req.setHeaders(headers);
+
+ writeRequest(nextFilter, req);
+ step++;
+ }
+ }
+
+ /**
+ * Returns the value of the NTLM Proxy-Authenticate header.
+ */
+ private String getNTLMHeader(final HttpProxyResponse response) {
+ List<String> values = response.getHeaders().get("Proxy-Authenticate");
+
+ for (String s : values) {
+ if (s.startsWith("NTLM")) {
+ return s;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void handleResponse(final HttpProxyResponse response)
+ throws ProxyAuthException {
+ if (step == 0) {
+ String challengeResponse = getNTLMHeader(response);
+ step = 1;
+
+ if (challengeResponse == null || challengeResponse.length() < 5) {
+ // Nothing to handle at this step. Just need to send a reply type 1 message in doHandshake().
+ return;
+ }
+
+ // else there was no step 0 so continue to step 1.
+ }
+
+ if (step == 1) {
+ // Header should be like this
+ // Proxy-Authenticate: NTLM still_some_more_stuff
+ String challengeResponse = getNTLMHeader(response);
+
+ if (challengeResponse == null || challengeResponse.length() < 5) {
+ throw new ProxyAuthException(
+ "Unexpected error while reading server challenge !");
+ }
+
+ try {
+ challengePacket = Base64
+ .decodeBase64(challengeResponse.substring(5).getBytes(
+ proxyIoSession.getCharsetName()));
+ } catch (IOException e) {
+ throw new ProxyAuthException(
+ "Unable to decode the base64 encoded NTLM challenge", e);
+ }
+ step = 2;
+ } else {
+ throw new ProxyAuthException("Received unexpected response code ("
+ + response.getStatusLine() + ").");
+ }
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.ntlm;
+
+/**
+ * NTLMConstants.java - All NTLM constants.
+ *
+ * @author <a href="mailto:doe_wanted@yahoo.fr">Edouard De Oliveira</a>
+ * @version $Revision:$, $Date:$
+ */
+public interface NTLMConstants {
+ // Signature "NTLMSSP"+{0}
+ public final static byte[] NTLM_SIGNATURE = new byte[] { 0x4E, 0x54, 0x4C,
+ 0x4D, 0x53, 0x53, 0x50, 0 };
+
+ // Version 5.1.2600 a Windows XP version (ex: Build 2600.xpsp_sp2_gdr.050301-1519 : Service Pack 2)
+ public final static byte[] DEFAULT_OS_VERSION = new byte[] { 0x05, 0x01,
+ 0x28, 0x0A, 0, 0, 0, 0x0F };
+
+ /**
+ * Message types
+ */
+
+ public final static int MESSAGE_TYPE_1 = 1;
+
+ public final static int MESSAGE_TYPE_2 = 2;
+
+ public final static int MESSAGE_TYPE_3 = 3;
+
+ /**
+ * Message flags
+ */
+
+ // Indicates that Unicode strings are supported for use in security buffer data
+ public final static int FLAG_NEGOTIATE_UNICODE = 0x00000001;
+
+ // Indicates that OEM strings are supported for use in security buffer data
+ public final static int FLAG_NEGOTIATE_OEM = 0x00000002;
+
+ // Requests that the server's authentication realm be included in the Type 2 message
+ public final static int FLAG_REQUEST_SERVER_AUTH_REALM = 0x00000004;
+
+ // Specifies that authenticated communication between the client
+ // and server should carry a digital signature (message integrity)
+ public final static int FLAG_NEGOTIATE_SIGN = 0x00000010;
+
+ // Specifies that authenticated communication between the client
+ // and server should be encrypted (message confidentiality)
+ public final static int FLAG_NEGOTIATE_SEAL = 0x00000020;
+
+ // Indicates that datagram authentication is being used
+ public final static int FLAG_NEGOTIATE_DATAGRAM_STYLE = 0x00000040;
+
+ // Indicates that the Lan Manager Session Key should be used for signing and
+ // sealing authenticated communications
+ public final static int FLAG_NEGOTIATE_LAN_MANAGER_KEY = 0x00000080;
+
+ // Indicates that NTLM authentication is being used
+ public final static int FLAG_NEGOTIATE_NTLM = 0x00000200;
+
+ // Sent by the client in the Type 3 message to indicate that an anonymous context
+ // has been established. This also affects the response fields
+ public final static int FLAG_NEGOTIATE_ANONYMOUS = 0x00000800;
+
+ // Sent by the client in the Type 1 message to indicate that the name of the domain in which
+ // the client workstation has membership is included in the message. This is used by the
+ // server to determine whether the client is eligible for local authentication
+ public final static int FLAG_NEGOTIATE_DOMAIN_SUPPLIED = 0x00001000;
+
+ // Sent by the client in the Type 1 message to indicate that the client workstation's name
+ // is included in the message. This is used by the server to determine whether the client
+ // is eligible for local authentication
+ public final static int FLAG_NEGOTIATE_WORKSTATION_SUPPLIED = 0x00002000;
+
+ // Sent by the server to indicate that the server and client are on the same machine.
+ // Implies that the client may use the established local credentials for authentication
+ // instead of calculating a response to the challenge
+ public final static int FLAG_NEGOTIATE_LOCAL_CALL = 0x00004000;
+
+ // Indicates that authenticated communication between the client and server should
+ // be signed with a "dummy" signature
+ public final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
+
+ // Sent by the server in the Type 2 message to indicate that the target authentication
+ // realm is a domain
+ public final static int FLAG_TARGET_TYPE_DOMAIN = 0x00010000;
+
+ // Sent by the server in the Type 2 message to indicate that the target authentication
+ // realm is a server
+ public final static int FLAG_TARGET_TYPE_SERVER = 0x00020000;
+
+ // Sent by the server in the Type 2 message to indicate that the target authentication
+ // realm is a share. Presumably, this is for share-level authentication. Usage is unclear
+ public final static int FLAG_TARGET_TYPE_SHARE = 0x00040000;
+
+ // Indicates that the NTLM2 signing and sealing scheme should be used for protecting
+ // authenticated communications. Note that this refers to a particular session security
+ // scheme, and is not related to the use of NTLMv2 authentication. This flag can, however,
+ // have an effect on the response calculations
+ public final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000;
+
+ // Sent by the server in the Type 2 message to indicate that it is including a Target
+ // Information block in the message. The Target Information block is used in the
+ // calculation of the NTLMv2 response
+ public final static int FLAG_NEGOTIATE_TARGET_INFO = 0x00800000;
+
+ // Indicates that 128-bit encryption is supported
+ public final static int FLAG_NEGOTIATE_128_BIT_ENCRYPTION = 0x20000000;
+
+ // Indicates that the client will provide an encrypted master key in the "Session Key"
+ // field of the Type 3 message
+ public final static int FLAG_NEGOTIATE_KEY_EXCHANGE = 0x40000000;
+
+ // Indicates that 56-bit encryption is supported
+ public final static int FLAG_NEGOTIATE_56_BIT_ENCRYPTION = 0x80000000;
+
+ // WARN : These flags usage has not been identified
+ public final static int FLAG_UNIDENTIFIED_1 = 0x00000008;
+
+ public final static int FLAG_UNIDENTIFIED_2 = 0x00000100; // Negotiate Netware ??!
+
+ public final static int FLAG_UNIDENTIFIED_3 = 0x00000400;
+
+ public final static int FLAG_UNIDENTIFIED_4 = 0x00100000; // Request Init Response ??!
+
+ public final static int FLAG_UNIDENTIFIED_5 = 0x00200000; // Request Accept Response ??!
+
+ public final static int FLAG_UNIDENTIFIED_6 = 0x00400000; // Request Non-NT Session Key ??!
+
+ public final static int FLAG_UNIDENTIFIED_7 = 0x01000000;
+
+ public final static int FLAG_UNIDENTIFIED_8 = 0x02000000;
+
+ public final static int FLAG_UNIDENTIFIED_9 = 0x04000000;
+
+ public final static int FLAG_UNIDENTIFIED_10 = 0x08000000;
+
+ public final static int FLAG_UNIDENTIFIED_11 = 0x10000000;
+
+ // Default minimal flag set
+ public final static int DEFAULT_FLAGS = FLAG_NEGOTIATE_OEM
+ | FLAG_NEGOTIATE_UNICODE | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
+ | FLAG_NEGOTIATE_DOMAIN_SUPPLIED;
+
+ /**
+ * Target Information sub blocks types. It may be that there are other
+ * as-yet-unidentified sub block types as well.
+ */
+
+ // Sub block terminator
+ public final static short TARGET_INFORMATION_SUBBLOCK_TERMINATOR_TYPE = 0x0000;
+
+ // Server name
+ public final static short TARGET_INFORMATION_SUBBLOCK_SERVER_TYPE = 0x0100;
+
+ // Domain name
+ public final static short TARGET_INFORMATION_SUBBLOCK_DOMAIN_TYPE = 0x0200;
+
+ // Fully-qualified DNS host name (i.e., server.domain.com)
+ public final static short TARGET_INFORMATION_SUBBLOCK_FQDNS_HOSTNAME_TYPE = 0x0300;
+
+ // DNS domain name (i.e., domain.com)
+ public final static short TARGET_INFORMATION_SUBBLOCK_DNS_DOMAIN_NAME_TYPE = 0x0400;
+
+ // Apparently the "parent" DNS domain for servers in sub domains
+ public final static short TARGET_INFORMATION_SUBBLOCK_PARENT_DNS_DOMAIN_NAME_TYPE = 0x0500;
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java?rev=685389&view=auto
==============================================================================
--- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java (added)
+++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java Tue Aug 12 17:05:41 2008
@@ -0,0 +1,413 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+package org.apache.mina.proxy.handlers.http.ntlm;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Key;
+import java.security.MessageDigest;
+
+import javax.crypto.Cipher;
+
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * NTLMResponses.java - Calculates the various Type 3 responses. Needs an MD4, MD5 and DES
+ * crypto provider (Please note that default provider doesn't provide MD4).
+ *
+ * Base code borrowed from : http://curl.haxx.se/rfc/ntlm.html
+ *
+ * @author <a href="mailto:doe_wanted@yahoo.fr">Edouard De Oliveira</a>
+ * @version $Revision:$, $Date:$
+ */
+public class NTLMResponses {
+
+ // LAN Manager magic constant used in LM Response calculation
+ public static byte[] LM_HASH_MAGIC_CONSTANT = null;
+
+ static {
+ try {
+ LM_HASH_MAGIC_CONSTANT = "KGS!@#$%".getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Calculates the LM Response for the given challenge, using the specified
+ * password.
+ *
+ * @param password The user's password.
+ * @param challenge The Type 2 challenge from the server.
+ *
+ * @return The LM Response.
+ */
+ public static byte[] getLMResponse(String password, byte[] challenge)
+ throws Exception {
+ byte[] lmHash = lmHash(password);
+ return lmResponse(lmHash, challenge);
+ }
+
+ /**
+ * Calculates the NTLM Response for the given challenge, using the
+ * specified password.
+ *
+ * @param password The user's password.
+ * @param challenge The Type 2 challenge from the server.
+ *
+ * @return The NTLM Response.
+ */
+ public static byte[] getNTLMResponse(String password, byte[] challenge)
+ throws Exception {
+ byte[] ntlmHash = ntlmHash(password);
+ return lmResponse(ntlmHash, challenge);
+ }
+
+ /**
+ * Calculates the NTLMv2 Response for the given challenge, using the
+ * specified authentication target, username, password, target information
+ * block, and client nonce.
+ *
+ * @param target The authentication target (i.e., domain).
+ * @param user The username.
+ * @param password The user's password.
+ * @param targetInformation The target information block from the Type 2
+ * message.
+ * @param challenge The Type 2 challenge from the server.
+ * @param clientNonce The random 8-byte client nonce.
+ *
+ * @return The NTLMv2 Response.
+ */
+ public static byte[] getNTLMv2Response(String target, String user,
+ String password, byte[] targetInformation, byte[] challenge,
+ byte[] clientNonce) throws Exception {
+
+ return getNTLMv2Response(target, user, password, targetInformation,
+ challenge, clientNonce, System.currentTimeMillis());
+ }
+
+ /**
+ * Calculates the NTLMv2 Response for the given challenge, using the
+ * specified authentication target, username, password, target information
+ * block, and client nonce.
+ *
+ * @param target The authentication target (i.e., domain).
+ * @param user The username.
+ * @param password The user's password.
+ * @param targetInformation The target information block from the Type 2
+ * message.
+ * @param challenge The Type 2 challenge from the server.
+ * @param clientNonce The random 8-byte client nonce.
+ * @param time The time stamp.
+ *
+ * @return The NTLMv2 Response.
+ */
+ public static byte[] getNTLMv2Response(String target, String user,
+ String password, byte[] targetInformation, byte[] challenge,
+ byte[] clientNonce, long time) throws Exception {
+ byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
+ byte[] blob = createBlob(targetInformation, clientNonce, time);
+ return lmv2Response(ntlmv2Hash, blob, challenge);
+ }
+
+ /**
+ * Calculates the LMv2 Response for the given challenge, using the
+ * specified authentication target, username, password, and client
+ * challenge.
+ *
+ * @param target The authentication target (i.e., domain).
+ * @param user The username.
+ * @param password The user's password.
+ * @param challenge The Type 2 challenge from the server.
+ * @param clientNonce The random 8-byte client nonce.
+ *
+ * @return The LMv2 Response.
+ */
+ public static byte[] getLMv2Response(String target, String user,
+ String password, byte[] challenge, byte[] clientNonce)
+ throws Exception {
+ byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
+ return lmv2Response(ntlmv2Hash, clientNonce, challenge);
+ }
+
+ /**
+ * Calculates the NTLM2 Session Response for the given challenge, using the
+ * specified password and client nonce.
+ *
+ * @param password The user's password.
+ * @param challenge The Type 2 challenge from the server.
+ * @param clientNonce The random 8-byte client nonce.
+ *
+ * @return The NTLM2 Session Response. This is placed in the NTLM
+ * response field of the Type 3 message; the LM response field contains
+ * the client nonce, null-padded to 24 bytes.
+ */
+ public static byte[] getNTLM2SessionResponse(String password,
+ byte[] challenge, byte[] clientNonce) throws Exception {
+ byte[] ntlmHash = ntlmHash(password);
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(challenge);
+ md5.update(clientNonce);
+ byte[] sessionHash = new byte[8];
+ System.arraycopy(md5.digest(), 0, sessionHash, 0, 8);
+ return lmResponse(ntlmHash, sessionHash);
+ }
+
+ /**
+ * Creates the LM Hash of the user's password.
+ *
+ * @param password The password.
+ *
+ * @return The LM Hash of the given password, used in the calculation
+ * of the LM Response.
+ */
+ private static byte[] lmHash(String password) throws Exception {
+ byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII");
+ int length = Math.min(oemPassword.length, 14);
+ byte[] keyBytes = new byte[14];
+ System.arraycopy(oemPassword, 0, keyBytes, 0, length);
+ Key lowKey = createDESKey(keyBytes, 0);
+ Key highKey = createDESKey(keyBytes, 7);
+ Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
+ des.init(Cipher.ENCRYPT_MODE, lowKey);
+ byte[] lowHash = des.doFinal(LM_HASH_MAGIC_CONSTANT);
+ des.init(Cipher.ENCRYPT_MODE, highKey);
+ byte[] highHash = des.doFinal(LM_HASH_MAGIC_CONSTANT);
+ byte[] lmHash = new byte[16];
+ System.arraycopy(lowHash, 0, lmHash, 0, 8);
+ System.arraycopy(highHash, 0, lmHash, 8, 8);
+ return lmHash;
+ }
+
+ /**
+ * Creates the NTLM Hash of the user's password.
+ *
+ * @param password The password.
+ *
+ * @return The NTLM Hash of the given password, used in the calculation
+ * of the NTLM Response and the NTLMv2 and LMv2 Hashes.
+ */
+ private static byte[] ntlmHash(String password) throws Exception {
+ byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked");
+ MessageDigest md4 = MessageDigest.getInstance("MD4");
+ return md4.digest(unicodePassword);
+ }
+
+ /**
+ * Creates the NTLMv2 Hash of the user's password.
+ *
+ * @param target The authentication target (i.e., domain).
+ * @param user The username.
+ * @param password The password.
+ *
+ * @return The NTLMv2 Hash, used in the calculation of the NTLMv2
+ * and LMv2 Responses.
+ */
+ private static byte[] ntlmv2Hash(String target, String user, String password)
+ throws Exception {
+ byte[] ntlmHash = ntlmHash(password);
+ String identity = user.toUpperCase() + target;
+ return hmacMD5(identity.getBytes("UnicodeLittleUnmarked"), ntlmHash);
+ }
+
+ /**
+ * Creates the LM Response from the given hash and Type 2 challenge.
+ *
+ * @param hash The LM or NTLM Hash.
+ * @param challenge The server challenge from the Type 2 message.
+ *
+ * @return The response (either LM or NTLM, depending on the provided
+ * hash).
+ */
+ private static byte[] lmResponse(byte[] hash, byte[] challenge)
+ throws Exception {
+ byte[] keyBytes = new byte[21];
+ System.arraycopy(hash, 0, keyBytes, 0, 16);
+ Key lowKey = createDESKey(keyBytes, 0);
+ Key middleKey = createDESKey(keyBytes, 7);
+ Key highKey = createDESKey(keyBytes, 14);
+ Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
+ des.init(Cipher.ENCRYPT_MODE, lowKey);
+ byte[] lowResponse = des.doFinal(challenge);
+ des.init(Cipher.ENCRYPT_MODE, middleKey);
+ byte[] middleResponse = des.doFinal(challenge);
+ des.init(Cipher.ENCRYPT_MODE, highKey);
+ byte[] highResponse = des.doFinal(challenge);
+ byte[] lmResponse = new byte[24];
+ System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
+ System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
+ System.arraycopy(highResponse, 0, lmResponse, 16, 8);
+ return lmResponse;
+ }
+
+ /**
+ * Creates the LMv2 Response from the given hash, client data, and
+ * Type 2 challenge.
+ *
+ * @param hash The NTLMv2 Hash.
+ * @param clientData The client data (blob or client nonce).
+ * @param challenge The server challenge from the Type 2 message.
+ *
+ * @return The response (either NTLMv2 or LMv2, depending on the
+ * client data).
+ */
+ private static byte[] lmv2Response(byte[] hash, byte[] clientData,
+ byte[] challenge) throws Exception {
+ byte[] data = new byte[challenge.length + clientData.length];
+ System.arraycopy(challenge, 0, data, 0, challenge.length);
+ System.arraycopy(clientData, 0, data, challenge.length,
+ clientData.length);
+ byte[] mac = hmacMD5(data, hash);
+ byte[] lmv2Response = new byte[mac.length + clientData.length];
+ System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
+ System.arraycopy(clientData, 0, lmv2Response, mac.length,
+ clientData.length);
+ return lmv2Response;
+ }
+
+ /**
+ * Creates the NTLMv2 blob from the given target information block and
+ * client nonce.
+ *
+ * @param targetInformation The target information block from the Type 2
+ * message.
+ * @param clientNonce The random 8-byte client nonce.
+ * @param time the time stamp.
+ *
+ * @return The blob, used in the calculation of the NTLMv2 Response.
+ */
+ private static byte[] createBlob(byte[] targetInformation,
+ byte[] clientNonce, long time) {
+ byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01,
+ (byte) 0x00, (byte) 0x00 };
+ byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00 };
+ byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00 };
+ byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00 };
+ time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
+ time *= 10000; // tenths of a microsecond.
+ // convert to little-endian byte array.
+ byte[] timestamp = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ timestamp[i] = (byte) time;
+ time >>>= 8;
+ }
+ byte[] blob = new byte[blobSignature.length + reserved.length
+ + timestamp.length + clientNonce.length + unknown1.length
+ + targetInformation.length + unknown2.length];
+ int offset = 0;
+ System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
+ offset += blobSignature.length;
+ System.arraycopy(reserved, 0, blob, offset, reserved.length);
+ offset += reserved.length;
+ System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
+ offset += timestamp.length;
+ System.arraycopy(clientNonce, 0, blob, offset, clientNonce.length);
+ offset += clientNonce.length;
+ System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
+ offset += unknown1.length;
+ System.arraycopy(targetInformation, 0, blob, offset,
+ targetInformation.length);
+ offset += targetInformation.length;
+ System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
+ return blob;
+ }
+
+ /**
+ * Calculates the HMAC-MD5 hash of the given data using the specified
+ * hashing key.
+ *
+ * @param data The data for which the hash will be calculated.
+ * @param key The hashing key.
+ *
+ * @return The HMAC-MD5 hash of the given data.
+ */
+ public static byte[] hmacMD5(byte[] data, byte[] key) throws Exception {
+ byte[] ipad = new byte[64];
+ byte[] opad = new byte[64];
+
+ // Stores key in pads and XOR it with ipad and opad values
+ for (int i = 0; i < 64; i++) {
+ if (i < key.length) {
+ ipad[i] = (byte) (key[i] ^ 0x36);
+ opad[i] = (byte) (key[i] ^ 0x5c);
+ } else {
+ ipad[i] = 0x36;
+ opad[i] = 0x5c;
+ }
+ }
+
+ byte[] content = new byte[data.length + 64];
+ System.arraycopy(ipad, 0, content, 0, 64);
+ System.arraycopy(data, 0, content, 64, data.length);
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ data = md5.digest(content);
+ content = new byte[data.length + 64];
+ System.arraycopy(opad, 0, content, 0, 64);
+ System.arraycopy(data, 0, content, 64, data.length);
+ return md5.digest(content);
+ }
+
+ /**
+ * Creates a DES encryption key from the given key material.
+ *
+ * @param bytes A byte array containing the DES key material.
+ * @param offset The offset in the given byte array at which
+ * the 7-byte key material starts.
+ *
+ * @return A DES encryption key created from the key material
+ * starting at the specified offset in the given byte array.
+ */
+ private static Key createDESKey(byte[] bytes, int offset) {
+ byte[] keyBytes = new byte[7];
+ System.arraycopy(bytes, offset, keyBytes, 0, 7);
+ byte[] material = new byte[8];
+ material[0] = keyBytes[0];
+ material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1);
+ material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2);
+ material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3);
+ material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4);
+ material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5);
+ material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6);
+ material[7] = (byte) (keyBytes[6] << 1);
+ oddParity(material);
+ return new SecretKeySpec(material, "DES");
+ }
+
+ /**
+ * Applies odd parity to the given byte array.
+ *
+ * @param bytes The data whose parity bits are to be adjusted for
+ * odd parity.
+ */
+ private static void oddParity(byte[] bytes) {
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5)
+ ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
+ if (needsParity) {
+ bytes[i] |= (byte) 0x01;
+ } else {
+ bytes[i] &= (byte) 0xfe;
+ }
+ }
+ }
+}
\ No newline at end of file
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Re: svn commit: r685389 [2/4] - in /mina/trunk/core/src/main/java/org/apache/mina/proxy: ./ event/ filter/ handlers/ handlers/http/ handlers/http/basic/ handlers/http/digest/ handlers/http/ntlm/ handlers/socks/ session/ utils/
Posted by Julien Vermillard <jv...@gmail.com>.
Javadoc on public method & authors tag here too
2008/8/13 <ed...@apache.org>:
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,219 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http;
> +
> +import java.net.InetSocketAddress;
> +import java.net.MalformedURLException;
> +import java.net.URL;
> +import java.util.List;
> +import java.util.Map;
> +
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.handlers.ProxyRequest;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpProxyRequest.java - Wrapper class for HTTP requests.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @author James Furness <a href="mailto:james.furness@lehman.com">james.furness@lehman.com</a>
> + * @version $Id: $
> + */
> +public class HttpProxyRequest extends ProxyRequest {
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpProxyRequest.class);
> +
> + public final String httpVerb;
> +
> + public final String httpURI;
> +
> + private String httpVersion;
> +
> + private String host;
> +
> + private Map<String, List<String>> headers;
> +
> + private transient Map<String, String> properties;
> +
> + public HttpProxyRequest(final InetSocketAddress endpointAddress) {
> + this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
> + + endpointAddress.getPort(), HttpProxyConstants.HTTP_1_0, null);
> + }
> +
> + public HttpProxyRequest(final InetSocketAddress endpointAddress,
> + final String httpVersion) {
> + this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
> + + endpointAddress.getPort(), httpVersion, null);
> + }
> +
> + public HttpProxyRequest(final InetSocketAddress endpointAddress,
> + final String httpVersion, final Map<String, List<String>> headers) {
> + this(HttpProxyConstants.CONNECT, endpointAddress.getHostName() + ":"
> + + endpointAddress.getPort(), httpVersion, headers);
> + }
> +
> + public HttpProxyRequest(final String httpURI) {
> + this(HttpProxyConstants.GET, httpURI, HttpProxyConstants.HTTP_1_0, null);
> + }
> +
> + public HttpProxyRequest(final String httpURI, final String httpVersion) {
> + this(HttpProxyConstants.GET, httpURI, httpVersion, null);
> + }
> +
> + public HttpProxyRequest(final String httpVerb, final String httpURI,
> + final String httpVersion) {
> + this(httpVerb, httpURI, httpVersion, null);
> + }
> +
> + public HttpProxyRequest(final String httpVerb, final String httpURI,
> + final String httpVersion, final Map<String, List<String>> headers) {
> + this.httpVerb = httpVerb;
> + this.httpURI = httpURI;
> + this.httpVersion = httpVersion;
> + this.headers = headers;
> + }
> +
> + /**
> + * The request verb.
> + */
> + public final String getHttpVerb() {
> + return httpVerb;
> + }
> +
> + /**
> + * The HTTP version.
> + */
> + public String getHttpVersion() {
> + return httpVersion;
> + }
> +
> + /**
> + * Sets the HTTP version.
> + */
> + public void setHttpVersion(String httpVersion) {
> + this.httpVersion = httpVersion;
> + }
> +
> + /**
> + * Returns the host to which we are connecting.
> + */
> + public synchronized final String getHost() {
> + if (host == null) {
> + if (getEndpointAddress() != null) {
> + host = getEndpointAddress().getHostName();
> + }
> +
> + if (host == null && httpURI != null) {
> + try {
> + host = (new URL(httpURI)).getHost();
> + } catch (MalformedURLException e) {
> + logger.debug("Malformed URL", e);
> + }
> + }
> + }
> +
> + return host;
> + }
> +
> + /**
> + * The request URI.
> + */
> + public final String getHttpURI() {
> + return httpURI;
> + }
> +
> + /**
> + * HTTP headers.
> + */
> + public final Map<String, List<String>> getHeaders() {
> + return headers;
> + }
> +
> + /**
> + * Set the HTTP headers.
> + */
> + public final void setHeaders(Map<String, List<String>> headers) {
> + this.headers = headers;
> + }
> +
> + /**
> + * Get the additional properties.
> + */
> + public Map<String, String> getProperties() {
> + return properties;
> + }
> +
> + /**
> + * Set the additional properties.
> + */
> + public void setProperties(Map<String, String> properties) {
> + this.properties = properties;
> + }
> +
> + /**
> + * Check if the property is set otherwise throw a{@link ProxyAuthException}.
> + */
> + public void checkRequiredProperty(String propertyName)
> + throws ProxyAuthException {
> + if (properties.get(propertyName) == null) {
> + StringBuilder sb = new StringBuilder("'");
> + sb.append(propertyName).append(
> + "' property not provided in the request properties");
> + throw new ProxyAuthException(sb.toString());
> + }
> + }
> +
> + /**
> + * Returns the string representation of the HTTP request .
> + */
> + public String toHttpString() {
> + StringBuilder sb = new StringBuilder();
> +
> + sb.append(getHttpVerb()).append(' ').append(getHttpURI()).append(' ')
> + .append(getHttpVersion()).append(HttpProxyConstants.CRLF);
> +
> + boolean hostHeaderFound = false;
> +
> + if (getHeaders() != null) {
> + for (Map.Entry<String, List<String>> header : getHeaders()
> + .entrySet()) {
> + if (!hostHeaderFound) {
> + hostHeaderFound = header.getKey().equalsIgnoreCase("host");
> + }
> +
> + for (String value : header.getValue()) {
> + sb.append(header.getKey()).append(": ").append(value)
> + .append(HttpProxyConstants.CRLF);
> + }
> + }
> +
> + if (!hostHeaderFound
> + && getHttpVersion() == HttpProxyConstants.HTTP_1_1) {
> + sb.append("Host: ").append(getHost()).append(
> + HttpProxyConstants.CRLF);
> + }
> + }
> +
> + sb.append(HttpProxyConstants.CRLF);
> +
> + return sb.toString();
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,96 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http;
> +
> +import java.util.List;
> +import java.util.Map;
> +
> +/**
> + * HttpProxyResponse.java - Wrapper class for HTTP requests.
> + *
> + * @author James Furness <a href="mailto:james.furness@lehman.com">james.furness@lehman.com</a>
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpProxyResponse {
> + public final String httpVersion;
> +
> + public final String statusLine;
> +
> + public final int statusCode;
> +
> + public final Map<String, List<String>> headers;
> +
> + public String body;
> +
> + protected HttpProxyResponse(final String httpVersion,
> + final String statusLine, final Map<String, List<String>> headers) {
> + this.httpVersion = httpVersion;
> + this.statusLine = statusLine;
> +
> + this.statusCode = statusLine.charAt(0) == ' ' ? Integer
> + .parseInt(statusLine.substring(1, 4)) : Integer
> + .parseInt(statusLine.substring(0, 3));
> +
> + this.headers = headers;
> + }
> +
> + /**
> + * The HTTP version.
> + */
> + public final String getHttpVersion() {
> + return httpVersion;
> + }
> +
> + /**
> + * The HTTP status code.
> + */
> + public final int getStatusCode() {
> + return statusCode;
> + }
> +
> + /**
> + * The HTTP status line.
> + */
> + public final String getStatusLine() {
> + return statusLine;
> + }
> +
> + /**
> + * The HTTP entity body.
> + */
> + public String getBody() {
> + return body;
> + }
> +
> + /**
> + * Sets the HTTP entity body.
> + */
> + public void setBody(String body) {
> + this.body = body;
> + }
> +
> + /**
> + * HTTP headers.
> + */
> + public final Map<String, List<String>> getHeaders() {
> + return headers;
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,212 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http;
> +
> +import java.util.HashMap;
> +import java.util.List;
> +import java.util.Map;
> +
> +import org.apache.mina.core.filterchain.IoFilter.NextFilter;
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.apache.mina.proxy.utils.StringUtilities;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpSmartProxyHandler.java - HTTP proxy handler that automatically handles forwarding request
> + * to the appropriate authentication mechanism logic handler.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpSmartProxyHandler extends AbstractHttpLogicHandler {
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpSmartProxyHandler.class);
> +
> + /**
> + * Has the HTTP proxy request been sent ?
> + */
> + private boolean requestSent = false;
> +
> + /**
> + * The automatically selected http authentication logic handler.
> + */
> + private AbstractAuthLogicHandler authHandler;
> +
> + public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) {
> + super(proxyIoSession);
> + }
> +
> + /**
> + * Perform any handshaking processing.
> + */
> + public void doHandshake(final NextFilter nextFilter)
> + throws ProxyAuthException {
> + logger.debug(" doHandshake()");
> +
> + if (authHandler != null) {
> + authHandler.doHandshake(nextFilter);
> + } else {
> + if (requestSent) {
> + throw new ProxyAuthException(
> + "Authentication request already sent");
> + }
> +
> + logger.debug(" sending HTTP request");
> +
> + // Send request
> + HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession()
> + .getRequest();
> + Map<String, List<String>> headers = req.getHeaders() != null ? req
> + .getHeaders() : new HashMap<String, List<String>>();
> +
> + StringUtilities.addValueToHeader(headers, "Keep-Alive",
> + HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
> + StringUtilities.addValueToHeader(headers, "Proxy-Connection",
> + "keep-Alive", true);
> + req.setHeaders(headers);
> +
> + writeRequest(nextFilter, req);
> + requestSent = true;
> + }
> + }
> +
> + /**
> + * Automatic selection of the authentication algorithm. If <code>preferedOrder</code> is set then
> + * algorithms are selected from the list order otherwise the algorithm tries to select the most
> + * secured algorithm available first.
> + */
> + private void autoSelectAuthHandler(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + // Get the Proxy-Authenticate header
> + List<String> values = response.getHeaders().get("Proxy-Authenticate");
> +
> + if (values == null || values.size() == 0) {
> + authHandler = HttpAuthenticationMethods.NO_AUTH
> + .getNewHandler(getProxyIoSession());
> +
> + } else if (getProxyIoSession().getPreferedOrder() == null) {
> + for (String proxyAuthHeader : values) {
> + proxyAuthHeader = proxyAuthHeader.toLowerCase();
> +
> + try {
> + // Test which auth mechanism to use. First found is the first used that's why we test
> + // in a decreasing security quality order.
> + if (proxyAuthHeader.contains("ntlm")) {
> + authHandler = HttpAuthenticationMethods.NTLM
> + .getNewHandler(getProxyIoSession());
> + break;
> + } else if (proxyAuthHeader.contains("digest")) {
> + authHandler = HttpAuthenticationMethods.DIGEST
> + .getNewHandler(getProxyIoSession());
> + break;
> + } else if (proxyAuthHeader.contains("basic")) {
> + authHandler = HttpAuthenticationMethods.BASIC
> + .getNewHandler(getProxyIoSession());
> + break;
> + }
> + } catch (Exception ex) {
> + logger.debug("Following exception occured:", ex);
> + }
> + }
> +
> + if (authHandler == null) {
> + authHandler = HttpAuthenticationMethods.NO_AUTH
> + .getNewHandler(getProxyIoSession());
> + }
> +
> + } else {
> + for (HttpAuthenticationMethods method : getProxyIoSession()
> + .getPreferedOrder()) {
> + if (authHandler != null) {
> + break;
> + }
> +
> + if (method == HttpAuthenticationMethods.NO_AUTH) {
> + authHandler = HttpAuthenticationMethods.NO_AUTH
> + .getNewHandler(getProxyIoSession());
> + break;
> + }
> +
> + for (String proxyAuthHeader : values) {
> + proxyAuthHeader = proxyAuthHeader.toLowerCase();
> +
> + try {
> + // test which auth mechanism to use
> + if (proxyAuthHeader.contains("basic")
> + && method == HttpAuthenticationMethods.BASIC) {
> + authHandler = HttpAuthenticationMethods.BASIC
> + .getNewHandler(getProxyIoSession());
> + break;
> + } else if (proxyAuthHeader.contains("digest")
> + && method == HttpAuthenticationMethods.DIGEST) {
> + authHandler = HttpAuthenticationMethods.DIGEST
> + .getNewHandler(getProxyIoSession());
> + break;
> + } else if (proxyAuthHeader.contains("ntlm")
> + && method == HttpAuthenticationMethods.NTLM) {
> + authHandler = HttpAuthenticationMethods.NTLM
> + .getNewHandler(getProxyIoSession());
> + break;
> + }
> + } catch (Exception ex) {
> + logger.debug("Following exception occured:", ex);
> + }
> + }
> + }
> +
> + }
> +
> + if (authHandler == null) {
> + throw new ProxyAuthException(
> + "Unknown authentication mechanism(s): " + values);
> + }
> + }
> +
> + /**
> + * Handle a HTTP response from the proxy server.
> + *
> + * @param response The response.
> + */
> + @Override
> + public void handleResponse(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + if (!isHandshakeComplete()
> + && ("close".equalsIgnoreCase(StringUtilities
> + .getSingleValuedHeader(response.getHeaders(),
> + "Proxy-Connection")) || "close"
> + .equalsIgnoreCase(StringUtilities
> + .getSingleValuedHeader(response.getHeaders(),
> + "Connection")))) {
> + getProxyIoSession().setReconnectionNeeded(true);
> + }
> +
> + if (response.getStatusCode() == 407) {
> + if (authHandler == null) {
> + autoSelectAuthHandler(response);
> + }
> + authHandler.handleResponse(response);
> + } else {
> + throw new ProxyAuthException("Received error response code ("
> + + response.getStatusLine() + ").");
> + }
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,111 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.basic;
> +
> +import java.util.HashMap;
> +import java.util.List;
> +import java.util.Map;
> +
> +import org.apache.commons.codec.binary.Base64;
> +import org.apache.mina.core.filterchain.IoFilter.NextFilter;
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
> +import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
> +import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
> +import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.apache.mina.proxy.utils.StringUtilities;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpBasicAuthLogicHandler.java - HTTP Basic authentication mechanism logic handler.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpBasicAuthLogicHandler extends AbstractAuthLogicHandler {
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpBasicAuthLogicHandler.class);
> +
> + public HttpBasicAuthLogicHandler(final ProxyIoSession proxyIoSession)
> + throws ProxyAuthException {
> + super(proxyIoSession);
> +
> + if (request == null || !(request instanceof HttpProxyRequest)) {
> + throw new IllegalArgumentException(
> + "request parameter should be a non null HttpProxyRequest instance");
> + }
> +
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
> + req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
> + }
> +
> + @Override
> + public void doHandshake(final NextFilter nextFilter)
> + throws ProxyAuthException {
> + logger.debug(" doHandshake()");
> +
> + if (step > 0) {
> + throw new ProxyAuthException("Authentication request already sent");
> + }
> +
> + // Send request
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + Map<String, List<String>> headers = req.getHeaders() != null ? req
> + .getHeaders() : new HashMap<String, List<String>>();
> +
> + String username = req.getProperties().get(
> + HttpProxyConstants.USER_PROPERTY);
> + String password = req.getProperties().get(
> + HttpProxyConstants.PWD_PROPERTY);
> +
> + StringUtilities.addValueToHeader(headers, "Proxy-Authorization",
> + "Basic " + createAuthorization(username, password), true);
> +
> + StringUtilities.addValueToHeader(headers, "Keep-Alive",
> + HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
> + StringUtilities.addValueToHeader(headers, "Proxy-Connection",
> + "keep-Alive", true);
> + req.setHeaders(headers);
> +
> + writeRequest(nextFilter, req);
> + step++;
> + }
> +
> + /**
> + * Computes authorization header value.
> + */
> + public static String createAuthorization(final String username,
> + final String password) {
> + return new String(Base64.encodeBase64((username + ":" + password)
> + .getBytes()));
> + }
> +
> + @Override
> + public void handleResponse(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + if (response.getStatusCode() != 407) {
> + throw new ProxyAuthException("Received error response code ("
> + + response.getStatusLine() + ").");
> + }
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,63 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.basic;
> +
> +import org.apache.mina.core.filterchain.IoFilter.NextFilter;
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
> +import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
> +import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpNoAuthLogicHandler.java - HTTP 'no auth' mechanism logic handler.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpNoAuthLogicHandler extends AbstractAuthLogicHandler {
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpNoAuthLogicHandler.class);
> +
> + public HttpNoAuthLogicHandler(final ProxyIoSession proxyIoSession)
> + throws ProxyAuthException {
> + super(proxyIoSession);
> + }
> +
> + @Override
> + public void doHandshake(final NextFilter nextFilter)
> + throws ProxyAuthException {
> + logger.debug(" doHandshake()");
> +
> + // Just send the request, no authentication needed
> + writeRequest(nextFilter, (HttpProxyRequest) request);
> + step++;
> + }
> +
> + @Override
> + public void handleResponse(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + // Should never get here !
> + throw new ProxyAuthException("Received error response code ("
> + + response.getStatusLine() + ").");
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,164 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.digest;
> +
> +import java.io.UnsupportedEncodingException;
> +import java.security.MessageDigest;
> +import java.security.NoSuchAlgorithmException;
> +import java.util.HashMap;
> +
> +import javax.security.sasl.AuthenticationException;
> +
> +import org.apache.mina.core.session.IoSession;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.apache.mina.proxy.utils.ByteUtilities;
> +import org.apache.mina.proxy.utils.StringUtilities;
> +
> +/**
> + * DigestUtilities.java - A class supporting the HTTP DIGEST authentication (see RFC 2617).
> + *
> + * Note: Some code has been borrowed from <a href="http://tedorg.free.fr/en/projects.php">Mailster</a>
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class DigestUtilities {
> +
> + public final static String SESSION_HA1 = DigestUtilities.class
> + + ".SessionHA1";
> +
> + private static MessageDigest md5;
> +
> + static {
> + // Initialize secure random generator
> + try {
> + md5 = MessageDigest.getInstance("MD5");
> + } catch (NoSuchAlgorithmException e) {
> + throw new RuntimeException(e);
> + }
> + }
> +
> + public final static String[] SUPPORTED_QOPS = new String[] { "auth",
> + "auth-int" };
> +
> + /**
> + * Computes the response to the DIGEST challenge.
> + */
> + public static String computeResponseValue(IoSession session,
> + HashMap<String, String> map, String method, String pwd,
> + String charsetName, String body) throws AuthenticationException,
> + UnsupportedEncodingException {
> +
> + byte[] hA1;
> + StringBuilder sb;
> + boolean isMD5Sess = "md5-sess".equalsIgnoreCase(StringUtilities
> + .getDirectiveValue(map, "algorithm", false));
> +
> + if (!!isMD5Sess || session.getAttribute(SESSION_HA1) == null) {
> + // Build A1
> + sb = new StringBuilder();
> + sb.append(
> + StringUtilities.stringTo8859_1(StringUtilities
> + .getDirectiveValue(map, "username", true))).append(
> + ':');
> +
> + String realm = StringUtilities.stringTo8859_1(StringUtilities
> + .getDirectiveValue(map, "realm", false));
> + if (realm != null) {
> + sb.append(realm);
> + }
> +
> + sb.append(':').append(pwd);
> +
> + if (isMD5Sess) {
> + byte[] prehA1;
> + synchronized (md5) {
> + md5.reset();
> + prehA1 = md5.digest(sb.toString().getBytes(charsetName));
> + }
> +
> + sb = new StringBuilder();
> + sb.append(ByteUtilities.asHex(prehA1));
> + sb.append(':').append(
> + StringUtilities.stringTo8859_1(StringUtilities
> + .getDirectiveValue(map, "nonce", true)));
> + sb.append(':').append(
> + StringUtilities.stringTo8859_1(StringUtilities
> + .getDirectiveValue(map, "cnonce", true)));
> +
> + synchronized (md5) {
> + md5.reset();
> + hA1 = md5.digest(sb.toString().getBytes(charsetName));
> + }
> +
> + session.setAttribute(SESSION_HA1, hA1);
> + } else {
> + synchronized (md5) {
> + md5.reset();
> + hA1 = md5.digest(sb.toString().getBytes(charsetName));
> + }
> + }
> + } else {
> + hA1 = (byte[]) session.getAttribute(SESSION_HA1);
> + }
> +
> + sb = new StringBuilder(method);
> + sb.append(':');
> + sb.append(StringUtilities.getDirectiveValue(map, "uri", false));
> +
> + String qop = StringUtilities.getDirectiveValue(map, "qop", false);
> + if ("auth-int".equalsIgnoreCase(qop)) {
> + ProxyIoSession proxyIoSession = (ProxyIoSession) session
> + .getAttribute(ProxyIoSession.PROXY_SESSION);
> + byte[] hEntity;
> +
> + synchronized (md5) {
> + md5.reset();
> + hEntity = md5.digest(body.getBytes(proxyIoSession
> + .getCharsetName()));
> + }
> + sb.append(':').append(hEntity);
> + }
> +
> + byte[] hA2;
> + synchronized (md5) {
> + md5.reset();
> + hA2 = md5.digest(sb.toString().getBytes(charsetName));
> + }
> +
> + sb = new StringBuilder();
> + sb.append(ByteUtilities.asHex(hA1));
> + sb.append(':').append(
> + StringUtilities.getDirectiveValue(map, "nonce", true));
> + sb.append(":00000001:");
> +
> + sb.append(StringUtilities.getDirectiveValue(map, "cnonce", true));
> + sb.append(':').append(qop).append(':');
> + sb.append(ByteUtilities.asHex(hA2));
> +
> + byte[] hFinal;
> + synchronized (md5) {
> + md5.reset();
> + hFinal = md5.digest(sb.toString().getBytes(charsetName));
> + }
> +
> + return ByteUtilities.asHex(hFinal);
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,260 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.digest;
> +
> +import java.io.UnsupportedEncodingException;
> +import java.security.NoSuchAlgorithmException;
> +import java.security.SecureRandom;
> +import java.util.Arrays;
> +import java.util.HashMap;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.StringTokenizer;
> +
> +import org.apache.commons.codec.binary.Base64;
> +import org.apache.mina.core.filterchain.IoFilter.NextFilter;
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
> +import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
> +import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
> +import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.apache.mina.proxy.utils.StringUtilities;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpDigestAuthLogicHandler.java - HTTP Digest authentication mechanism logic handler.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpDigestAuthLogicHandler extends AbstractAuthLogicHandler {
> +
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpDigestAuthLogicHandler.class);
> +
> + /**
> + * The challenge directives provided by the server.
> + */
> + private HashMap<String, String> directives = null;
> +
> + /**
> + * The response received to the last request.
> + */
> + private HttpProxyResponse response;
> +
> + private static SecureRandom rnd;
> +
> + static {
> + // Initialize secure random generator
> + try {
> + rnd = SecureRandom.getInstance("SHA1PRNG");
> + } catch (NoSuchAlgorithmException e) {
> + throw new RuntimeException(e);
> + }
> + }
> +
> + public HttpDigestAuthLogicHandler(final ProxyIoSession proxyIoSession)
> + throws ProxyAuthException {
> + super(proxyIoSession);
> +
> + if (request == null || !(request instanceof HttpProxyRequest)) {
> + throw new IllegalArgumentException(
> + "request parameter should be a non null HttpProxyRequest instance");
> + }
> +
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
> + req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
> + }
> +
> + @Override
> + public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
> + logger.debug(" doHandshake()");
> +
> + if (step > 0 && directives == null) {
> + throw new ProxyAuthException(
> + "Authentication challenge not received");
> + } else {
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + Map<String, List<String>> headers = req.getHeaders() != null ? req
> + .getHeaders() : new HashMap<String, List<String>>();
> +
> + if (step > 0) {
> + logger.debug(" sending DIGEST challenge response");
> +
> + HashMap<String, String> map = new HashMap<String, String>();
> + map.put("username", req.getProperties().get(
> + HttpProxyConstants.USER_PROPERTY));
> + StringUtilities.copyDirective(directives, map, "realm");
> + StringUtilities.copyDirective(directives, map, "uri");
> + StringUtilities.copyDirective(directives, map, "opaque");
> + StringUtilities.copyDirective(directives, map, "nonce");
> + String algorithm = StringUtilities.copyDirective(directives,
> + map, "algorithm");
> +
> + // Check for a supported algorithm
> + if (algorithm != null && !"md5".equalsIgnoreCase(algorithm)
> + && !"md5-sess".equalsIgnoreCase(algorithm)) {
> + throw new ProxyAuthException(
> + "Unknown algorithm required by server");
> + }
> +
> + // Check for a supported qop
> + String qop = directives.get("qop");
> + if (qop != null) {
> + StringTokenizer st = new StringTokenizer(qop, ",");
> + String token = null;
> +
> + while (st.hasMoreTokens()) {
> + String tk = st.nextToken();
> + if ("auth".equalsIgnoreCase(token)) {
> + break;
> + } else {
> + int pos = Arrays.binarySearch(
> + DigestUtilities.SUPPORTED_QOPS, tk);
> + if (pos > -1) {
> + token = tk;
> + }
> + }
> + }
> +
> + if (token != null) {
> + map.put("qop", token);
> +
> + byte[] nonce = new byte[8];
> + rnd.nextBytes(nonce);
> +
> + try {
> + String cnonce = new String(Base64
> + .encodeBase64(nonce), proxyIoSession
> + .getCharsetName());
> + map.put("cnonce", cnonce);
> + } catch (UnsupportedEncodingException e) {
> + throw new ProxyAuthException(
> + "Unable to encode cnonce", e);
> + }
> + } else {
> + throw new ProxyAuthException(
> + "No supported qop option available");
> + }
> + }
> +
> + map.put("nc", "00000001");
> + map.put("uri", req.getHttpURI());
> +
> + // Compute the response
> + try {
> + map.put("response", DigestUtilities
> + .computeResponseValue(proxyIoSession.getSession(),
> + map, req.getHttpVerb().toUpperCase(),
> + req.getProperties().get(
> + HttpProxyConstants.PWD_PROPERTY),
> + proxyIoSession.getCharsetName(), response
> + .getBody()));
> +
> + } catch (Exception e) {
> + throw new ProxyAuthException(
> + "Digest response computing failed", e);
> + }
> +
> + // Prepare the challenge response header and add it to the request we will send
> + StringBuilder sb = new StringBuilder("Digest ");
> + boolean addSeparator = false;
> +
> + for (String key : map.keySet()) {
> +
> + if (addSeparator) {
> + sb.append(", ");
> + } else {
> + addSeparator = true;
> + }
> +
> + boolean quotedValue = !"qop".equals(key)
> + && !"nc".equals(key);
> + sb.append(key);
> + if (quotedValue) {
> + sb.append("=\"").append(map.get(key)).append('\"');
> + } else {
> + sb.append('=').append(map.get(key));
> + }
> + }
> +
> + StringUtilities.addValueToHeader(headers,
> + "Proxy-Authorization", sb.toString(), true);
> + }
> +
> + StringUtilities.addValueToHeader(headers, "Keep-Alive",
> + HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
> + StringUtilities.addValueToHeader(headers, "Proxy-Connection",
> + "keep-Alive", true);
> + req.setHeaders(headers);
> +
> + writeRequest(nextFilter, req);
> + step++;
> + }
> + }
> +
> + @Override
> + public void handleResponse(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + this.response = response;
> +
> + if (step == 0) {
> + if (response.getStatusCode() != 401
> + && response.getStatusCode() != 407) {
> + throw new ProxyAuthException(
> + "Received unexpected response code ("
> + + response.getStatusLine() + ").");
> + }
> +
> + // Header should be like this
> + // Proxy-Authenticate: Digest still_some_more_stuff
> + List<String> values = response.getHeaders().get(
> + "Proxy-Authenticate");
> + String challengeResponse = null;
> +
> + for (String s : values) {
> + if (s.startsWith("Digest")) {
> + challengeResponse = s;
> + break;
> + }
> + }
> +
> + if (challengeResponse == null) {
> + throw new ProxyAuthException(
> + "Server doesn't support digest authentication method !");
> + }
> +
> + try {
> + directives = StringUtilities.parseDirectives(challengeResponse
> + .substring(7).getBytes(proxyIoSession.getCharsetName()));
> + } catch (Exception e) {
> + throw new ProxyAuthException(
> + "Parsing of server digest directives failed", e);
> + }
> + step = 1;
> + } else {
> + throw new ProxyAuthException("Received unexpected response code ("
> + + response.getStatusLine() + ").");
> + }
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,191 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.ntlm;
> +
> +import java.io.IOException;
> +import java.util.HashMap;
> +import java.util.List;
> +import java.util.Map;
> +
> +import org.apache.commons.codec.binary.Base64;
> +import org.apache.mina.core.filterchain.IoFilter.NextFilter;
> +import org.apache.mina.proxy.ProxyAuthException;
> +import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
> +import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
> +import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
> +import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
> +import org.apache.mina.proxy.session.ProxyIoSession;
> +import org.apache.mina.proxy.utils.StringUtilities;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * HttpNTLMAuthLogicHandler.java - HTTP NTLM authentication mechanism logic handler.
> + *
> + * @author Edouard De Oliveira <a href="mailto:doe_wanted@yahoo.fr">doe_wanted@yahoo.fr</a>
> + * @version $Id: $
> + */
> +public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler {
> +
> + private final static Logger logger = LoggerFactory
> + .getLogger(HttpNTLMAuthLogicHandler.class);
> +
> + /**
> + * The challenge provided by the server.
> + */
> + private byte[] challengePacket = null;
> +
> + public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession)
> + throws ProxyAuthException {
> + super(proxyIoSession);
> +
> + if (request == null || !(request instanceof HttpProxyRequest)) {
> + throw new IllegalArgumentException(
> + "request parameter should be a non null HttpProxyRequest instance");
> + }
> +
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
> + req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
> + req.checkRequiredProperty(HttpProxyConstants.DOMAIN_PROPERTY);
> + req.checkRequiredProperty(HttpProxyConstants.WORKSTATION_PROPERTY);
> + }
> +
> + @Override
> + public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
> + logger.debug(" doHandshake()");
> +
> + if (step > 0 && challengePacket == null) {
> + throw new IllegalStateException("Challenge packet not received");
> + } else {
> + HttpProxyRequest req = (HttpProxyRequest) request;
> + Map<String, List<String>> headers = req.getHeaders() != null ? req
> + .getHeaders() : new HashMap<String, List<String>>();
> +
> + String domain = req.getProperties().get(
> + HttpProxyConstants.DOMAIN_PROPERTY);
> + String workstation = req.getProperties().get(
> + HttpProxyConstants.WORKSTATION_PROPERTY);
> +
> + if (step > 0) {
> + logger.debug(" sending NTLM challenge response");
> +
> + byte[] challenge = NTLMUtilities
> + .extractChallengeFromType2Message(challengePacket);
> + int serverFlags = NTLMUtilities
> + .extractFlagsFromType2Message(challengePacket);
> +
> + String username = req.getProperties().get(
> + HttpProxyConstants.USER_PROPERTY);
> + String password = req.getProperties().get(
> + HttpProxyConstants.PWD_PROPERTY);
> +
> + byte[] authenticationPacket = NTLMUtilities.createType3Message(
> + username, password, challenge, domain, workstation,
> + serverFlags, null);
> +
> + StringUtilities.addValueToHeader(headers,
> + "Proxy-Authorization", "NTLM "
> + + new String(Base64
> + .encodeBase64(authenticationPacket)),
> + true);
> +
> + } else {
> + logger.debug(" sending HTTP request");
> +
> + byte[] negotiationPacket = NTLMUtilities.createType1Message(
> + workstation, domain, null, null);
> + StringUtilities
> + .addValueToHeader(
> + headers,
> + "Proxy-Authorization",
> + "NTLM "
> + + new String(
> + Base64
> + .encodeBase64(negotiationPacket)),
> + true);
> + }
> +
> + StringUtilities.addValueToHeader(headers, "Keep-Alive",
> + HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
> + StringUtilities.addValueToHeader(headers, "Proxy-Connection",
> + "keep-Alive", true);
> + req.setHeaders(headers);
> +
> + writeRequest(nextFilter, req);
> + step++;
> + }
> + }
> +
> + /**
> + * Returns the value of the NTLM Proxy-Authenticate header.
> + */
> + private String getNTLMHeader(final HttpProxyResponse response) {
> + List<String> values = response.getHeaders().get("Proxy-Authenticate");
> +
> + for (String s : values) {
> + if (s.startsWith("NTLM")) {
> + return s;
> + }
> + }
> +
> + return null;
> + }
> +
> + @Override
> + public void handleResponse(final HttpProxyResponse response)
> + throws ProxyAuthException {
> + if (step == 0) {
> + String challengeResponse = getNTLMHeader(response);
> + step = 1;
> +
> + if (challengeResponse == null || challengeResponse.length() < 5) {
> + // Nothing to handle at this step. Just need to send a reply type 1 message in doHandshake().
> + return;
> + }
> +
> + // else there was no step 0 so continue to step 1.
> + }
> +
> + if (step == 1) {
> + // Header should be like this
> + // Proxy-Authenticate: NTLM still_some_more_stuff
> + String challengeResponse = getNTLMHeader(response);
> +
> + if (challengeResponse == null || challengeResponse.length() < 5) {
> + throw new ProxyAuthException(
> + "Unexpected error while reading server challenge !");
> + }
> +
> + try {
> + challengePacket = Base64
> + .decodeBase64(challengeResponse.substring(5).getBytes(
> + proxyIoSession.getCharsetName()));
> + } catch (IOException e) {
> + throw new ProxyAuthException(
> + "Unable to decode the base64 encoded NTLM challenge", e);
> + }
> + step = 2;
> + } else {
> + throw new ProxyAuthException("Received unexpected response code ("
> + + response.getStatusLine() + ").");
> + }
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,184 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.ntlm;
> +
> +/**
> + * NTLMConstants.java - All NTLM constants.
> + *
> + * @author <a href="mailto:doe_wanted@yahoo.fr">Edouard De Oliveira</a>
> + * @version $Revision:$, $Date:$
> + */
> +public interface NTLMConstants {
> + // Signature "NTLMSSP"+{0}
> + public final static byte[] NTLM_SIGNATURE = new byte[] { 0x4E, 0x54, 0x4C,
> + 0x4D, 0x53, 0x53, 0x50, 0 };
> +
> + // Version 5.1.2600 a Windows XP version (ex: Build 2600.xpsp_sp2_gdr.050301-1519 : Service Pack 2)
> + public final static byte[] DEFAULT_OS_VERSION = new byte[] { 0x05, 0x01,
> + 0x28, 0x0A, 0, 0, 0, 0x0F };
> +
> + /**
> + * Message types
> + */
> +
> + public final static int MESSAGE_TYPE_1 = 1;
> +
> + public final static int MESSAGE_TYPE_2 = 2;
> +
> + public final static int MESSAGE_TYPE_3 = 3;
> +
> + /**
> + * Message flags
> + */
> +
> + // Indicates that Unicode strings are supported for use in security buffer data
> + public final static int FLAG_NEGOTIATE_UNICODE = 0x00000001;
> +
> + // Indicates that OEM strings are supported for use in security buffer data
> + public final static int FLAG_NEGOTIATE_OEM = 0x00000002;
> +
> + // Requests that the server's authentication realm be included in the Type 2 message
> + public final static int FLAG_REQUEST_SERVER_AUTH_REALM = 0x00000004;
> +
> + // Specifies that authenticated communication between the client
> + // and server should carry a digital signature (message integrity)
> + public final static int FLAG_NEGOTIATE_SIGN = 0x00000010;
> +
> + // Specifies that authenticated communication between the client
> + // and server should be encrypted (message confidentiality)
> + public final static int FLAG_NEGOTIATE_SEAL = 0x00000020;
> +
> + // Indicates that datagram authentication is being used
> + public final static int FLAG_NEGOTIATE_DATAGRAM_STYLE = 0x00000040;
> +
> + // Indicates that the Lan Manager Session Key should be used for signing and
> + // sealing authenticated communications
> + public final static int FLAG_NEGOTIATE_LAN_MANAGER_KEY = 0x00000080;
> +
> + // Indicates that NTLM authentication is being used
> + public final static int FLAG_NEGOTIATE_NTLM = 0x00000200;
> +
> + // Sent by the client in the Type 3 message to indicate that an anonymous context
> + // has been established. This also affects the response fields
> + public final static int FLAG_NEGOTIATE_ANONYMOUS = 0x00000800;
> +
> + // Sent by the client in the Type 1 message to indicate that the name of the domain in which
> + // the client workstation has membership is included in the message. This is used by the
> + // server to determine whether the client is eligible for local authentication
> + public final static int FLAG_NEGOTIATE_DOMAIN_SUPPLIED = 0x00001000;
> +
> + // Sent by the client in the Type 1 message to indicate that the client workstation's name
> + // is included in the message. This is used by the server to determine whether the client
> + // is eligible for local authentication
> + public final static int FLAG_NEGOTIATE_WORKSTATION_SUPPLIED = 0x00002000;
> +
> + // Sent by the server to indicate that the server and client are on the same machine.
> + // Implies that the client may use the established local credentials for authentication
> + // instead of calculating a response to the challenge
> + public final static int FLAG_NEGOTIATE_LOCAL_CALL = 0x00004000;
> +
> + // Indicates that authenticated communication between the client and server should
> + // be signed with a "dummy" signature
> + public final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
> +
> + // Sent by the server in the Type 2 message to indicate that the target authentication
> + // realm is a domain
> + public final static int FLAG_TARGET_TYPE_DOMAIN = 0x00010000;
> +
> + // Sent by the server in the Type 2 message to indicate that the target authentication
> + // realm is a server
> + public final static int FLAG_TARGET_TYPE_SERVER = 0x00020000;
> +
> + // Sent by the server in the Type 2 message to indicate that the target authentication
> + // realm is a share. Presumably, this is for share-level authentication. Usage is unclear
> + public final static int FLAG_TARGET_TYPE_SHARE = 0x00040000;
> +
> + // Indicates that the NTLM2 signing and sealing scheme should be used for protecting
> + // authenticated communications. Note that this refers to a particular session security
> + // scheme, and is not related to the use of NTLMv2 authentication. This flag can, however,
> + // have an effect on the response calculations
> + public final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000;
> +
> + // Sent by the server in the Type 2 message to indicate that it is including a Target
> + // Information block in the message. The Target Information block is used in the
> + // calculation of the NTLMv2 response
> + public final static int FLAG_NEGOTIATE_TARGET_INFO = 0x00800000;
> +
> + // Indicates that 128-bit encryption is supported
> + public final static int FLAG_NEGOTIATE_128_BIT_ENCRYPTION = 0x20000000;
> +
> + // Indicates that the client will provide an encrypted master key in the "Session Key"
> + // field of the Type 3 message
> + public final static int FLAG_NEGOTIATE_KEY_EXCHANGE = 0x40000000;
> +
> + // Indicates that 56-bit encryption is supported
> + public final static int FLAG_NEGOTIATE_56_BIT_ENCRYPTION = 0x80000000;
> +
> + // WARN : These flags usage has not been identified
> + public final static int FLAG_UNIDENTIFIED_1 = 0x00000008;
> +
> + public final static int FLAG_UNIDENTIFIED_2 = 0x00000100; // Negotiate Netware ??!
> +
> + public final static int FLAG_UNIDENTIFIED_3 = 0x00000400;
> +
> + public final static int FLAG_UNIDENTIFIED_4 = 0x00100000; // Request Init Response ??!
> +
> + public final static int FLAG_UNIDENTIFIED_5 = 0x00200000; // Request Accept Response ??!
> +
> + public final static int FLAG_UNIDENTIFIED_6 = 0x00400000; // Request Non-NT Session Key ??!
> +
> + public final static int FLAG_UNIDENTIFIED_7 = 0x01000000;
> +
> + public final static int FLAG_UNIDENTIFIED_8 = 0x02000000;
> +
> + public final static int FLAG_UNIDENTIFIED_9 = 0x04000000;
> +
> + public final static int FLAG_UNIDENTIFIED_10 = 0x08000000;
> +
> + public final static int FLAG_UNIDENTIFIED_11 = 0x10000000;
> +
> + // Default minimal flag set
> + public final static int DEFAULT_FLAGS = FLAG_NEGOTIATE_OEM
> + | FLAG_NEGOTIATE_UNICODE | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
> + | FLAG_NEGOTIATE_DOMAIN_SUPPLIED;
> +
> + /**
> + * Target Information sub blocks types. It may be that there are other
> + * as-yet-unidentified sub block types as well.
> + */
> +
> + // Sub block terminator
> + public final static short TARGET_INFORMATION_SUBBLOCK_TERMINATOR_TYPE = 0x0000;
> +
> + // Server name
> + public final static short TARGET_INFORMATION_SUBBLOCK_SERVER_TYPE = 0x0100;
> +
> + // Domain name
> + public final static short TARGET_INFORMATION_SUBBLOCK_DOMAIN_TYPE = 0x0200;
> +
> + // Fully-qualified DNS host name (i.e., server.domain.com)
> + public final static short TARGET_INFORMATION_SUBBLOCK_FQDNS_HOSTNAME_TYPE = 0x0300;
> +
> + // DNS domain name (i.e., domain.com)
> + public final static short TARGET_INFORMATION_SUBBLOCK_DNS_DOMAIN_NAME_TYPE = 0x0400;
> +
> + // Apparently the "parent" DNS domain for servers in sub domains
> + public final static short TARGET_INFORMATION_SUBBLOCK_PARENT_DNS_DOMAIN_NAME_TYPE = 0x0500;
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
> Added: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
> URL: http://svn.apache.org/viewvc/mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java?rev=685389&view=auto
> ==============================================================================
> --- mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java (added)
> +++ mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java Tue Aug 12 17:05:41 2008
> @@ -0,0 +1,413 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one
> + * or more contributor license agreements. See the NOTICE file
> + * distributed with this work for additional information
> + * regarding copyright ownership. The ASF licenses this file
> + * to you 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.
> + *
> + */
> +package org.apache.mina.proxy.handlers.http.ntlm;
> +
> +import java.io.UnsupportedEncodingException;
> +import java.security.Key;
> +import java.security.MessageDigest;
> +
> +import javax.crypto.Cipher;
> +
> +import javax.crypto.spec.SecretKeySpec;
> +
> +/**
> + * NTLMResponses.java - Calculates the various Type 3 responses. Needs an MD4, MD5 and DES
> + * crypto provider (Please note that default provider doesn't provide MD4).
> + *
> + * Base code borrowed from : http://curl.haxx.se/rfc/ntlm.html
> + *
> + * @author <a href="mailto:doe_wanted@yahoo.fr">Edouard De Oliveira</a>
> + * @version $Revision:$, $Date:$
> + */
> +public class NTLMResponses {
> +
> + // LAN Manager magic constant used in LM Response calculation
> + public static byte[] LM_HASH_MAGIC_CONSTANT = null;
> +
> + static {
> + try {
> + LM_HASH_MAGIC_CONSTANT = "KGS!@#$%".getBytes("US-ASCII");
> + } catch (UnsupportedEncodingException e) {
> + e.printStackTrace();
> + }
> + }
> +
> + /**
> + * Calculates the LM Response for the given challenge, using the specified
> + * password.
> + *
> + * @param password The user's password.
> + * @param challenge The Type 2 challenge from the server.
> + *
> + * @return The LM Response.
> + */
> + public static byte[] getLMResponse(String password, byte[] challenge)
> + throws Exception {
> + byte[] lmHash = lmHash(password);
> + return lmResponse(lmHash, challenge);
> + }
> +
> + /**
> + * Calculates the NTLM Response for the given challenge, using the
> + * specified password.
> + *
> + * @param password The user's password.
> + * @param challenge The Type 2 challenge from the server.
> + *
> + * @return The NTLM Response.
> + */
> + public static byte[] getNTLMResponse(String password, byte[] challenge)
> + throws Exception {
> + byte[] ntlmHash = ntlmHash(password);
> + return lmResponse(ntlmHash, challenge);
> + }
> +
> + /**
> + * Calculates the NTLMv2 Response for the given challenge, using the
> + * specified authentication target, username, password, target information
> + * block, and client nonce.
> + *
> + * @param target The authentication target (i.e., domain).
> + * @param user The username.
> + * @param password The user's password.
> + * @param targetInformation The target information block from the Type 2
> + * message.
> + * @param challenge The Type 2 challenge from the server.
> + * @param clientNonce The random 8-byte client nonce.
> + *
> + * @return The NTLMv2 Response.
> + */
> + public static byte[] getNTLMv2Response(String target, String user,
> + String password, byte[] targetInformation, byte[] challenge,
> + byte[] clientNonce) throws Exception {
> +
> + return getNTLMv2Response(target, user, password, targetInformation,
> + challenge, clientNonce, System.currentTimeMillis());
> + }
> +
> + /**
> + * Calculates the NTLMv2 Response for the given challenge, using the
> + * specified authentication target, username, password, target information
> + * block, and client nonce.
> + *
> + * @param target The authentication target (i.e., domain).
> + * @param user The username.
> + * @param password The user's password.
> + * @param targetInformation The target information block from the Type 2
> + * message.
> + * @param challenge The Type 2 challenge from the server.
> + * @param clientNonce The random 8-byte client nonce.
> + * @param time The time stamp.
> + *
> + * @return The NTLMv2 Response.
> + */
> + public static byte[] getNTLMv2Response(String target, String user,
> + String password, byte[] targetInformation, byte[] challenge,
> + byte[] clientNonce, long time) throws Exception {
> + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
> + byte[] blob = createBlob(targetInformation, clientNonce, time);
> + return lmv2Response(ntlmv2Hash, blob, challenge);
> + }
> +
> + /**
> + * Calculates the LMv2 Response for the given challenge, using the
> + * specified authentication target, username, password, and client
> + * challenge.
> + *
> + * @param target The authentication target (i.e., domain).
> + * @param user The username.
> + * @param password The user's password.
> + * @param challenge The Type 2 challenge from the server.
> + * @param clientNonce The random 8-byte client nonce.
> + *
> + * @return The LMv2 Response.
> + */
> + public static byte[] getLMv2Response(String target, String user,
> + String password, byte[] challenge, byte[] clientNonce)
> + throws Exception {
> + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
> + return lmv2Response(ntlmv2Hash, clientNonce, challenge);
> + }
> +
> + /**
> + * Calculates the NTLM2 Session Response for the given challenge, using the
> + * specified password and client nonce.
> + *
> + * @param password The user's password.
> + * @param challenge The Type 2 challenge from the server.
> + * @param clientNonce The random 8-byte client nonce.
> + *
> + * @return The NTLM2 Session Response. This is placed in the NTLM
> + * response field of the Type 3 message; the LM response field contains
> + * the client nonce, null-padded to 24 bytes.
> + */
> + public static byte[] getNTLM2SessionResponse(String password,
> + byte[] challenge, byte[] clientNonce) throws Exception {
> + byte[] ntlmHash = ntlmHash(password);
> + MessageDigest md5 = MessageDigest.getInstance("MD5");
> + md5.update(challenge);
> + md5.update(clientNonce);
> + byte[] sessionHash = new byte[8];
> + System.arraycopy(md5.digest(), 0, sessionHash, 0, 8);
> + return lmResponse(ntlmHash, sessionHash);
> + }
> +
> + /**
> + * Creates the LM Hash of the user's password.
> + *
> + * @param password The password.
> + *
> + * @return The LM Hash of the given password, used in the calculation
> + * of the LM Response.
> + */
> + private static byte[] lmHash(String password) throws Exception {
> + byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII");
> + int length = Math.min(oemPassword.length, 14);
> + byte[] keyBytes = new byte[14];
> + System.arraycopy(oemPassword, 0, keyBytes, 0, length);
> + Key lowKey = createDESKey(keyBytes, 0);
> + Key highKey = createDESKey(keyBytes, 7);
> + Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
> + des.init(Cipher.ENCRYPT_MODE, lowKey);
> + byte[] lowHash = des.doFinal(LM_HASH_MAGIC_CONSTANT);
> + des.init(Cipher.ENCRYPT_MODE, highKey);
> + byte[] highHash = des.doFinal(LM_HASH_MAGIC_CONSTANT);
> + byte[] lmHash = new byte[16];
> + System.arraycopy(lowHash, 0, lmHash, 0, 8);
> + System.arraycopy(highHash, 0, lmHash, 8, 8);
> + return lmHash;
> + }
> +
> + /**
> + * Creates the NTLM Hash of the user's password.
> + *
> + * @param password The password.
> + *
> + * @return The NTLM Hash of the given password, used in the calculation
> + * of the NTLM Response and the NTLMv2 and LMv2 Hashes.
> + */
> + private static byte[] ntlmHash(String password) throws Exception {
> + byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked");
> + MessageDigest md4 = MessageDigest.getInstance("MD4");
> + return md4.digest(unicodePassword);
> + }
> +
> + /**
> + * Creates the NTLMv2 Hash of the user's password.
> + *
> + * @param target The authentication target (i.e., domain).
> + * @param user The username.
> + * @param password The password.
> + *
> + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2
> + * and LMv2 Responses.
> + */
> + private static byte[] ntlmv2Hash(String target, String user, String password)
> + throws Exception {
> + byte[] ntlmHash = ntlmHash(password);
> + String identity = user.toUpperCase() + target;
> + return hmacMD5(identity.getBytes("UnicodeLittleUnmarked"), ntlmHash);
> + }
> +
> + /**
> + * Creates the LM Response from the given hash and Type 2 challenge.
> + *
> + * @param hash The LM or NTLM Hash.
> + * @param challenge The server challenge from the Type 2 message.
> + *
> + * @return The response (either LM or NTLM, depending on the provided
> + * hash).
> + */
> + private static byte[] lmResponse(byte[] hash, byte[] challenge)
> + throws Exception {
> + byte[] keyBytes = new byte[21];
> + System.arraycopy(hash, 0, keyBytes, 0, 16);
> + Key lowKey = createDESKey(keyBytes, 0);
> + Key middleKey = createDESKey(keyBytes, 7);
> + Key highKey = createDESKey(keyBytes, 14);
> + Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
> + des.init(Cipher.ENCRYPT_MODE, lowKey);
> + byte[] lowResponse = des.doFinal(challenge);
> + des.init(Cipher.ENCRYPT_MODE, middleKey);
> + byte[] middleResponse = des.doFinal(challenge);
> + des.init(Cipher.ENCRYPT_MODE, highKey);
> + byte[] highResponse = des.doFinal(challenge);
> + byte[] lmResponse = new byte[24];
> + System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
> + System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
> + System.arraycopy(highResponse, 0, lmResponse, 16, 8);
> + return lmResponse;
> + }
> +
> + /**
> + * Creates the LMv2 Response from the given hash, client data, and
> + * Type 2 challenge.
> + *
> + * @param hash The NTLMv2 Hash.
> + * @param clientData The client data (blob or client nonce).
> + * @param challenge The server challenge from the Type 2 message.
> + *
> + * @return The response (either NTLMv2 or LMv2, depending on the
> + * client data).
> + */
> + private static byte[] lmv2Response(byte[] hash, byte[] clientData,
> + byte[] challenge) throws Exception {
> + byte[] data = new byte[challenge.length + clientData.length];
> + System.arraycopy(challenge, 0, data, 0, challenge.length);
> + System.arraycopy(clientData, 0, data, challenge.length,
> + clientData.length);
> + byte[] mac = hmacMD5(data, hash);
> + byte[] lmv2Response = new byte[mac.length + clientData.length];
> + System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
> + System.arraycopy(clientData, 0, lmv2Response, mac.length,
> + clientData.length);
> + return lmv2Response;
> + }
> +
> + /**
> + * Creates the NTLMv2 blob from the given target information block and
> + * client nonce.
> + *
> + * @param targetInformation The target information block from the Type 2
> + * message.
> + * @param clientNonce The random 8-byte client nonce.
> + * @param time the time stamp.
> + *
> + * @return The blob, used in the calculation of the NTLMv2 Response.
> + */
> + private static byte[] createBlob(byte[] targetInformation,
> + byte[] clientNonce, long time) {
> + byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01,
> + (byte) 0x00, (byte) 0x00 };
> + byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
> + (byte) 0x00 };
> + byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
> + (byte) 0x00 };
> + byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
> + (byte) 0x00 };
> + time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
> + time *= 10000; // tenths of a microsecond.
> + // convert to little-endian byte array.
> + byte[] timestamp = new byte[8];
> + for (int i = 0; i < 8; i++) {
> + timestamp[i] = (byte) time;
> + time >>>= 8;
> + }
> + byte[] blob = new byte[blobSignature.length + reserved.length
> + + timestamp.length + clientNonce.length + unknown1.length
> + + targetInformation.length + unknown2.length];
> + int offset = 0;
> + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
> + offset += blobSignature.length;
> + System.arraycopy(reserved, 0, blob, offset, reserved.length);
> + offset += reserved.length;
> + System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
> + offset += timestamp.length;
> + System.arraycopy(clientNonce, 0, blob, offset, clientNonce.length);
> + offset += clientNonce.length;
> + System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
> + offset += unknown1.length;
> + System.arraycopy(targetInformation, 0, blob, offset,
> + targetInformation.length);
> + offset += targetInformation.length;
> + System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
> + return blob;
> + }
> +
> + /**
> + * Calculates the HMAC-MD5 hash of the given data using the specified
> + * hashing key.
> + *
> + * @param data The data for which the hash will be calculated.
> + * @param key The hashing key.
> + *
> + * @return The HMAC-MD5 hash of the given data.
> + */
> + public static byte[] hmacMD5(byte[] data, byte[] key) throws Exception {
> + byte[] ipad = new byte[64];
> + byte[] opad = new byte[64];
> +
> + // Stores key in pads and XOR it with ipad and opad values
> + for (int i = 0; i < 64; i++) {
> + if (i < key.length) {
> + ipad[i] = (byte) (key[i] ^ 0x36);
> + opad[i] = (byte) (key[i] ^ 0x5c);
> + } else {
> + ipad[i] = 0x36;
> + opad[i] = 0x5c;
> + }
> + }
> +
> + byte[] content = new byte[data.length + 64];
> + System.arraycopy(ipad, 0, content, 0, 64);
> + System.arraycopy(data, 0, content, 64, data.length);
> + MessageDigest md5 = MessageDigest.getInstance("MD5");
> + data = md5.digest(content);
> + content = new byte[data.length + 64];
> + System.arraycopy(opad, 0, content, 0, 64);
> + System.arraycopy(data, 0, content, 64, data.length);
> + return md5.digest(content);
> + }
> +
> + /**
> + * Creates a DES encryption key from the given key material.
> + *
> + * @param bytes A byte array containing the DES key material.
> + * @param offset The offset in the given byte array at which
> + * the 7-byte key material starts.
> + *
> + * @return A DES encryption key created from the key material
> + * starting at the specified offset in the given byte array.
> + */
> + private static Key createDESKey(byte[] bytes, int offset) {
> + byte[] keyBytes = new byte[7];
> + System.arraycopy(bytes, offset, keyBytes, 0, 7);
> + byte[] material = new byte[8];
> + material[0] = keyBytes[0];
> + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1);
> + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2);
> + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3);
> + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4);
> + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5);
> + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6);
> + material[7] = (byte) (keyBytes[6] << 1);
> + oddParity(material);
> + return new SecretKeySpec(material, "DES");
> + }
> +
> + /**
> + * Applies odd parity to the given byte array.
> + *
> + * @param bytes The data whose parity bits are to be adjusted for
> + * odd parity.
> + */
> + private static void oddParity(byte[] bytes) {
> + for (int i = 0; i < bytes.length; i++) {
> + byte b = bytes[i];
> + boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5)
> + ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
> + if (needsParity) {
> + bytes[i] |= (byte) 0x01;
> + } else {
> + bytes[i] &= (byte) 0xfe;
> + }
> + }
> + }
> +}
> \ No newline at end of file
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Propchange: mina/trunk/core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java
> ------------------------------------------------------------------------------
> svn:mime-type = text/plain
>
>
>