001: /* jcifs smb client library in Java
002: * Copyright (C) 2002 "Michael B. Allen" <jcifs at samba dot org>
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package com.knowgate.jcifs.http;
020:
021: import java.util.Enumeration;
022: import java.util.LinkedList;
023: import java.util.ListIterator;
024: import java.util.Date;
025: import java.util.GregorianCalendar;
026:
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.PrintWriter;
030:
031: import javax.servlet.*;
032: import javax.servlet.http.*;
033:
034: import java.text.SimpleDateFormat;
035: import java.net.UnknownHostException;
036:
037: import com.knowgate.jcifs.Config;
038: import com.knowgate.jcifs.UniAddress;
039: import com.knowgate.jcifs.ntlmssp.*;
040: import com.knowgate.jcifs.smb.SmbFile;
041: import com.knowgate.jcifs.smb.SmbFileInputStream;
042: import com.knowgate.jcifs.smb.NtlmPasswordAuthentication;
043: import com.knowgate.jcifs.smb.SmbException;
044: import com.knowgate.jcifs.smb.SmbAuthException;
045: import com.knowgate.jcifs.smb.SmbSession;
046: import com.knowgate.jcifs.smb.DfsReferral;
047: import com.knowgate.jcifs.netbios.NbtAddress;
048: import com.knowgate.jcifs.util.MimeMap;
049:
050: import com.knowgate.misc.Base64Decoder;
051:
052: /**
053: * This servlet may be used to "browse" the entire hierarchy of resources
054: * on an SMB network like one might with Network Neighborhood or Windows
055: * Explorer. The users credentials with be negotiated using NTLM SSP if
056: * the client is Microsoft Internet Explorer.
057: *
058: * Please read <a href="../../../ntlmhttpauth.html">jCIFS NTLM HTTP Authentication and the Network Explorer Servlet</a>.
059: * @version 0.9.1
060: */
061:
062: public class NetworkExplorer extends HttpServlet {
063:
064: private MimeMap mimeMap;
065: private String style;
066: private NtlmSsp ntlmSsp;
067: private boolean credentialsSupplied;
068: private boolean enableBasic;
069: private boolean insecureBasic;
070: private String realm, defaultDomain;
071:
072: public void init() throws ServletException {
073: InputStream is;
074: StringBuffer sb = new StringBuffer();
075: byte[] buf = new byte[1024];
076: int n;
077: String name;
078:
079: Config.setProperty("jcifs.smb.client.soTimeout", "600000");
080: Config.setProperty("jcifs.smb.client.attrExpirationPeriod",
081: "300000");
082:
083: Enumeration e = getInitParameterNames();
084: while (e.hasMoreElements()) {
085: name = (String) e.nextElement();
086: if (name.startsWith("jcifs.")) {
087: Config.setProperty(name, getInitParameter(name));
088: }
089: }
090:
091: if (Config.getProperty("jcifs.smb.client.username") == null) {
092: ntlmSsp = new NtlmSsp();
093: } else {
094: credentialsSupplied = true;
095: }
096:
097: try {
098: mimeMap = new MimeMap();
099: is = getClass().getClassLoader().getResourceAsStream(
100: "jcifs/http/ne.css");
101: while ((n = is.read(buf)) != -1) {
102: sb.append(new String(buf, 0, n, "ISO8859_1"));
103: }
104: style = sb.toString();
105: } catch (IOException ioe) {
106: throw new ServletException(ioe.getMessage());
107: }
108:
109: enableBasic = Config
110: .getBoolean("jcifs.http.enableBasic", false);
111: insecureBasic = Config.getBoolean("jcifs.http.insecureBasic",
112: false);
113: realm = Config.getProperty("jcifs.http.basicRealm");
114: if (realm == null)
115: realm = "jCIFS";
116: defaultDomain = Config.getProperty("jcifs.smb.client.domain");
117: }
118:
119: protected void doFile(HttpServletRequest req,
120: HttpServletResponse resp, SmbFile file) throws IOException {
121: byte[] buf = new byte[8192];
122: SmbFileInputStream in;
123: ServletOutputStream out;
124: String url, type;
125: int n;
126:
127: in = new SmbFileInputStream(file);
128: out = resp.getOutputStream();
129: url = file.getPath();
130:
131: resp.setContentType("text/plain");
132:
133: if ((n = url.lastIndexOf('.')) > 0
134: && (type = url.substring(n + 1)) != null
135: && type.length() > 1 && type.length() < 6) {
136: resp.setContentType(mimeMap.getMimeType(type));
137: }
138: resp.setHeader("Content-Length", file.length() + "");
139: resp.setHeader("Accept-Ranges", "Bytes");
140:
141: while ((n = in.read(buf)) != -1) {
142: out.write(buf, 0, n);
143: }
144: }
145:
146: protected int compareNames(SmbFile f1, String f1name, SmbFile f2)
147: throws IOException {
148: if (f1.isDirectory() != f2.isDirectory()) {
149: return f1.isDirectory() ? -1 : 1;
150: }
151: return f1name.compareToIgnoreCase(f2.getName());
152: }
153:
154: protected int compareSizes(SmbFile f1, String f1name, SmbFile f2)
155: throws IOException {
156: long diff;
157:
158: if (f1.isDirectory() != f2.isDirectory()) {
159: return f1.isDirectory() ? -1 : 1;
160: }
161: if (f1.isDirectory()) {
162: return f1name.compareToIgnoreCase(f2.getName());
163: }
164: diff = f1.length() - f2.length();
165: if (diff == 0) {
166: return f1name.compareToIgnoreCase(f2.getName());
167: }
168: return diff > 0 ? -1 : 1;
169: }
170:
171: protected int compareTypes(SmbFile f1, String f1name, SmbFile f2)
172: throws IOException {
173: String f2name, t1, t2;
174: int i;
175:
176: if (f1.isDirectory() != f2.isDirectory()) {
177: return f1.isDirectory() ? -1 : 1;
178: }
179: f2name = f2.getName();
180: if (f1.isDirectory()) {
181: return f1name.compareToIgnoreCase(f2name);
182: }
183: i = f1name.lastIndexOf('.');
184: t1 = i == -1 ? "" : f1name.substring(i + 1);
185: i = f2name.lastIndexOf('.');
186: t2 = i == -1 ? "" : f2name.substring(i + 1);
187:
188: i = t1.compareToIgnoreCase(t2);
189: if (i == 0) {
190: return f1name.compareToIgnoreCase(f2name);
191: }
192: return i;
193: }
194:
195: protected int compareDates(SmbFile f1, String f1name, SmbFile f2)
196: throws IOException {
197: if (f1.isDirectory() != f2.isDirectory()) {
198: return f1.isDirectory() ? -1 : 1;
199: }
200: if (f1.isDirectory()) {
201: return f1name.compareToIgnoreCase(f2.getName());
202: }
203: return f1.lastModified() > f2.lastModified() ? -1 : 1;
204: }
205:
206: protected void doDirectory(HttpServletRequest req,
207: HttpServletResponse resp, SmbFile dir) throws IOException {
208: PrintWriter out;
209: SmbFile[] dirents;
210: SmbFile f;
211: int i, j, len, maxLen, dirCount, fileCount, sort;
212: String str, name, path, fmt;
213: LinkedList sorted;
214: ListIterator iter;
215: SimpleDateFormat sdf = new SimpleDateFormat("MM/d/yy h:mm a");
216: GregorianCalendar cal = new GregorianCalendar();
217:
218: sdf.setCalendar(cal);
219:
220: dirents = dir.listFiles();
221: sorted = new LinkedList();
222: if ((fmt = req.getParameter("fmt")) == null) {
223: fmt = "col";
224: }
225: sort = 0;
226: if ((str = req.getParameter("sort")) == null
227: || str.equals("name")) {
228: sort = 0;
229: } else if (str.equals("size")) {
230: sort = 1;
231: } else if (str.equals("type")) {
232: sort = 2;
233: } else if (str.equals("date")) {
234: sort = 3;
235: }
236: dirCount = fileCount = 0;
237: maxLen = 28;
238: for (i = 0; i < dirents.length; i++) {
239: try {
240: if (dirents[i].getType() == SmbFile.TYPE_NAMED_PIPE) {
241: continue;
242: }
243: } catch (SmbAuthException sae) {
244: } catch (SmbException se) {
245: if (se.getNtStatus() != se.NT_STATUS_UNSUCCESSFUL) {
246: throw se;
247: }
248: }
249: if (dirents[i].isDirectory()) {
250: dirCount++;
251: } else {
252: fileCount++;
253: }
254:
255: name = dirents[i].getName();
256: len = name.length();
257: if (len > maxLen) {
258: maxLen = len;
259: }
260:
261: iter = sorted.listIterator();
262: for (j = 0; iter.hasNext(); j++) {
263: if (sort == 0) {
264: if (compareNames(dirents[i], name, (SmbFile) iter
265: .next()) < 0) {
266: break;
267: }
268: } else if (sort == 1) {
269: if (compareSizes(dirents[i], name, (SmbFile) iter
270: .next()) < 0) {
271: break;
272: }
273: } else if (sort == 2) {
274: if (compareTypes(dirents[i], name, (SmbFile) iter
275: .next()) < 0) {
276: break;
277: }
278: } else if (sort == 3) {
279: if (compareDates(dirents[i], name, (SmbFile) iter
280: .next()) < 0) {
281: break;
282: }
283: }
284: }
285: sorted.add(j, dirents[i]);
286: }
287: if (maxLen > 50) {
288: maxLen = 50;
289: }
290: maxLen *= 9; /* convert to px */
291:
292: out = resp.getWriter();
293:
294: resp.setContentType("text/html");
295:
296: out
297: .println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
298: out.println("<html><head><title>Network Explorer</title>");
299: out
300: .println("<meta HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">");
301: out.println("<style TYPE=\"text/css\">");
302:
303: out.println(style);
304:
305: if (dirents.length < 200) {
306: out.println(" a:hover {");
307: out.println(" background: #a2ff01;");
308: out.println(" }");
309: }
310:
311: out.println("</STYLE>");
312: out.println("</head><body>");
313:
314: out.print("<a class=\"sort\" style=\"width: " + maxLen
315: + ";\" href=\"?fmt=detail&sort=name\">Name</a>");
316: out
317: .println("<a class=\"sort\" href=\"?fmt=detail&sort=size\">Size</a>");
318: out
319: .println("<a class=\"sort\" href=\"?fmt=detail&sort=type\">Type</a>");
320: out
321: .println("<a class=\"sort\" style=\"width: 180\" href=\"?fmt=detail&sort=date\">Modified</a><br clear='all'><p>");
322:
323: path = dir.getCanonicalPath();
324:
325: if (path.length() < 7) {
326: out.println("<b><big>smb://</big></b><br>");
327: path = ".";
328: } else {
329: out.println("<b><big>" + path + "</big></b><br>");
330: path = "../";
331: }
332: out.println((dirCount + fileCount) + " objects (" + dirCount
333: + " directories, " + fileCount + " files)<br>");
334: out
335: .println("<b><a class=\"plain\" href=\".\">normal</a> | <a class=\"plain\" href=\"?fmt=detail\">detailed</a></b>");
336: out
337: .println("<p><table border='0' cellspacing='0' cellpadding='0'><tr><td>");
338:
339: out.print("<A style=\"width: " + maxLen);
340: out.print("; height: 18;\" HREF=\"");
341: out.print(path);
342: out.println("\"><b>↑</b></a>");
343: if (fmt.equals("detail")) {
344: out.println("<br clear='all'>");
345: }
346:
347: if (path.length() == 1
348: || dir.getType() != SmbFile.TYPE_WORKGROUP) {
349: path = "";
350: }
351:
352: iter = sorted.listIterator();
353: while (iter.hasNext()) {
354: f = (SmbFile) iter.next();
355: name = f.getName();
356:
357: if (fmt.equals("detail")) {
358: out.print("<A style=\"width: " + maxLen);
359: out.print("; height: 18;\" HREF=\"");
360: out.print(path);
361: out.print(name);
362:
363: if (f.isDirectory()) {
364: out.print("?fmt=detail\"><b>");
365: out.print(name);
366: out.print("</b></a>");
367: } else {
368: out.print("\"><b>");
369: out.print(name);
370: out.print("</b></a><div align='right'>");
371: out.print((f.length() / 1024) + " KB </div><div>");
372: i = name.lastIndexOf('.') + 1;
373: if (i > 1 && (name.length() - i) < 6) {
374: out.print(name.substring(i).toUpperCase()
375: + "</div class='ext'>");
376: } else {
377: out.print(" </div>");
378: }
379: out.print("<div style='width: 180'>");
380: out.print(sdf.format(new Date(f.lastModified())));
381: out.print("</div>");
382: }
383: out.println("<br clear='all'>");
384: } else {
385: out.print("<A style=\"width: " + maxLen);
386: if (f.isDirectory()) {
387: out.print("; height: 18;\" HREF=\"");
388: out.print(path);
389: out.print(name);
390: out.print("\"><b>");
391: out.print(name);
392: out.print("</b></a>");
393: } else {
394: out.print(";\" HREF=\"");
395: out.print(path);
396: out.print(name);
397: out.print("\"><b>");
398: out.print(name);
399: out.print("</b><br><small>");
400: out.print((f.length() / 1024) + "KB <br>");
401: out.print(sdf.format(new Date(f.lastModified())));
402: out.print("</small>");
403: out.println("</a>");
404: }
405: }
406: }
407:
408: out.println("</td></tr></table>");
409: out.println("</BODY></HTML>");
410: out.close();
411: }
412:
413: private String parseServerAndShare(String pathInfo) {
414: char[] out = new char[256];
415: char ch;
416: int len, p, i;
417:
418: if (pathInfo == null) {
419: return null;
420: }
421: len = pathInfo.length();
422:
423: p = i = 0;
424: while (p < len && pathInfo.charAt(p) == '/') {
425: p++;
426: }
427: if (p == len) {
428: return null;
429: }
430:
431: /* collect server name */
432: while (p < len && (ch = pathInfo.charAt(p)) != '/') {
433: out[i++] = ch;
434: p++;
435: }
436: while (p < len && pathInfo.charAt(p) == '/') {
437: p++;
438: }
439: if (p < len) { /* then there must be a share */
440: out[i++] = '/';
441: do { /* collect the share name */
442: out[i++] = (ch = pathInfo.charAt(p++));
443: } while (p < len && ch != '/');
444: }
445: return new String(out, 0, i);
446: }
447:
448: public void doGet(HttpServletRequest req, HttpServletResponse resp)
449: throws IOException, ServletException {
450: UniAddress dc;
451: String msg, pathInfo, server = null;
452: boolean offerBasic, possibleWorkgroup = true;
453: NtlmPasswordAuthentication ntlm = null;
454: HttpSession ssn = req.getSession(false);
455:
456: if ((pathInfo = req.getPathInfo()) != null) {
457: int i;
458: server = parseServerAndShare(pathInfo);
459: if (server != null && (i = server.indexOf('/')) > 0) {
460: server = server.substring(0, i).toLowerCase();
461: possibleWorkgroup = false;
462: }
463: }
464:
465: msg = req.getHeader("Authorization");
466: offerBasic = enableBasic && (insecureBasic || req.isSecure());
467:
468: if (msg != null
469: && (msg.startsWith("NTLM ") || (offerBasic && msg
470: .startsWith("Basic ")))) {
471:
472: if (msg.startsWith("NTLM ")) {
473: byte[] challenge;
474:
475: if (pathInfo == null || server == null) {
476: String mb = NbtAddress.getByName(
477: NbtAddress.MASTER_BROWSER_NAME, 0x01, null)
478: .getHostAddress();
479: dc = UniAddress.getByName(mb);
480: } else {
481: dc = UniAddress
482: .getByName(server, possibleWorkgroup);
483: }
484:
485: req.getSession(); /* ensure session id is set for cluster env. */
486: challenge = SmbSession.getChallenge(dc);
487: if ((ntlm = NtlmSsp.authenticate(req, resp, challenge)) == null) {
488: return;
489: }
490: } else { /* Basic */
491: String auth = new String(Base64Decoder
492: .decodeToBytes(msg.substring(6)), "US-ASCII");
493: int index = auth.indexOf(':');
494: String user = (index != -1) ? auth.substring(0, index)
495: : auth;
496: String password = (index != -1) ? auth
497: .substring(index + 1) : "";
498: index = user.indexOf('\\');
499: if (index == -1)
500: index = user.indexOf('/');
501: String domain = (index != -1) ? user
502: .substring(0, index) : defaultDomain;
503: user = (index != -1) ? user.substring(index + 1) : user;
504: ntlm = new NtlmPasswordAuthentication(domain, user,
505: password);
506: }
507:
508: req.getSession().setAttribute("npa-" + server, ntlm);
509:
510: } else if (!credentialsSupplied) {
511: if (ssn != null) {
512: ntlm = (NtlmPasswordAuthentication) ssn
513: .getAttribute("npa-" + server);
514: }
515: if (ntlm == null) {
516: resp.setHeader("WWW-Authenticate", "NTLM");
517: if (offerBasic) {
518: resp.addHeader("WWW-Authenticate", "Basic realm=\""
519: + realm + "\"");
520: }
521: resp.setHeader("Connection", "close");
522: resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
523: resp.flushBuffer();
524: return;
525: }
526: }
527:
528: try {
529: SmbFile file;
530:
531: if (ntlm != null) {
532: file = new SmbFile("smb:/" + pathInfo, ntlm);
533: } else if (server == null) {
534: file = new SmbFile("smb://");
535: } else {
536: file = new SmbFile("smb:/" + pathInfo);
537: }
538:
539: if (file.isDirectory()) {
540: doDirectory(req, resp, file);
541: } else {
542: doFile(req, resp, file);
543: }
544: } catch (SmbAuthException sae) {
545: if (ssn != null) {
546: ssn.removeAttribute("npa-" + server);
547: }
548: if (sae.getNtStatus() == sae.NT_STATUS_ACCESS_VIOLATION) {
549: /* Server challenge no longer valid for
550: * externally supplied password hashes.
551: */
552: resp.sendRedirect(req.getRequestURL().toString());
553: return;
554: }
555: resp.setHeader("WWW-Authenticate", "NTLM");
556: if (offerBasic) {
557: resp.addHeader("WWW-Authenticate", "Basic realm=\""
558: + realm + "\"");
559: }
560: resp.setHeader("Connection", "close");
561: resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
562: resp.flushBuffer();
563: return;
564: } catch (DfsReferral dr) {
565: StringBuffer redir = req.getRequestURL();
566: String qs = req.getQueryString();
567: redir = new StringBuffer(redir.substring(0, redir.length()
568: - req.getPathInfo().length()));
569: redir.append(dr.node.replace('\\', '/'));
570: redir.append('/');
571: if (qs != null) {
572: redir.append(req.getQueryString());
573: }
574: resp.sendRedirect(redir.toString());
575: resp.flushBuffer();
576: return;
577: }
578: }
579: }
|