001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.proxy.handlers.http.ntlm; 021 022import java.io.IOException; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.mina.core.filterchain.IoFilter.NextFilter; 028import org.apache.mina.proxy.ProxyAuthException; 029import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler; 030import org.apache.mina.proxy.handlers.http.HttpProxyConstants; 031import org.apache.mina.proxy.handlers.http.HttpProxyRequest; 032import org.apache.mina.proxy.handlers.http.HttpProxyResponse; 033import org.apache.mina.proxy.session.ProxyIoSession; 034import org.apache.mina.proxy.utils.StringUtilities; 035import org.apache.mina.util.Base64; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * HttpNTLMAuthLogicHandler.java - HTTP NTLM authentication mechanism logic handler. 041 * 042 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 043 * @since MINA 2.0.0-M3 044 */ 045public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler { 046 047 private final static Logger LOGGER = LoggerFactory.getLogger(HttpNTLMAuthLogicHandler.class); 048 049 /** 050 * The challenge provided by the server. 051 */ 052 private byte[] challengePacket = null; 053 054 /** 055 * Build an HttpNTLMAuthLogicHandler 056 * 057 * @param proxyIoSession The original session 058 * @throws ProxyAuthException If we get an error during the proxy authentication 059 */ 060 public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException { 061 super(proxyIoSession); 062 063 ((HttpProxyRequest) request).checkRequiredProperties(HttpProxyConstants.USER_PROPERTY, 064 HttpProxyConstants.PWD_PROPERTY, HttpProxyConstants.DOMAIN_PROPERTY, 065 HttpProxyConstants.WORKSTATION_PROPERTY); 066 } 067 068 /** 069 * {@inheritDoc} 070 */ 071 @Override 072 public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { 073 LOGGER.debug(" doHandshake()"); 074 075 if (step > 0 && challengePacket == null) { 076 throw new IllegalStateException("NTLM Challenge packet not received"); 077 } 078 079 HttpProxyRequest req = (HttpProxyRequest) request; 080 Map<String, List<String>> headers = req.getHeaders() != null ? req.getHeaders() 081 : new HashMap<String, List<String>>(); 082 083 String domain = req.getProperties().get(HttpProxyConstants.DOMAIN_PROPERTY); 084 String workstation = req.getProperties().get(HttpProxyConstants.WORKSTATION_PROPERTY); 085 086 if (step > 0) { 087 LOGGER.debug(" sending NTLM challenge response"); 088 089 byte[] challenge = NTLMUtilities.extractChallengeFromType2Message(challengePacket); 090 int serverFlags = NTLMUtilities.extractFlagsFromType2Message(challengePacket); 091 092 String username = req.getProperties().get(HttpProxyConstants.USER_PROPERTY); 093 String password = req.getProperties().get(HttpProxyConstants.PWD_PROPERTY); 094 095 byte[] authenticationPacket = NTLMUtilities.createType3Message(username, password, challenge, domain, 096 workstation, serverFlags, null); 097 098 StringUtilities.addValueToHeader(headers, "Proxy-Authorization", 099 "NTLM " + new String(Base64.encodeBase64(authenticationPacket)), true); 100 101 } else { 102 LOGGER.debug(" sending NTLM negotiation packet"); 103 104 byte[] negotiationPacket = NTLMUtilities.createType1Message(workstation, domain, null, null); 105 StringUtilities.addValueToHeader(headers, "Proxy-Authorization", 106 "NTLM " + new String(Base64.encodeBase64(negotiationPacket)), true); 107 } 108 109 addKeepAliveHeaders(headers); 110 req.setHeaders(headers); 111 112 writeRequest(nextFilter, req); 113 step++; 114 } 115 116 /** 117 * @return the value of the NTLM Proxy-Authenticate header. 118 * 119 * @param response the proxy response 120 */ 121 private String getNTLMHeader(final HttpProxyResponse response) { 122 List<String> values = response.getHeaders().get("Proxy-Authenticate"); 123 124 for (String s : values) { 125 if (s.startsWith("NTLM")) { 126 return s; 127 } 128 } 129 130 return null; 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { 138 if (step == 0) { 139 String challengeResponse = getNTLMHeader(response); 140 step = 1; 141 142 if (challengeResponse == null || challengeResponse.length() < 5) { 143 // Nothing to handle at this step. 144 // Just need to send a reply type 1 message in doHandshake(). 145 return; 146 } 147 148 // else there was no step 0 so continue to step 1. 149 } 150 151 if (step == 1) { 152 // Header should look like : 153 // Proxy-Authenticate: NTLM still_some_more_stuff 154 String challengeResponse = getNTLMHeader(response); 155 156 if (challengeResponse == null || challengeResponse.length() < 5) { 157 throw new ProxyAuthException("Unexpected error while reading server challenge !"); 158 } 159 160 try { 161 challengePacket = Base64.decodeBase64(challengeResponse.substring(5).getBytes( 162 proxyIoSession.getCharsetName())); 163 } catch (IOException e) { 164 throw new ProxyAuthException("Unable to decode the base64 encoded NTLM challenge", e); 165 } 166 step = 2; 167 } else { 168 throw new ProxyAuthException("Received unexpected response code (" + response.getStatusLine() + ")."); 169 } 170 } 171}