001: /* jcifs smb client library in Java
002: * Copyright (C) 2002 "Michael B. Allen" <jcifs at samba dot org>
003: * "Eric Glass" <jcifs at samba dot org>
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package com.knowgate.jcifs.http;
021:
022: import java.io.InputStream;
023: import java.io.IOException;
024: import java.io.OutputStream;
025:
026: import java.net.Authenticator;
027: import java.net.HttpURLConnection;
028: import java.net.PasswordAuthentication;
029: import java.net.ProtocolException;
030: import java.net.URL;
031: import java.net.URLDecoder;
032:
033: import java.security.Permission;
034:
035: import java.util.ArrayList;
036: import java.util.Collections;
037: import java.util.HashMap;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.Map;
041:
042: import com.knowgate.jcifs.Config;
043:
044: import com.knowgate.jcifs.ntlmssp.NtlmFlags;
045: import com.knowgate.jcifs.ntlmssp.NtlmMessage;
046: import com.knowgate.jcifs.ntlmssp.Type1Message;
047: import com.knowgate.jcifs.ntlmssp.Type2Message;
048: import com.knowgate.jcifs.ntlmssp.Type3Message;
049:
050: import com.knowgate.misc.Base64Decoder;
051: import com.knowgate.misc.Base64Encoder;
052:
053: /**
054: * Wraps an <code>HttpURLConnection</code> to provide NTLM authentication
055: * services.
056: *
057: * Please read <a href="../../../httpclient.html">Using jCIFS NTLM Authentication for HTTP Connections</a>.
058: * @version 0.9.1
059: */
060: public class NtlmHttpURLConnection extends HttpURLConnection {
061:
062: private static final int MAX_REDIRECTS = Integer.parseInt(System
063: .getProperty("http.maxRedirects", "20"));
064:
065: private static final int LM_COMPATIBILITY = Config.getInt(
066: "jcifs.smb.lmCompatibility", 0);
067:
068: private static final String DEFAULT_DOMAIN;
069:
070: private HttpURLConnection connection;
071:
072: private Map requestProperties;
073:
074: private Map headerFields;
075:
076: private String authProperty;
077:
078: private String method;
079:
080: static {
081: String domain = System.getProperty("http.auth.ntlm.domain");
082: if (domain == null)
083: domain = Type3Message.getDefaultDomain();
084: DEFAULT_DOMAIN = domain;
085: }
086:
087: public NtlmHttpURLConnection(HttpURLConnection connection) {
088: super (connection.getURL());
089: this .connection = connection;
090: requestProperties = new HashMap();
091: }
092:
093: public void connect() throws IOException {
094: if (connected)
095: return;
096: doConnect();
097: connected = true;
098: }
099:
100: public URL getURL() {
101: return connection.getURL();
102: }
103:
104: public int getContentLength() {
105: try {
106: connect();
107: } catch (IOException ex) {
108: }
109: return connection.getContentLength();
110: }
111:
112: public String getContentType() {
113: try {
114: connect();
115: } catch (IOException ex) {
116: }
117: return connection.getContentType();
118: }
119:
120: public String getContentEncoding() {
121: try {
122: connect();
123: } catch (IOException ex) {
124: }
125: return connection.getContentEncoding();
126: }
127:
128: public long getExpiration() {
129: try {
130: connect();
131: } catch (IOException ex) {
132: }
133: return connection.getExpiration();
134: }
135:
136: public long getDate() {
137: try {
138: connect();
139: } catch (IOException ex) {
140: }
141: return connection.getDate();
142: }
143:
144: public long getLastModified() {
145: try {
146: connect();
147: } catch (IOException ex) {
148: }
149: return connection.getLastModified();
150: }
151:
152: public String getHeaderField(String header) {
153: try {
154: connect();
155: } catch (IOException ex) {
156: }
157: return connection.getHeaderField(header);
158: }
159:
160: private synchronized Map getHeaderFields0() {
161: if (headerFields != null)
162: return headerFields;
163: Map map = new HashMap();
164: String key = connection.getHeaderFieldKey(0);
165: String value = connection.getHeaderField(0);
166: for (int i = 1; key != null || value != null; i++) {
167: List values = (List) map.get(key);
168: if (values == null) {
169: values = new ArrayList();
170: map.put(key, values);
171: }
172: values.add(value);
173: key = connection.getHeaderFieldKey(i);
174: value = connection.getHeaderField(i);
175: }
176: Iterator entries = map.entrySet().iterator();
177: while (entries.hasNext()) {
178: Map.Entry entry = (Map.Entry) entries.next();
179: entry.setValue(Collections.unmodifiableList((List) entry
180: .getValue()));
181: }
182: return (headerFields = Collections.unmodifiableMap(map));
183: }
184:
185: public Map getHeaderFields() {
186: synchronized (this ) {
187: if (headerFields != null)
188: return headerFields;
189: }
190: try {
191: connect();
192: } catch (IOException ex) {
193: }
194: return getHeaderFields0();
195: }
196:
197: public int getHeaderFieldInt(String header, int def) {
198: try {
199: connect();
200: } catch (IOException ex) {
201: }
202: return connection.getHeaderFieldInt(header, def);
203: }
204:
205: public long getHeaderFieldDate(String header, long def) {
206: try {
207: connect();
208: } catch (IOException ex) {
209: }
210: return connection.getHeaderFieldDate(header, def);
211: }
212:
213: public String getHeaderFieldKey(int index) {
214: try {
215: connect();
216: } catch (IOException ex) {
217: }
218: return connection.getHeaderFieldKey(index);
219: }
220:
221: public String getHeaderField(int index) {
222: try {
223: connect();
224: } catch (IOException ex) {
225: }
226: return connection.getHeaderField(index);
227: }
228:
229: public Object getContent() throws IOException {
230: try {
231: connect();
232: } catch (IOException ex) {
233: }
234: return connection.getContent();
235: }
236:
237: public Object getContent(Class[] classes) throws IOException {
238: try {
239: connect();
240: } catch (IOException ex) {
241: }
242: return connection.getContent(classes);
243: }
244:
245: public Permission getPermission() throws IOException {
246: return connection.getPermission();
247: }
248:
249: public InputStream getInputStream() throws IOException {
250: try {
251: connect();
252: } catch (IOException ex) {
253: }
254: return connection.getInputStream();
255: }
256:
257: public OutputStream getOutputStream() throws IOException {
258: try {
259: connect();
260: } catch (IOException ex) {
261: }
262: return connection.getOutputStream();
263: }
264:
265: public String toString() {
266: return connection.toString();
267: }
268:
269: public void setDoInput(boolean doInput) {
270: connection.setDoInput(doInput);
271: this .doInput = doInput;
272: }
273:
274: public boolean getDoInput() {
275: return connection.getDoInput();
276: }
277:
278: public void setDoOutput(boolean doOutput) {
279: connection.setDoOutput(doOutput);
280: this .doOutput = doOutput;
281: }
282:
283: public boolean getDoOutput() {
284: return connection.getDoOutput();
285: }
286:
287: public void setAllowUserInteraction(boolean allowUserInteraction) {
288: connection.setAllowUserInteraction(allowUserInteraction);
289: this .allowUserInteraction = allowUserInteraction;
290: }
291:
292: public boolean getAllowUserInteraction() {
293: return connection.getAllowUserInteraction();
294: }
295:
296: public void setUseCaches(boolean useCaches) {
297: connection.setUseCaches(useCaches);
298: this .useCaches = useCaches;
299: }
300:
301: public boolean getUseCaches() {
302: return connection.getUseCaches();
303: }
304:
305: public void setIfModifiedSince(long ifModifiedSince) {
306: connection.setIfModifiedSince(ifModifiedSince);
307: this .ifModifiedSince = ifModifiedSince;
308: }
309:
310: public long getIfModifiedSince() {
311: return connection.getIfModifiedSince();
312: }
313:
314: public boolean getDefaultUseCaches() {
315: return connection.getDefaultUseCaches();
316: }
317:
318: public void setDefaultUseCaches(boolean defaultUseCaches) {
319: connection.setDefaultUseCaches(defaultUseCaches);
320: }
321:
322: public void setRequestProperty(String key, String value) {
323: if (key == null)
324: throw new NullPointerException();
325: List values = new ArrayList();
326: values.add(value);
327: boolean found = false;
328: synchronized (requestProperties) {
329: Iterator entries = requestProperties.entrySet().iterator();
330: while (entries.hasNext()) {
331: Map.Entry entry = (Map.Entry) entries.next();
332: if (key.equalsIgnoreCase((String) entry.getKey())) {
333: entry.setValue(value);
334: found = true;
335: break;
336: }
337: }
338: if (!found)
339: requestProperties.put(key, values);
340: }
341: connection.setRequestProperty(key, value);
342: }
343:
344: public void addRequestProperty(String key, String value) {
345: if (key == null)
346: throw new NullPointerException();
347: List values = null;
348: synchronized (requestProperties) {
349: Iterator entries = requestProperties.entrySet().iterator();
350: while (entries.hasNext()) {
351: Map.Entry entry = (Map.Entry) entries.next();
352: if (key.equalsIgnoreCase((String) entry.getKey())) {
353: values = (List) entry.getValue();
354: values.add(value);
355: break;
356: }
357: }
358: if (values == null) {
359: values = new ArrayList();
360: values.add(value);
361: requestProperties.put(key, values);
362: }
363: }
364: // 1.3-compatible.
365: StringBuffer buffer = new StringBuffer();
366: Iterator propertyValues = values.iterator();
367: while (propertyValues.hasNext()) {
368: buffer.append(propertyValues.next());
369: if (propertyValues.hasNext()) {
370: buffer.append(", ");
371: }
372: }
373: connection.setRequestProperty(key, buffer.toString());
374: }
375:
376: public String getRequestProperty(String key) {
377: return connection.getRequestProperty(key);
378: }
379:
380: public Map getRequestProperties() {
381: Map map = new HashMap();
382: synchronized (requestProperties) {
383: Iterator entries = requestProperties.entrySet().iterator();
384: while (entries.hasNext()) {
385: Map.Entry entry = (Map.Entry) entries.next();
386: map.put(entry.getKey(), Collections
387: .unmodifiableList((List) entry.getValue()));
388: }
389: }
390: return Collections.unmodifiableMap(map);
391: }
392:
393: public void setInstanceFollowRedirects(
394: boolean instanceFollowRedirects) {
395: connection.setInstanceFollowRedirects(instanceFollowRedirects);
396: }
397:
398: public boolean getInstanceFollowRedirects() {
399: return connection.getInstanceFollowRedirects();
400: }
401:
402: public void setRequestMethod(String requestMethod)
403: throws ProtocolException {
404: connection.setRequestMethod(requestMethod);
405: }
406:
407: public String getRequestMethod() {
408: return connection.getRequestMethod();
409: }
410:
411: public int getResponseCode() throws IOException {
412: return connection.getResponseCode();
413: }
414:
415: public String getResponseMessage() throws IOException {
416: return connection.getResponseMessage();
417: }
418:
419: public void disconnect() {
420: connection.disconnect();
421: connected = false;
422: }
423:
424: public boolean usingProxy() {
425: return connection.usingProxy();
426: }
427:
428: public InputStream getErrorStream() {
429: return connection.getErrorStream();
430: }
431:
432: private int parseResponseCode() throws IOException {
433: try {
434: String response = connection.getHeaderField(0);
435: int index = response.indexOf(' ');
436: while (response.charAt(index) == ' ')
437: index++;
438: return Integer.parseInt(response
439: .substring(index, index + 3));
440: } catch (Exception ex) {
441: throw new IOException(ex.getMessage());
442: }
443: }
444:
445: private synchronized void doConnect() throws IOException {
446: connection.connect();
447: int response = parseResponseCode();
448: if (response != HTTP_UNAUTHORIZED
449: && response != HTTP_PROXY_AUTH) {
450: return;
451: }
452: Type1Message type1 = (Type1Message) attemptNegotiation(response);
453: if (type1 == null)
454: return; // no NTLM
455: int attempt = 0;
456: while (attempt < MAX_REDIRECTS) {
457: connection.setRequestProperty(authProperty, method + ' '
458: + Base64Encoder.encode(type1.toByteArray()));
459: connection.connect(); // send type 1
460: response = parseResponseCode();
461: if (response != HTTP_UNAUTHORIZED
462: && response != HTTP_PROXY_AUTH) {
463: return;
464: }
465: Type3Message type3 = (Type3Message) attemptNegotiation(response);
466: if (type3 == null)
467: return;
468: connection.setRequestProperty(authProperty, method + ' '
469: + Base64Encoder.encode(type3.toByteArray()));
470: connection.connect(); // send type 3
471: response = parseResponseCode();
472: if (response != HTTP_UNAUTHORIZED
473: && response != HTTP_PROXY_AUTH) {
474: return;
475: }
476: attempt++;
477: if (attempt < MAX_REDIRECTS)
478: reconnect();
479: }
480: throw new IOException(
481: "Unable to negotiate NTLM authentication.");
482: }
483:
484: private NtlmMessage attemptNegotiation(int response)
485: throws IOException {
486: authProperty = null;
487: method = null;
488: InputStream errorStream = connection.getErrorStream();
489: if (errorStream != null && errorStream.available() != 0) {
490: int count;
491: byte[] buf = new byte[1024];
492: while ((count = errorStream.read(buf, 0, 1024)) != -1)
493: ;
494: }
495: String authHeader;
496: if (response == HTTP_UNAUTHORIZED) {
497: authHeader = "WWW-Authenticate";
498: authProperty = "Authorization";
499: } else {
500: authHeader = "Proxy-Authenticate";
501: authProperty = "Proxy-Authorization";
502: }
503: String authorization = null;
504: List methods = (List) getHeaderFields0().get(authHeader);
505: if (methods == null)
506: return null;
507: Iterator iterator = methods.iterator();
508: while (iterator.hasNext()) {
509: String authMethod = (String) iterator.next();
510: if (authMethod.startsWith("NTLM")) {
511: if (authMethod.length() == 4) {
512: method = "NTLM";
513: break;
514: }
515: if (authMethod.indexOf(' ') != 4)
516: continue;
517: method = "NTLM";
518: authorization = authMethod.substring(5).trim();
519: break;
520: } else if (authMethod.startsWith("Negotiate")) {
521: if (authMethod.length() == 9) {
522: method = "Negotiate";
523: break;
524: }
525: if (authMethod.indexOf(' ') != 9)
526: continue;
527: method = "Negotiate";
528: authorization = authMethod.substring(10).trim();
529: break;
530: }
531: }
532: if (method == null)
533: return null;
534: NtlmMessage message = (authorization != null) ? new Type2Message(
535: Base64Decoder.decodeToBytes(authorization))
536: : null;
537: reconnect();
538: if (message == null) {
539: message = new Type1Message();
540: if (LM_COMPATIBILITY > 2) {
541: message.setFlag(NtlmFlags.NTLMSSP_REQUEST_TARGET, true);
542: }
543: } else {
544: String domain = DEFAULT_DOMAIN;
545: String user = Type3Message.getDefaultUser();
546: String password = Type3Message.getDefaultPassword();
547: String userInfo = url.getUserInfo();
548: if (userInfo != null) {
549: userInfo = URLDecoder.decode(userInfo);
550: int index = userInfo.indexOf(':');
551: user = (index != -1) ? userInfo.substring(0, index)
552: : userInfo;
553: if (index != -1)
554: password = userInfo.substring(index + 1);
555: index = user.indexOf('\\');
556: if (index == -1)
557: index = user.indexOf('/');
558: domain = (index != -1) ? user.substring(0, index)
559: : domain;
560: user = (index != -1) ? user.substring(index + 1) : user;
561: }
562: if (user == null) {
563: try {
564: URL url = getURL();
565: String protocol = url.getProtocol();
566: int port = url.getPort();
567: if (port == -1) {
568: port = "https".equalsIgnoreCase(protocol) ? 443
569: : 80;
570: }
571: PasswordAuthentication auth = Authenticator
572: .requestPasswordAuthentication(null, port,
573: protocol, "", method);
574: if (auth != null) {
575: user = auth.getUserName();
576: password = new String(auth.getPassword());
577: }
578: } catch (Exception ex) {
579: }
580: }
581: Type2Message type2 = (Type2Message) message;
582: message = new Type3Message(type2, password, domain, user,
583: Type3Message.getDefaultWorkstation());
584: }
585: return message;
586: }
587:
588: private void reconnect() throws IOException {
589: connection = (HttpURLConnection) connection.getURL()
590: .openConnection();
591: headerFields = null;
592: synchronized (requestProperties) {
593: Iterator properties = requestProperties.entrySet()
594: .iterator();
595: while (properties.hasNext()) {
596: Map.Entry property = (Map.Entry) properties.next();
597: String key = (String) property.getKey();
598: StringBuffer value = new StringBuffer();
599: Iterator values = ((List) property.getValue())
600: .iterator();
601: while (values.hasNext()) {
602: value.append(values.next());
603: if (values.hasNext())
604: value.append(", ");
605: }
606: connection.setRequestProperty(key, value.toString());
607: }
608: }
609: connection.setAllowUserInteraction(allowUserInteraction);
610: connection.setDoInput(doInput);
611: connection.setDoOutput(doOutput);
612: connection.setIfModifiedSince(ifModifiedSince);
613: connection.setUseCaches(useCaches);
614: }
615: }
|