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 jcifs.http;
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.InputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026:
027: import java.net.Authenticator;
028: import java.net.HttpURLConnection;
029: import java.net.PasswordAuthentication;
030: import java.net.ProtocolException;
031: import java.net.URL;
032: import java.net.URLDecoder;
033:
034: import java.security.Permission;
035:
036: import java.util.ArrayList;
037: import java.util.Collections;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042:
043: import jcifs.Config;
044:
045: import jcifs.ntlmssp.NtlmFlags;
046: import jcifs.ntlmssp.NtlmMessage;
047: import jcifs.ntlmssp.Type1Message;
048: import jcifs.ntlmssp.Type2Message;
049: import jcifs.ntlmssp.Type3Message;
050:
051: import jcifs.util.Base64;
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: */
059: public class NtlmHttpURLConnection extends HttpURLConnection {
060:
061: private static final int MAX_REDIRECTS = Integer.parseInt(System
062: .getProperty("http.maxRedirects", "20"));
063:
064: private static final int LM_COMPATIBILITY = Config.getInt(
065: "jcifs.smb.lmCompatibility", 0);
066:
067: private static final String DEFAULT_DOMAIN;
068:
069: private HttpURLConnection connection;
070:
071: private Map requestProperties;
072:
073: private Map headerFields;
074:
075: private ByteArrayOutputStream cachedOutput;
076:
077: private String authProperty;
078:
079: private String authMethod;
080:
081: private boolean handshakeComplete;
082:
083: static {
084: String domain = System.getProperty("http.auth.ntlm.domain");
085: if (domain == null)
086: domain = Type3Message.getDefaultDomain();
087: DEFAULT_DOMAIN = domain;
088: }
089:
090: public NtlmHttpURLConnection(HttpURLConnection connection) {
091: super (connection.getURL());
092: this .connection = connection;
093: requestProperties = new HashMap();
094: }
095:
096: public void connect() throws IOException {
097: if (connected)
098: return;
099: connection.connect();
100: connected = true;
101: }
102:
103: private void handshake() throws IOException {
104: if (handshakeComplete)
105: return;
106: doHandshake();
107: handshakeComplete = true;
108: }
109:
110: public URL getURL() {
111: return connection.getURL();
112: }
113:
114: public int getContentLength() {
115: try {
116: handshake();
117: } catch (IOException ex) {
118: }
119: return connection.getContentLength();
120: }
121:
122: public String getContentType() {
123: try {
124: handshake();
125: } catch (IOException ex) {
126: }
127: return connection.getContentType();
128: }
129:
130: public String getContentEncoding() {
131: try {
132: handshake();
133: } catch (IOException ex) {
134: }
135: return connection.getContentEncoding();
136: }
137:
138: public long getExpiration() {
139: try {
140: handshake();
141: } catch (IOException ex) {
142: }
143: return connection.getExpiration();
144: }
145:
146: public long getDate() {
147: try {
148: handshake();
149: } catch (IOException ex) {
150: }
151: return connection.getDate();
152: }
153:
154: public long getLastModified() {
155: try {
156: handshake();
157: } catch (IOException ex) {
158: }
159: return connection.getLastModified();
160: }
161:
162: public String getHeaderField(String header) {
163: try {
164: handshake();
165: } catch (IOException ex) {
166: }
167: return connection.getHeaderField(header);
168: }
169:
170: private Map getHeaderFields0() {
171: if (headerFields != null)
172: return headerFields;
173: Map map = new HashMap();
174: String key = connection.getHeaderFieldKey(0);
175: String value = connection.getHeaderField(0);
176: for (int i = 1; key != null || value != null; i++) {
177: List values = (List) map.get(key);
178: if (values == null) {
179: values = new ArrayList();
180: map.put(key, values);
181: }
182: values.add(value);
183: key = connection.getHeaderFieldKey(i);
184: value = connection.getHeaderField(i);
185: }
186: Iterator entries = map.entrySet().iterator();
187: while (entries.hasNext()) {
188: Map.Entry entry = (Map.Entry) entries.next();
189: entry.setValue(Collections.unmodifiableList((List) entry
190: .getValue()));
191: }
192: return (headerFields = Collections.unmodifiableMap(map));
193: }
194:
195: public Map getHeaderFields() {
196: if (headerFields != null)
197: return headerFields;
198: try {
199: handshake();
200: } catch (IOException ex) {
201: }
202: return getHeaderFields0();
203: }
204:
205: public int getHeaderFieldInt(String header, int def) {
206: try {
207: handshake();
208: } catch (IOException ex) {
209: }
210: return connection.getHeaderFieldInt(header, def);
211: }
212:
213: public long getHeaderFieldDate(String header, long def) {
214: try {
215: handshake();
216: } catch (IOException ex) {
217: }
218: return connection.getHeaderFieldDate(header, def);
219: }
220:
221: public String getHeaderFieldKey(int index) {
222: try {
223: handshake();
224: } catch (IOException ex) {
225: }
226: return connection.getHeaderFieldKey(index);
227: }
228:
229: public String getHeaderField(int index) {
230: try {
231: handshake();
232: } catch (IOException ex) {
233: }
234: return connection.getHeaderField(index);
235: }
236:
237: public Object getContent() throws IOException {
238: try {
239: handshake();
240: } catch (IOException ex) {
241: }
242: return connection.getContent();
243: }
244:
245: public Object getContent(Class[] classes) throws IOException {
246: try {
247: handshake();
248: } catch (IOException ex) {
249: }
250: return connection.getContent(classes);
251: }
252:
253: public Permission getPermission() throws IOException {
254: return connection.getPermission();
255: }
256:
257: public InputStream getInputStream() throws IOException {
258: try {
259: handshake();
260: } catch (IOException ex) {
261: }
262: return connection.getInputStream();
263: }
264:
265: public OutputStream getOutputStream() throws IOException {
266: try {
267: connect();
268: } catch (IOException ex) {
269: }
270: OutputStream output = connection.getOutputStream();
271: cachedOutput = new ByteArrayOutputStream();
272: return new CacheStream(output, cachedOutput);
273: }
274:
275: public String toString() {
276: return connection.toString();
277: }
278:
279: public void setDoInput(boolean doInput) {
280: connection.setDoInput(doInput);
281: this .doInput = doInput;
282: }
283:
284: public boolean getDoInput() {
285: return connection.getDoInput();
286: }
287:
288: public void setDoOutput(boolean doOutput) {
289: connection.setDoOutput(doOutput);
290: this .doOutput = doOutput;
291: }
292:
293: public boolean getDoOutput() {
294: return connection.getDoOutput();
295: }
296:
297: public void setAllowUserInteraction(boolean allowUserInteraction) {
298: connection.setAllowUserInteraction(allowUserInteraction);
299: this .allowUserInteraction = allowUserInteraction;
300: }
301:
302: public boolean getAllowUserInteraction() {
303: return connection.getAllowUserInteraction();
304: }
305:
306: public void setUseCaches(boolean useCaches) {
307: connection.setUseCaches(useCaches);
308: this .useCaches = useCaches;
309: }
310:
311: public boolean getUseCaches() {
312: return connection.getUseCaches();
313: }
314:
315: public void setIfModifiedSince(long ifModifiedSince) {
316: connection.setIfModifiedSince(ifModifiedSince);
317: this .ifModifiedSince = ifModifiedSince;
318: }
319:
320: public long getIfModifiedSince() {
321: return connection.getIfModifiedSince();
322: }
323:
324: public boolean getDefaultUseCaches() {
325: return connection.getDefaultUseCaches();
326: }
327:
328: public void setDefaultUseCaches(boolean defaultUseCaches) {
329: connection.setDefaultUseCaches(defaultUseCaches);
330: }
331:
332: public void setRequestProperty(String key, String value) {
333: if (key == null)
334: throw new NullPointerException();
335: List values = new ArrayList();
336: values.add(value);
337: boolean found = false;
338: Iterator entries = requestProperties.entrySet().iterator();
339: while (entries.hasNext()) {
340: Map.Entry entry = (Map.Entry) entries.next();
341: if (key.equalsIgnoreCase((String) entry.getKey())) {
342: entry.setValue(value);
343: found = true;
344: break;
345: }
346: }
347: if (!found)
348: requestProperties.put(key, values);
349: connection.setRequestProperty(key, value);
350: }
351:
352: public void addRequestProperty(String key, String value) {
353: if (key == null)
354: throw new NullPointerException();
355: List values = null;
356: Iterator entries = requestProperties.entrySet().iterator();
357: while (entries.hasNext()) {
358: Map.Entry entry = (Map.Entry) entries.next();
359: if (key.equalsIgnoreCase((String) entry.getKey())) {
360: values = (List) entry.getValue();
361: values.add(value);
362: break;
363: }
364: }
365: if (values == null) {
366: values = new ArrayList();
367: values.add(value);
368: requestProperties.put(key, values);
369: }
370: // 1.3-compatible.
371: StringBuffer buffer = new StringBuffer();
372: Iterator propertyValues = values.iterator();
373: while (propertyValues.hasNext()) {
374: buffer.append(propertyValues.next());
375: if (propertyValues.hasNext()) {
376: buffer.append(", ");
377: }
378: }
379: connection.setRequestProperty(key, buffer.toString());
380: }
381:
382: public String getRequestProperty(String key) {
383: return connection.getRequestProperty(key);
384: }
385:
386: public Map getRequestProperties() {
387: Map map = new HashMap();
388: Iterator entries = requestProperties.entrySet().iterator();
389: while (entries.hasNext()) {
390: Map.Entry entry = (Map.Entry) entries.next();
391: map.put(entry.getKey(), Collections
392: .unmodifiableList((List) entry.getValue()));
393: }
394: return Collections.unmodifiableMap(map);
395: }
396:
397: public void setInstanceFollowRedirects(
398: boolean instanceFollowRedirects) {
399: connection.setInstanceFollowRedirects(instanceFollowRedirects);
400: }
401:
402: public boolean getInstanceFollowRedirects() {
403: return connection.getInstanceFollowRedirects();
404: }
405:
406: public void setRequestMethod(String requestMethod)
407: throws ProtocolException {
408: connection.setRequestMethod(requestMethod);
409: this .method = requestMethod;
410: }
411:
412: public String getRequestMethod() {
413: return connection.getRequestMethod();
414: }
415:
416: public int getResponseCode() throws IOException {
417: try {
418: handshake();
419: } catch (IOException ex) {
420: }
421: return connection.getResponseCode();
422: }
423:
424: public String getResponseMessage() throws IOException {
425: try {
426: handshake();
427: } catch (IOException ex) {
428: }
429: return connection.getResponseMessage();
430: }
431:
432: public void disconnect() {
433: connection.disconnect();
434: handshakeComplete = false;
435: connected = false;
436: }
437:
438: public boolean usingProxy() {
439: return connection.usingProxy();
440: }
441:
442: public InputStream getErrorStream() {
443: try {
444: handshake();
445: } catch (IOException ex) {
446: }
447: return connection.getErrorStream();
448: }
449:
450: private int parseResponseCode() throws IOException {
451: try {
452: String response = connection.getHeaderField(0);
453: int index = response.indexOf(' ');
454: while (response.charAt(index) == ' ')
455: index++;
456: return Integer.parseInt(response
457: .substring(index, index + 3));
458: } catch (Exception ex) {
459: throw new IOException(ex.getMessage());
460: }
461: }
462:
463: private void doHandshake() throws IOException {
464: connect();
465: try {
466: int response = parseResponseCode();
467: if (response != HTTP_UNAUTHORIZED
468: && response != HTTP_PROXY_AUTH) {
469: return;
470: }
471: Type1Message type1 = (Type1Message) attemptNegotiation(response);
472: if (type1 == null)
473: return; // no NTLM
474: int attempt = 0;
475: while (attempt < MAX_REDIRECTS) {
476: connection.setRequestProperty(authProperty, authMethod
477: + ' ' + Base64.encode(type1.toByteArray()));
478: connection.connect(); // send type 1
479: response = parseResponseCode();
480: if (response != HTTP_UNAUTHORIZED
481: && response != HTTP_PROXY_AUTH) {
482: return;
483: }
484: Type3Message type3 = (Type3Message) attemptNegotiation(response);
485: if (type3 == null)
486: return;
487: connection.setRequestProperty(authProperty, authMethod
488: + ' ' + Base64.encode(type3.toByteArray()));
489: connection.connect(); // send type 3
490: if (cachedOutput != null && doOutput) {
491: OutputStream output = connection.getOutputStream();
492: cachedOutput.writeTo(output);
493: output.flush();
494: }
495: response = parseResponseCode();
496: if (response != HTTP_UNAUTHORIZED
497: && response != HTTP_PROXY_AUTH) {
498: return;
499: }
500: attempt++;
501: if (allowUserInteraction && attempt < MAX_REDIRECTS) {
502: reconnect();
503: } else {
504: break;
505: }
506: }
507: throw new IOException(
508: "Unable to negotiate NTLM authentication.");
509: } finally {
510: cachedOutput = null;
511: }
512: }
513:
514: private NtlmMessage attemptNegotiation(int response)
515: throws IOException {
516: authProperty = null;
517: authMethod = null;
518: InputStream errorStream = connection.getErrorStream();
519: if (errorStream != null && errorStream.available() != 0) {
520: int count;
521: byte[] buf = new byte[1024];
522: while ((count = errorStream.read(buf, 0, 1024)) != -1)
523: ;
524: }
525: String authHeader;
526: if (response == HTTP_UNAUTHORIZED) {
527: authHeader = "WWW-Authenticate";
528: authProperty = "Authorization";
529: } else {
530: authHeader = "Proxy-Authenticate";
531: authProperty = "Proxy-Authorization";
532: }
533: String authorization = null;
534: List methods = (List) getHeaderFields0().get(authHeader);
535: if (methods == null)
536: return null;
537: Iterator iterator = methods.iterator();
538: while (iterator.hasNext()) {
539: String currentAuthMethod = (String) iterator.next();
540: if (currentAuthMethod.startsWith("NTLM")) {
541: if (currentAuthMethod.length() == 4) {
542: authMethod = "NTLM";
543: break;
544: }
545: if (currentAuthMethod.indexOf(' ') != 4)
546: continue;
547: authMethod = "NTLM";
548: authorization = currentAuthMethod.substring(5).trim();
549: break;
550: } else if (currentAuthMethod.startsWith("Negotiate")) {
551: if (currentAuthMethod.length() == 9) {
552: authMethod = "Negotiate";
553: break;
554: }
555: if (currentAuthMethod.indexOf(' ') != 9)
556: continue;
557: authMethod = "Negotiate";
558: authorization = currentAuthMethod.substring(10).trim();
559: break;
560: }
561: }
562: if (authMethod == null)
563: return null;
564: NtlmMessage message = (authorization != null) ? new Type2Message(
565: Base64.decode(authorization))
566: : null;
567: reconnect();
568: if (message == null) {
569: message = new Type1Message();
570: if (LM_COMPATIBILITY > 2) {
571: message.setFlag(NtlmFlags.NTLMSSP_REQUEST_TARGET, true);
572: }
573: } else {
574: String domain = DEFAULT_DOMAIN;
575: String user = Type3Message.getDefaultUser();
576: String password = Type3Message.getDefaultPassword();
577: String userInfo = url.getUserInfo();
578: if (userInfo != null) {
579: userInfo = URLDecoder.decode(userInfo);
580: int index = userInfo.indexOf(':');
581: user = (index != -1) ? userInfo.substring(0, index)
582: : userInfo;
583: if (index != -1)
584: password = userInfo.substring(index + 1);
585: index = user.indexOf('\\');
586: if (index == -1)
587: index = user.indexOf('/');
588: domain = (index != -1) ? user.substring(0, index)
589: : domain;
590: user = (index != -1) ? user.substring(index + 1) : user;
591: }
592: if (user == null) {
593: if (!allowUserInteraction)
594: return null;
595: try {
596: URL url = getURL();
597: String protocol = url.getProtocol();
598: int port = url.getPort();
599: if (port == -1) {
600: port = "https".equalsIgnoreCase(protocol) ? 443
601: : 80;
602: }
603: PasswordAuthentication auth = Authenticator
604: .requestPasswordAuthentication(null, port,
605: protocol, "", authMethod);
606: if (auth == null)
607: return null;
608: user = auth.getUserName();
609: password = new String(auth.getPassword());
610: } catch (Exception ex) {
611: }
612: }
613: Type2Message type2 = (Type2Message) message;
614: message = new Type3Message(type2, password, domain, user,
615: Type3Message.getDefaultWorkstation());
616: }
617: return message;
618: }
619:
620: private void reconnect() throws IOException {
621: connection = (HttpURLConnection) connection.getURL()
622: .openConnection();
623: connection.setRequestMethod(method);
624: headerFields = null;
625: Iterator properties = requestProperties.entrySet().iterator();
626: while (properties.hasNext()) {
627: Map.Entry property = (Map.Entry) properties.next();
628: String key = (String) property.getKey();
629: StringBuffer value = new StringBuffer();
630: Iterator values = ((List) property.getValue()).iterator();
631: while (values.hasNext()) {
632: value.append(values.next());
633: if (values.hasNext())
634: value.append(", ");
635: }
636: connection.setRequestProperty(key, value.toString());
637: }
638: connection.setAllowUserInteraction(allowUserInteraction);
639: connection.setDoInput(doInput);
640: connection.setDoOutput(doOutput);
641: connection.setIfModifiedSince(ifModifiedSince);
642: connection.setUseCaches(useCaches);
643: }
644:
645: private static class CacheStream extends OutputStream {
646:
647: private final OutputStream stream;
648:
649: private final OutputStream collector;
650:
651: public CacheStream(OutputStream stream, OutputStream collector) {
652: this .stream = stream;
653: this .collector = collector;
654: }
655:
656: public void close() throws IOException {
657: stream.close();
658: collector.close();
659: }
660:
661: public void flush() throws IOException {
662: stream.flush();
663: collector.flush();
664: }
665:
666: public void write(byte[] b) throws IOException {
667: stream.write(b);
668: collector.write(b);
669: }
670:
671: public void write(byte[] b, int off, int len)
672: throws IOException {
673: stream.write(b, off, len);
674: collector.write(b, off, len);
675: }
676:
677: public void write(int b) throws IOException {
678: stream.write(b);
679: collector.write(b);
680: }
681:
682: }
683:
684: }
|