All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jcifs.smb.SmbFile Maven / Gradle / Ivy

There is a newer version: 2.1.10
Show newest version
/* jcifs smb client library in Java
 * Copyright (C) 2000  "Michael B. Allen" 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.smb;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.log4j.Logger;

import jcifs.CIFSContext;
import jcifs.RuntimeCIFSException;
import jcifs.SmbConstants;
import jcifs.context.SingletonContext;
import jcifs.dcerpc.DcerpcHandle;
import jcifs.dcerpc.msrpc.MsrpcDfsRootEnum;
import jcifs.dcerpc.msrpc.MsrpcShareEnum;
import jcifs.dcerpc.msrpc.MsrpcShareGetInfo;
import jcifs.netbios.NbtAddress;
import jcifs.netbios.UniAddress;


/**
 * This class represents a resource on an SMB network. Mainly these
 * resources are files and directories however an SmbFile
 * may also refer to servers and workgroups. If the resource is a file or
 * directory the methods of SmbFile follow the behavior of
 * the well known {@link java.io.File} class. One fundamental difference
 * is the usage of a URL scheme [1] to specify the target file or
 * directory. SmbFile URLs have the following syntax:
 *
 * 
* *
 *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?param=value[param2=value2[...]]]
 * 
* *
* * This example: * *
* *
 *     smb://storage15/public/foo.txt
 * 
* *
* * would reference the file foo.txt in the share * public on the server storage15. In addition * to referencing files and directories, jCIFS can also address servers, * and workgroups. *

* Important: all SMB URLs that represent * workgroups, servers, shares, or directories require a trailing slash '/'. * *

* When using the java.net.URL class with * 'smb://' URLs it is necessary to first call the static * jcifs.Config.registerSmbURLHandler(); method. This is required * to register the SMB protocol handler. *

* The userinfo component of the SMB URL (domain;user:pass) must * be URL encoded if it contains reserved characters. According to RFC 2396 * these characters are non US-ASCII characters and most meta characters * however jCIFS will work correctly with anything but '@' which is used * to delimit the userinfo component from the server and '%' which is the * URL escape character itself. *

* The server * component may a traditional NetBIOS name, a DNS name, or IP * address. These name resolution mechanisms and their resolution order * can be changed (See Setting Name * Resolution Properties). The servername and path components are * not case sensitive but the domain, username, and password components * are. It is also likely that properties must be specified for jcifs * to function (See Setting * JCIFS Properties). Here are some examples of SMB URLs with brief * descriptions of what they do: * *

* [1] This URL scheme is based largely on the SMB * Filesharing URL Scheme IETF draft. * *

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SMB URL Examples
URLDescription
smb://users-nyc;miallen:mypass@angus/tmp/ * This URL references a share called tmp on the server * angus as user miallen who's password is * mypass. *
* smb://Administrator:P%40ss@msmith1/c/WINDOWS/Desktop/foo.txt * A relativly sophisticated example that references a file * msmith1's desktop as user Administrator. Notice the '@' is URL encoded with the '%40' * hexcode escape. *
smb://angus/ * This references only a server. The behavior of some methods is different * in this context(e.g. you cannot delete a server) however * as you might expect the list method will list the available * shares on this server. *
smb://myworkgroup/ * This syntactically is identical to the above example. However if * myworkgroup happends to be a workgroup(which is indeed * suggested by the name) the list method will return * a list of servers that have registered themselves as members of * myworkgroup. *
smb:// * Just as smb://server/ lists shares and * smb://workgroup/ lists servers, the smb:// * URL lists all available workgroups on a netbios LAN. Again, * in this context many methods are not valid and return default * values(e.g. isHidden will always return false). *
smb://angus.foo.net/d/jcifs/pipes.doc * The server name may also be a DNS name as it is in this example. See * Setting Name Resolution Properties * for details. *
smb://192.168.1.15/ADMIN$/ * The server name may also be an IP address. See Setting Name Resolution Properties * for details. *
* smb://domain;username:password@server/share/path/to/file.txt * A prototypical example that uses all the fields. *
smb://myworkgroup/angus/ <-- ILLEGAL * Despite the hierarchial relationship between workgroups, servers, and * filesystems this example is not valid. *
* smb://server/share/path/to/dir <-- ILLEGAL * URLs that represent workgroups, servers, shares, or directories require a trailing slash '/'. *
* smb://MYGROUP/?SERVER=192.168.10.15 * SMB URLs support some query string parameters. In this example * the SERVER parameter is used to override the * server name service lookup to contact the server 192.168.10.15 * (presumably known to be a master * browser) for the server list in workgroup MYGROUP. *
* *

* A second constructor argument may be specified to augment the URL * for better programmatic control when processing many files under * a common base. This is slightly different from the corresponding * java.io.File usage; a '/' at the beginning of the second * parameter will still use the server component of the first parameter. The * examples below illustrate the resulting URLs when this second contructor * argument is used. * *

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Examples Of SMB URLs When Augmented With A Second Constructor Parameter
* First ParameterSecond ParameterResult
* smb://host/share/a/b/ * * c/d/ * * smb://host/share/a/b/c/d/ *
* smb://host/share/foo/bar/ * * /share2/zig/zag * * smb://host/share2/zig/zag *
* smb://host/share/foo/bar/ * * ../zip/ * * smb://host/share/foo/zip/ *
* smb://host/share/zig/zag * * smb://foo/bar/ * * smb://foo/bar/ *
* smb://host/share/foo/ * * ../.././.././../foo/ * * smb://host/foo/ *
* smb://host/share/zig/zag * * / * * smb://host/ *
* smb://server/ * * ../ * * smb://server/ *
* smb:// * * myworkgroup/ * * smb://myworkgroup/ *
* smb://myworkgroup/ * * angus/ * * smb://myworkgroup/angus/ <-- ILLEGAL
(But if you first create an SmbFile with 'smb://workgroup/' and * use and use it as the first parameter to a constructor that accepts it with a second String parameter jCIFS * will factor out the 'workgroup'.) *
* *

* Instances of the SmbFile class are immutable; that is, * once created, the abstract pathname represented by an SmbFile object * will never change. * * @see java.io.File */ public class SmbFile extends URLConnection implements SmbConstants { static final int O_RDONLY = 0x01; static final int O_WRONLY = 0x02; static final int O_RDWR = 0x03; static final int O_APPEND = 0x04; // Open Function Encoding // create if the file does not exist static final int O_CREAT = 0x0010; // fail if the file exists static final int O_EXCL = 0x0020; // truncate if the file exists static final int O_TRUNC = 0x0040; // share access /** * When specified as the shareAccess constructor parameter, * other SMB clients (including other threads making calls into jCIFS) * will not be permitted to access the target file and will receive "The * file is being accessed by another process" message. */ public static final int FILE_NO_SHARE = 0x00; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to read from the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_READ = 0x01; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to write to the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_WRITE = 0x02; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to delete the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_DELETE = 0x04; // file attribute encoding /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be read-only */ public static final int ATTR_READONLY = 0x01; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be hidden */ public static final int ATTR_HIDDEN = 0x02; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be a system file */ public static final int ATTR_SYSTEM = 0x04; /** * A file with this bit on as returned by getAttributes() is * a volume */ public static final int ATTR_VOLUME = 0x08; /** * A file with this bit on as returned by getAttributes() is * a directory */ public static final int ATTR_DIRECTORY = 0x10; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() is an archived file */ public static final int ATTR_ARCHIVE = 0x20; // extended file attribute encoding(others same as above) static final int ATTR_COMPRESSED = 0x800; static final int ATTR_NORMAL = 0x080; static final int ATTR_TEMPORARY = 0x100; static final int ATTR_GET_MASK = 0x7FFF; /* orig 0x7fff */ static final int ATTR_SET_MASK = 0x30A7; /* orig 0x0027 */ static final int DEFAULT_ATTR_EXPIRATION_PERIOD = 5000; static final int HASH_DOT = ".".hashCode(); static final int HASH_DOT_DOT = "..".hashCode(); static Logger log = Logger.getLogger(SmbFile.class); /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a regular file or directory. */ public static final int TYPE_FILESYSTEM = 0x01; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a workgroup. */ public static final int TYPE_WORKGROUP = 0x02; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a server. */ public static final int TYPE_SERVER = 0x04; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a share. */ public static final int TYPE_SHARE = 0x08; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a named pipe. */ public static final int TYPE_NAMED_PIPE = 0x10; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a printer. */ public static final int TYPE_PRINTER = 0x20; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a communications device. */ public static final int TYPE_COMM = 0x40; private String canon; // Initially null; set by getUncPath; dir must end with '/' private String share; // Can be null private long createTime; private long lastModified; private long lastAccess; private int attributes; private long attrExpiration; private long size; private long sizeExpiration; private boolean isExists; private int shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; private SmbComBlankResponse blank_resp = null; private DfsReferral dfsReferral = null; // For getDfsPath() and getServerWithDfs() SmbTree tree = null; // Initially null String unc; // Initially null; set by getUncPath; never ends with '/' int fid; // Initially 0; set by open() int type; boolean opened; private int tree_num; private CIFSContext transportContext; private boolean nonPooled; private SmbTransport exclusiveTransport; private Random random = new Random(); /** * Constructs an SmbFile representing a resource on an SMB network such as * a file or directory. See the description and examples of smb URLs above. * * @param url * A URL string * @throws MalformedURLException * If the parent and child parameters * do not follow the prescribed syntax */ @Deprecated public SmbFile ( String url ) throws MalformedURLException { this(new URL(null, url, SingletonContext.getInstance().getUrlHandler())); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory from a URL object. * * @param url * The URL of the target resource */ @Deprecated public SmbFile ( URL url ) { this(url, SingletonContext.getInstance().withCredentials(new NtlmPasswordAuthentication(SingletonContext.getInstance(), url.getUserInfo()))); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the parent SmbFile. See the description above for examples * of using the second name parameter. * * @param context * A base SmbFile * @param name * A path string relative to the parent paremeter * @throws MalformedURLException * If the parent and child parameters * do not follow the prescribed syntax * @throws UnknownHostException * If the server or workgroup of the context file cannot be determined */ public SmbFile ( SmbFile context, String name ) throws MalformedURLException, UnknownHostException { this( context.isWorkgroup0() ? new URL(null, "smb://" + checkName(name), context.transportContext.getUrlHandler()) : new URL(context.getURL(), checkName(name), context.transportContext.getUrlHandler()), context.transportContext); setupContext(context, name); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the context. See the description above for examples of * using the second name parameter. The shareAccess * parameter controls what permissions other clients have when trying * to access the same file while this instance is still open. This * value is either FILE_NO_SHARE or any combination * of FILE_SHARE_READ, FILE_SHARE_WRITE, and * FILE_SHARE_DELETE logically OR'd together. * * @param context * A base SmbFile * @param name * A path string relative to the context file path * @param shareAccess * Specifies what access other clients have while this file is open. * @throws MalformedURLException * If the context and name parameters * do not follow the prescribed syntax * @throws UnknownHostException */ public SmbFile ( SmbFile context, String name, int shareAccess ) throws MalformedURLException, UnknownHostException { this( context.isWorkgroup0() ? new URL(null, "smb://" + checkName(name), context.getTransportContext().getUrlHandler()) : new URL(context.getURL(), checkName(name), context.getTransportContext().getUrlHandler()), context.transportContext); if ( ( shareAccess & ~ ( FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ) ) != 0 ) { throw new RuntimeCIFSException("Illegal shareAccess parameter"); } this.shareAccess = shareAccess; setupContext(context, name); } /** * @param url * @param tc * context to use * @param shareAccess * Specifies what access other clients have while this file is open. * @throws MalformedURLException */ public SmbFile ( String url, CIFSContext tc, int shareAccess ) throws MalformedURLException { this(new URL(null, url, tc.getUrlHandler()), tc); if ( ( shareAccess & ~ ( FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ) ) != 0 ) { throw new RuntimeCIFSException("Illegal shareAccess parameter"); } this.shareAccess = shareAccess; } /** * Construct from string URL * * @param url * @param tc * context to use * @throws MalformedURLException */ public SmbFile ( String url, CIFSContext tc ) throws MalformedURLException { this(new URL(null, url, tc.getUrlHandler()), tc); } /** * Construct from URL * * @param url * @param tc * context to use */ public SmbFile ( URL url, CIFSContext tc ) { super(url); this.transportContext = tc; } SmbFile ( SmbFile context, String name, boolean loadedAttributes, int type, int attributes, long createTime, long lastModified, long lastAccess, long size ) throws MalformedURLException, UnknownHostException { this( context.isWorkgroup0() ? new URL(null, "smb://" + checkName(name) + "/", Handler.SMB_HANDLER) : new URL(context.url, checkName(name) + ( ( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" )), context.transportContext); setupContext(context, name); /* * why? am I going around in circles? * this.type = type == TYPE_WORKGROUP ? 0 : type; */ this.type = type; this.attributes = attributes; this.createTime = createTime; this.lastModified = lastModified; this.lastAccess = lastAccess; this.size = size; this.isExists = true; if ( loadedAttributes ) { this.attrExpiration = this.sizeExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); } } private static String checkName ( String name ) throws MalformedURLException { if ( name == null || name.length() == 0 ) { throw new MalformedURLException("Name must not be empty"); } return name; } /** * @param context * @param name */ private final void setupContext ( SmbFile context, String name ) { if ( context.share != null ) { this.tree = context.tree; this.dfsReferral = context.dfsReferral; } int last = name.length() - 1; if ( last >= 0 && name.charAt(last) == '/' ) { name = name.substring(0, last); } context.getUncPath0(); if ( context.share == null ) { this.unc = "\\"; this.canon = "/"; } else if ( context.unc.equals("\\") ) { this.unc = '\\' + name; this.canon = '/' + name; this.share = context.share; } else { this.unc = context.unc + '\\' + name; this.canon = context.canon + '/' + name; this.share = context.share; } } /** * @param nonPooled * the nonPooled to set */ public void setNonPooled ( boolean nonPooled ) { this.nonPooled = nonPooled; } /** * @return the transportContext */ public CIFSContext getTransportContext () { return this.transportContext; } /** * @return session that this file has been loaded through */ public SmbSession getSession () { SmbTree t = this.tree; if ( t != null ) { return t.session; } return null; } private SmbComBlankResponse blank_resp () { if ( this.blank_resp == null ) { this.blank_resp = new SmbComBlankResponse(getSession().getConfig()); } return this.blank_resp; } void resolveDfs ( ServerMessageBlock request ) throws SmbException { if ( request instanceof SmbComClose ) return; for ( int retries = 0; retries < 1 + this.getTransportContext().getConfig().getMaxRequestRetries(); retries++ ) { try { resolveDfs0(request); return; } catch ( NullPointerException npe ) { // Bug where transport or tconHostName is null indicates // failed to clean up properly from dropped connection. log.debug("resolveDfs", npe); } catch ( SmbException smbe ) { // The connection may have been dropped? if ( smbe.getNtStatus() != NtStatus.NT_STATUS_NOT_FOUND ) { throw smbe; } log.debug("resolveDfs", smbe); } // If we get here, we apparently have a bad connection. // Disconnect and try again. if ( log.isDebugEnabled() ) log.debug("Retrying (" + retries + ") resolveDfs: " + request); disconnect(true); try { Thread.sleep(500 + this.random.nextInt(5000)); } catch ( InterruptedException e ) { log.debug("resolveDfs", e); } } } private void resolveDfs0 ( ServerMessageBlock request ) throws SmbException { connect0(); SmbSession session = getSession(); SmbTransport transport = session.getTransport(); CIFSContext tc = getTransportContext(); DfsReferral dr = tc.getDfs().resolve(tc, transport.tconHostName, this.tree.share, this.unc); if ( dr != null ) { if ( log.isDebugEnabled() ) { log.debug("Info " + transport.tconHostName + "\\" + this.tree.share + this.unc + " -> " + dr); } String service = this.tree != null ? this.tree.service : null; if ( request != null ) { switch ( request.command ) { case ServerMessageBlock.SMB_COM_TRANSACTION: case ServerMessageBlock.SMB_COM_TRANSACTION2: switch ( ( (SmbComTransaction) request ).subCommand & 0xFF ) { case SmbComTransaction.TRANS2_GET_DFS_REFERRAL: break; default: service = "A:"; } break; default: service = "A:"; } } SmbException se = null; DfsReferral start = dr; do { try { if ( log.isTraceEnabled() ) { log.trace("DFS redirect: " + dr); } UniAddress addr = tc.getNameServiceClient().getByName(dr.server); SmbTransport trans = tc.getTransportPool().getSmbTransport(tc, addr, this.url.getPort(), false, shouldForceSigning(tc, dr.share)); synchronized ( trans ) { /* * This is a key point. This is where we set the "tree" of this file which * is like changing the rug out from underneath our feet. */ /* * Technically we should also try to authenticate here but that means doing the session setup * and * tree connect separately. For now a simple connect will at least tell us if the host is alive. * That should be sufficient for 99% of the cases. We can revisit this again for 2.0. */ trans.connect(); this.tree = trans.getSmbSession(tc).getSmbTree(dr.share, service); if ( dr != start && dr.key != null ) { dr.map.put(dr.key, dr); } } se = null; break; } catch ( IOException ioe ) { log.debug("Error checking dfs root", ioe); if ( ioe instanceof SmbException ) { se = (SmbException) ioe; } else { se = new SmbException("Failed to connect to server " + dr.server, ioe); } } dr = dr.next; } while ( dr != start ); if ( se != null ) throw se; this.dfsReferral = dr; if ( dr.pathConsumed < 0 ) { log.warn("Path consumed out of range " + dr.pathConsumed); dr.pathConsumed = 0; } else if ( dr.pathConsumed > this.unc.length() ) { log.warn("Path consumed out of range " + dr.pathConsumed); dr.pathConsumed = this.unc.length(); } if ( log.isDebugEnabled() ) { log.debug("UNC is '" + this.unc + "'"); log.debug("Consumed '" + this.unc.substring(0, dr.pathConsumed) + "'"); } String dunc = this.unc.substring(dr.pathConsumed); if ( log.isDebugEnabled() ) { log.debug("Remaining '" + dunc + "'"); } if ( dunc.equals("") ) dunc = "\\"; if ( !dr.path.equals("") ) dunc = "\\" + dr.path + dunc; if ( dunc.charAt(0) != '\\' ) { log.warn("No slash at start of remaining DFS path " + dunc); } this.unc = dunc; if ( log.isDebugEnabled() ) { log.debug("Final resolved " + transport.tconHostName + "\\" + this.tree.share + this.unc + " -> " + dr); } if ( request != null && request.path != null && request.path.endsWith("\\") && dunc.endsWith("\\") == false ) { dunc += "\\"; } if ( request != null ) { request.path = dunc; request.flags2 |= SmbConstants.FLAGS2_RESOLVE_PATHS_IN_DFS; } } else if ( this.tree.inDomainDfs && ! ( request instanceof NtTransQuerySecurityDesc ) && ! ( request instanceof SmbComClose ) && ! ( request instanceof SmbComFindClose2 ) ) { throw new SmbException(NtStatus.NT_STATUS_NOT_FOUND, false); } else { log.trace("Not in DFS"); if ( request != null ) request.flags2 &= ~SmbConstants.FLAGS2_RESOLVE_PATHS_IN_DFS; } } private static boolean shouldForceSigning ( CIFSContext tc, String share ) { return tc.getConfig().isIpcSigningEnforced() && !tc.getCredentials().isAnonymous() && isIPC(share); } /** * @param service * @param share * @return whether this is a IPC */ private static boolean isIPC ( String share ) { log.debug("Check " + share); if ( share == null || "IPC$".equals(share) ) { if ( log.isDebugEnabled() ) { log.debug("Share is " + share + " enforcing signing"); } return true; } return false; } synchronized void disconnect ( boolean inError ) { SmbTransport transport = this.getSession().transport(); synchronized ( transport ) { SmbTree t = this.tree; if ( t != null ) { t.treeDisconnect(inError); t.session.logoff(inError); this.tree = null; } } } void send ( ServerMessageBlock request, ServerMessageBlock response ) throws SmbException { send(request, response, true); } void send ( ServerMessageBlock request, ServerMessageBlock response, boolean timeout ) throws SmbException { String savedPath = ( request != null ) ? request.path : null; for ( int retries = 1; retries < getTransportContext().getConfig().getMaxRequestRetries(); retries++ ) { try { send0(request, response, timeout); return; } catch ( SmbException smbe ) { if ( smbe.getNtStatus() != NtStatus.NT_STATUS_INVALID_PARAMETER ) { throw smbe; } log.debug("send", smbe); } // If we get here, we got the 'The Parameter is incorrect' error. // Disconnect and try again from scratch. if ( log.isDebugEnabled() ) log.debug("Retrying (" + retries + ") send: " + request); disconnect(true); try { Thread.sleep(500 + this.random.nextInt(5000)); } catch ( InterruptedException e ) { log.debug("send", e); } if ( request != null ) { // resolveDfs() and tree.send() modify the request packet. // I want to restore it before retrying. request.reset() // restores almost everything that was modified, except the path. request.reset(); request.path = savedPath; } if ( response != null ) response.reset(); connect0(); } } private void send0 ( ServerMessageBlock request, ServerMessageBlock response, boolean timeout ) throws SmbException, DfsReferral { for ( ;; ) { resolveDfs(request); try { this.tree.send(request, response, timeout); break; } catch ( DfsReferral dre ) { if ( dre.resolveHashes ) { throw dre; } request.reset(); log.trace("send0", dre); } } } static String queryLookup ( String query, String param ) { char in[] = query.toCharArray(); int i, ch, st, eq; st = eq = 0; for ( i = 0; i < in.length; i++ ) { ch = in[ i ]; if ( ch == '&' ) { if ( eq > st ) { String p = new String(in, st, eq - st); if ( p.equalsIgnoreCase(param) ) { eq++; return new String(in, eq, i - eq); } } st = i + 1; } else if ( ch == '=' ) { eq = i; } } if ( eq > st ) { String p = new String(in, st, eq - st); if ( p.equalsIgnoreCase(param) ) { eq++; return new String(in, eq, in.length - eq); } } return null; } UniAddress[] addresses; int addressIndex; UniAddress getAddress () throws UnknownHostException { if ( this.addressIndex == 0 ) return getFirstAddress(); return this.addresses[ this.addressIndex - 1 ]; } UniAddress getFirstAddress () throws UnknownHostException { this.addressIndex = 0; String host = this.url.getHost(); String path = this.url.getPath(); String query = this.url.getQuery(); if ( query != null ) { String server = queryLookup(query, "server"); if ( server != null && server.length() > 0 ) { this.addresses = new UniAddress[1]; this.addresses[ 0 ] = getTransportContext().getNameServiceClient().getByName(server); return getNextAddress(); } String address = queryLookup(query, "address"); if ( address != null && address.length() > 0 ) { byte[] ip = java.net.InetAddress.getByName(address).getAddress(); this.addresses = new UniAddress[1]; this.addresses[ 0 ] = new UniAddress(java.net.InetAddress.getByAddress(host, ip)); return getNextAddress(); } } if ( host.length() == 0 ) { try { NbtAddress addr = getTransportContext().getNameServiceClient().getNbtByName(NbtAddress.MASTER_BROWSER_NAME, 0x01, null); this.addresses = new UniAddress[1]; this.addresses[ 0 ] = getTransportContext().getNameServiceClient().getByName(addr.getHostAddress()); } catch ( UnknownHostException uhe ) { log.debug("Unknown host", uhe); if ( getTransportContext().getConfig().getDefaultDomain() == null ) { throw uhe; } this.addresses = getTransportContext().getNameServiceClient() .getAllByName(getTransportContext().getConfig().getDefaultDomain(), true); } } else if ( path.length() == 0 || path.equals("/") ) { this.addresses = getTransportContext().getNameServiceClient().getAllByName(host, true); } else { this.addresses = getTransportContext().getNameServiceClient().getAllByName(host, false); } return getNextAddress(); } UniAddress getNextAddress () { UniAddress addr = null; if ( this.addressIndex < this.addresses.length ) addr = this.addresses[ this.addressIndex++ ]; return addr; } boolean hasNextAddress () { return this.addressIndex < this.addresses.length; } void connect0 () throws SmbException { try { connect(); } catch ( UnknownHostException uhe ) { throw new SmbException("Failed to connect to server", uhe); } catch ( SmbException se ) { throw se; } catch ( IOException ioe ) { throw new SmbException("Failed to connect to server", ioe); } } synchronized void doConnect () throws IOException { SmbTransport trans; UniAddress addr = getAddress(); CIFSContext ctx = getTransportContext(); SmbTree t; if ( log.isDebugEnabled() ) { log.debug("Tree is " + this.tree); } if ( this.tree != null ) { trans = getSession().getTransport(); t = this.tree; } else if ( this.nonPooled ) { if ( log.isDebugEnabled() ) { log.debug("Using exclusive transport for " + this); } this.exclusiveTransport = ctx.getTransportPool() .getSmbTransport(ctx, addr, this.url.getPort(), true, shouldForceSigning(ctx, this.share)); trans = this.exclusiveTransport; trans.setDontTimeout(true); t = trans.getSmbSession(ctx).getSmbTree(this.share, null); } else { trans = ctx.getTransportPool().getSmbTransport(ctx, addr, this.url.getPort(), false, shouldForceSigning(ctx, this.share)); t = trans.getSmbSession(ctx).getSmbTree(this.share, null); } if ( log.isDebugEnabled() && ( trans.flags2 & SmbConstants.FLAGS2_SECURITY_SIGNATURES ) != 0 && !trans.server.signaturesRequired && !isIPC(this.share) && !ctx.getConfig().isSigningEnforced() ) { log.debug("Signatures for file enabled but not required " + this); } String hostName = getServerWithDfs(); DfsReferral referral = ctx.getDfs().resolve(ctx, hostName, t.share, null); t.inDomainDfs = referral != null; if ( t.inDomainDfs ) { // make sure transport is connected trans.connect(); t.connectionState = 2; } try { if ( log.isTraceEnabled() ) log.trace("doConnect: " + addr); t.treeConnect(null, null); } catch ( SmbAuthException sae ) { log.debug("Authentication failed", sae); SmbSession ssn; if ( this.share == null ) { // IPC$ - try "anonymous" credentials ssn = trans.getSmbSession(ctx.withAnonymousCredentials()); t = ssn.getSmbTree(null, null); t.treeConnect(null, null); } else if ( ctx.renewCredentials(this.url.toString(), sae) ) { log.debug("Trying to renew credentials after auth error"); ssn = trans.getSmbSession(ctx); t = ssn.getSmbTree(this.share, null); t.inDomainDfs = referral != null; if ( this.tree.inDomainDfs ) { this.tree.connectionState = 2; } t.treeConnect(null, null); } else { throw sae; } } this.tree = t; } /** * It is not necessary to call this method directly. This is the * URLConnection implementation of connect(). */ @Override public synchronized void connect () throws IOException { if ( isConnected() && getSession().getTransport().tconHostName == null ) { /* * Tree/session thinks it is connected but transport disconnected * under it, reset tree to reflect the truth. */ log.debug("Disconnecting failed tree and session"); disconnect(true); } if ( isConnected() ) { log.trace("Already connected"); return; } getUncPath0(); getFirstAddress(); for ( ;; ) { try { doConnect(); return; } catch ( SmbAuthException sae ) { throw sae; // Prevents account lockout on servers with multiple IPs } catch ( SmbException se ) { if ( getNextAddress() == null ) throw se; log.debug("Connect failed", se); } } } synchronized boolean isConnected () { return this.tree != null && this.tree.isConnected(); } int open0 ( int flags, int access, int attrs, int options ) throws SmbException { int f; connect0(); if ( log.isDebugEnabled() ) log.debug("open0: " + this.unc); /* * NT Create AndX / Open AndX Request / Response */ if ( getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { SmbComNTCreateAndXResponse response = new SmbComNTCreateAndXResponse(getSession().getConfig()); SmbComNTCreateAndX request = new SmbComNTCreateAndX( getSession().getConfig(), this.unc, flags, access, this.shareAccess, attrs, options, null); if ( this instanceof SmbNamedPipe ) { request.flags0 |= 0x16; request.desiredAccess |= 0x20000; response.isExtended = true; } send(request, response); f = response.fid; this.type = response.fileType; this.createTime = response.creationTime; this.lastModified = response.lastWriteTime; this.lastAccess = response.lastAccessTime; this.size = response.allocationSize; this.attributes = response.extFileAttributes & ATTR_GET_MASK; this.attrExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); this.isExists = true; } else { SmbComOpenAndXResponse response = new SmbComOpenAndXResponse(getSession().getConfig()); send(new SmbComOpenAndX(getSession().getConfig(), this.unc, access, flags, null), response); f = response.fid; } return f; } void open ( int flags, int access, int attrs, int options ) throws SmbException { if ( isOpen() ) { return; } this.fid = open0(flags, access, attrs, options); this.opened = true; this.tree_num = this.tree.tree_num; } boolean isOpen () { return this.opened && isConnected() && this.tree_num == this.tree.tree_num; } void close ( int f, long lastWriteTime ) throws SmbException { if ( log.isDebugEnabled() ) { log.debug("close: " + f); } /* * Close Request / Response */ try { send(new SmbComClose(getSession().getConfig(), f, lastWriteTime), blank_resp()); } finally { if ( this.exclusiveTransport != null ) { try { log.debug("Disconnecting exclusive transport"); this.exclusiveTransport.doDisconnect(false); } catch ( IOException e ) { throw new SmbException("Failed to close exclusive transport"); } } } } void close ( long lastWriteTime ) throws SmbException { if ( isOpen() == false ) { return; } close(this.fid, lastWriteTime); this.opened = false; } /** * Close the file handle * * @throws SmbException */ public void close () throws SmbException { close(0L); } /** * Returns the last component of the target URL. This will * effectively be the name of the file or directory represented by this * SmbFile or in the case of URLs that only specify a server * or workgroup, the server or workgroup will be returned. The name of * the root URL smb:// is also smb://. If this * SmbFile refers to a workgroup, server, share, or directory, * the name will include a trailing slash '/' so that composing new * SmbFiles will maintain the trailing slash requirement. * * @return The last component of the URL associated with this SMB * resource or smb:// if the resource is smb:// * itself. */ public String getName () { getUncPath0(); if ( this.canon.length() > 1 ) { int i = this.canon.length() - 2; while ( this.canon.charAt(i) != '/' ) { i--; } return this.canon.substring(i + 1); } else if ( this.share != null ) { return this.share + '/'; } else if ( this.url.getHost().length() > 0 ) { return this.url.getHost() + '/'; } else { return "smb://"; } } /** * Everything but the last component of the URL representing this SMB * resource is effectivly it's parent. The root URL smb:// * does not have a parent. In this case smb:// is returned. * * @return The parent directory of this SMB resource or * smb:// if the resource refers to the root of the URL * hierarchy which incedentally is also smb://. */ public String getParent () { String str = this.url.getAuthority(); if ( str.length() > 0 ) { StringBuffer sb = new StringBuffer("smb://"); sb.append(str); getUncPath0(); if ( this.canon.length() > 1 ) { sb.append(this.canon); } else { sb.append('/'); } str = sb.toString(); int i = str.length() - 2; while ( str.charAt(i) != '/' ) { i--; } return str.substring(0, i + 1); } return "smb://"; } /** * Returns the full uncanonicalized URL of this SMB resource. An * SmbFile constructed with the result of this method will * result in an SmbFile that is equal to the original. * * @return The uncanonicalized full URL of this SMB resource. */ public String getPath () { return this.url.toString(); } String getUncPath0 () { if ( this.unc == null ) { char[] in = this.url.getPath().toCharArray(); char[] out = new char[in.length]; int length = in.length, i, o, state; /* * The canonicalization routine */ state = 0; o = 0; for ( i = 0; i < length; i++ ) { switch ( state ) { case 0: if ( in[ i ] != '/' ) { return null; } out[ o++ ] = in[ i ]; state = 1; break; case 1: if ( in[ i ] == '/' ) { break; } else if ( in[ i ] == '.' && ( ( i + 1 ) >= length || in[ i + 1 ] == '/' ) ) { i++; break; } else if ( ( i + 1 ) < length && in[ i ] == '.' && in[ i + 1 ] == '.' && ( ( i + 2 ) >= length || in[ i + 2 ] == '/' ) ) { i += 2; if ( o == 1 ) break; do { o--; } while ( o > 1 && out[ o - 1 ] != '/' ); break; } state = 2; case 2: if ( in[ i ] == '/' ) { state = 1; } out[ o++ ] = in[ i ]; break; } } this.canon = new String(out, 0, o); if ( o > 1 ) { o--; i = this.canon.indexOf('/', 1); if ( i < 0 ) { this.share = this.canon.substring(1); this.unc = "\\"; } else if ( i == o ) { this.share = this.canon.substring(1, i); this.unc = "\\"; } else { this.share = this.canon.substring(1, i); this.unc = this.canon.substring(i, out[ o ] == '/' ? o : o + 1); this.unc = this.unc.replace('/', '\\'); } } else { this.share = null; this.unc = "\\"; } } return this.unc; } /** * Retuns the Windows UNC style path with backslashs intead of forward slashes. * * @return The UNC path. */ public String getUncPath () { getUncPath0(); if ( this.share == null ) { return "\\\\" + this.url.getHost(); } return "\\\\" + this.url.getHost() + this.canon.replace('/', '\\'); } /** * Returns the full URL of this SMB resource with '.' and '..' components * factored out. An SmbFile constructed with the result of * this method will result in an SmbFile that is equal to * the original. * * @return The canonicalized URL of this SMB resource. */ public String getCanonicalPath () { String str = this.url.getAuthority(); getUncPath0(); if ( str.length() > 0 ) { return "smb://" + this.url.getAuthority() + this.canon; } return "smb://"; } /** * Retrieves the share associated with this SMB resource. In * the case of smb://, smb://workgroup/, * and smb://server/ URLs which do not specify a share, * null will be returned. * * @return The share component or null if there is no share */ public String getShare () { return this.share; } String getServerWithDfs () { if ( this.dfsReferral != null ) { return this.dfsReferral.server; } return getServer(); } /** * Retrieve the hostname of the server for this SMB resource. If this * SmbFile references a workgroup, the name of the workgroup * is returned. If this SmbFile refers to the root of this * SMB network hierarchy, null is returned. * * @return The server or workgroup name or null if this * SmbFile refers to the root smb:// resource. */ public String getServer () { String str = this.url.getHost(); if ( str.length() == 0 ) { return null; } return str; } /** * Returns type of of object this SmbFile represents. * * @return TYPE_FILESYSTEM, TYPE_WORKGROUP, TYPE_SERVER, TYPE_SHARE, * TYPE_PRINTER, TYPE_NAMED_PIPE, or TYPE_COMM. * @throws SmbException */ public int getType () throws SmbException { if ( this.type == 0 ) { if ( getUncPath0().length() > 1 ) { this.type = TYPE_FILESYSTEM; } else if ( this.share != null ) { // treeConnect good enough to test service type connect0(); if ( this.share.equals("IPC$") ) { this.type = TYPE_NAMED_PIPE; } else if ( this.tree.service.equals("LPT1:") ) { this.type = TYPE_PRINTER; } else if ( this.tree.service.equals("COMM") ) { this.type = TYPE_COMM; } else { this.type = TYPE_SHARE; } } else if ( this.url.getAuthority() == null || this.url.getAuthority().length() == 0 ) { this.type = TYPE_WORKGROUP; } else { UniAddress addr; try { addr = getAddress(); } catch ( UnknownHostException uhe ) { throw new SmbException(this.url.toString(), uhe); } if ( addr.getAddress() instanceof NbtAddress ) { int code = ( (NbtAddress) addr.getAddress() ).getNameType(); if ( code == 0x1d || code == 0x1b ) { this.type = TYPE_WORKGROUP; return this.type; } } this.type = TYPE_SERVER; } } return this.type; } boolean isWorkgroup0 () throws UnknownHostException { if ( this.type == TYPE_WORKGROUP || this.url.getHost().length() == 0 ) { this.type = TYPE_WORKGROUP; return true; } getUncPath0(); if ( this.share == null ) { UniAddress addr = getAddress(); if ( addr.getAddress() instanceof NbtAddress ) { int code = ( (NbtAddress) addr.getAddress() ).getNameType(); if ( code == 0x1d || code == 0x1b ) { this.type = TYPE_WORKGROUP; return true; } } this.type = TYPE_SERVER; } return false; } Info queryPath ( String path, int infoLevel ) throws SmbException { connect0(); if ( log.isDebugEnabled() ) { log.debug("queryPath: " + path); } /* * normally we'd check the negotiatedCapabilities for CAP_NT_SMBS * however I can't seem to get a good last modified time from * SMB_COM_QUERY_INFORMATION so if NT_SMBs are requested * by the server than in this case that's what it will get * regardless of what jcifs.smb.client.useNTSmbs is set * to(overrides negotiatedCapabilities). */ /* * We really should do the referral before this in case * the redirected target has different capabilities. But * the way we have been doing that is to call exists() which * calls this method so another technique will be necessary * to support DFS referral _to_ Win95/98/ME. */ if ( getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { /* * Trans2 Query Path Information Request / Response */ Trans2QueryPathInformationResponse response = new Trans2QueryPathInformationResponse(getSession().getConfig(), infoLevel); send(new Trans2QueryPathInformation(getSession().getConfig(), path, infoLevel), response); if ( log.isDebugEnabled() ) { log.debug("Path information " + response); } return response.info; } /* * Query Information Request / Response */ SmbComQueryInformationResponse response = new SmbComQueryInformationResponse( getSession().getConfig(), getSession().getTransport().server.serverTimeZone * 1000 * 60L); send(new SmbComQueryInformation(getSession().getConfig(), path), response); if ( log.isDebugEnabled() ) { log.debug("Legacy path information " + response); } return response; } /** * Tests to see if the SMB resource exists. If the resource refers * only to a server, this method determines if the server exists on the * network and is advertising SMB services. If this resource refers to * a workgroup, this method determines if the workgroup name is valid on * the local SMB network. If this SmbFile refers to the root * smb:// resource true is always returned. If * this SmbFile is a traditional file or directory, it will * be queried for on the specified server as expected. * * @return true if the resource exists or is alive or * false otherwise * @throws SmbException */ public boolean exists () throws SmbException { if ( this.attrExpiration > System.currentTimeMillis() ) { log.trace("Using cached attributes"); return this.isExists; } getUncPath0(); this.attributes = ATTR_READONLY | ATTR_DIRECTORY; this.createTime = 0L; this.lastModified = 0L; this.lastAccess = 0L; this.isExists = false; try { if ( this.url.getHost().length() == 0 ) {} else if ( this.share == null ) { if ( getType() == TYPE_WORKGROUP ) { getTransportContext().getNameServiceClient().getByName(this.url.getHost(), true); } else { getTransportContext().getNameServiceClient().getByName(this.url.getHost()).getHostName(); } } else if ( getUncPath0().length() == 1 || this.share.equalsIgnoreCase("IPC$") ) { connect0(); // treeConnect is good enough } else { Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO); this.attributes = info.getAttributes(); this.createTime = info.getCreateTime(); this.lastModified = info.getLastWriteTime(); this.lastAccess = info.getLastAccessTime(); } /* * If any of the above fail, isExists will not be set true */ this.isExists = true; } catch ( UnknownHostException uhe ) { log.debug("Unknown host", uhe); } catch ( SmbException se ) { log.trace("exists:", se); switch ( se.getNtStatus() ) { case NtStatus.NT_STATUS_NO_SUCH_FILE: case NtStatus.NT_STATUS_OBJECT_NAME_INVALID: case NtStatus.NT_STATUS_OBJECT_NAME_NOT_FOUND: case NtStatus.NT_STATUS_OBJECT_PATH_NOT_FOUND: break; default: throw se; } } this.attrExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); return this.isExists; } /** * Watches a directory for changes * * Will block until the server returns a set of changes that match the given filter. The file will be automatically * opened if it is not and should be closed with {@link #close()} when no longer * needed. * * Closing the file should cancel a pending notify request, but that does not seem to work reliable in all * implementations. * * Changes in between these calls (as long as the file is open) are buffered by the server, so iteratively calling * this method should provide all changes (size of that buffer can be adjusted through * {@link jcifs.Configuration#getNotifyBufferSize()}). * If the server cannot fulfill the request because the changes did not fit the buffer * it will return an empty list of changes. * * * @param filter * see constants in {@link FileNotifyInformation} * @param recursive * whether to also watch subdirectories * @return list of changes since the last watch call, empty if there were more changes than the server could handle. * @throws SmbException */ public List watch ( int filter, boolean recursive ) throws SmbException { if ( filter == 0 ) { throw new IllegalArgumentException("filter must not be 0"); } if ( !isDirectory() ) { throw new SmbException("Is not a directory"); } connect0(); if ( !this.getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS"); } boolean wasOpen = this.opened; if ( !wasOpen ) { open(O_RDONLY, READ_CONTROL, 0, 1); if ( log.isDebugEnabled() ) { log.debug("Opened " + this.fid); } } /* * NtTrans Notify Change Request / Response */ NtTransNotifyChange request = new NtTransNotifyChange(getSession().getConfig(), this.fid, filter, recursive); NtTransNotifyChangeResponse response = new NtTransNotifyChangeResponse(getSession().getConfig()); if ( log.isTraceEnabled() ) { log.trace("Sending NtTransNotifyChange for " + this.fid); } send(request, response, false); if ( log.isTraceEnabled() ) { log.trace("Returned from NtTransNotifyChange " + response.status); } if ( response.status == 0x0000010B ) { wasOpen = false; } if ( response.status == 0x0000010C ) { response.notifyInformation.clear(); } return response.notifyInformation; } /** * Tests to see if the file this SmbFile represents can be * read. Because any file, directory, or other resource can be read if it * exists, this method simply calls the exists method. * * @return true if the file is read-only * @throws SmbException */ public boolean canRead () throws SmbException { if ( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for reading? return true; } return exists(); // try opening and catch sharing violation? } /** * Tests to see if the file this SmbFile represents * exists and is not marked read-only. By default, resources are * considered to be read-only and therefore for smb://, * smb://workgroup/, and smb://server/ resources * will be read-only. * * @return true if the resource exists is not marked * read-only * @throws SmbException */ public boolean canWrite () throws SmbException { if ( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for writing? return true; } return exists() && ( this.attributes & ATTR_READONLY ) == 0; } /** * Tests to see if the file this SmbFile represents is a directory. * * @return true if this SmbFile is a directory * @throws SmbException */ public boolean isDirectory () throws SmbException { if ( getUncPath0().length() == 1 ) { return true; } if ( !exists() ) return false; return ( this.attributes & ATTR_DIRECTORY ) == ATTR_DIRECTORY; } /** * Tests to see if the file this SmbFile represents is not a directory. * * @return true if this SmbFile is not a directory * @throws SmbException */ public boolean isFile () throws SmbException { if ( getUncPath0().length() == 1 ) { return false; } exists(); return ( this.attributes & ATTR_DIRECTORY ) == 0; } /** * Tests to see if the file this SmbFile represents is marked as * hidden. This method will also return true for shares with names that * end with '$' such as IPC$ or C$. * * @return true if the SmbFile is marked as being hidden * @throws SmbException */ public boolean isHidden () throws SmbException { if ( this.share == null ) { return false; } else if ( getUncPath0().length() == 1 ) { if ( this.share.endsWith("$") ) { return true; } return false; } exists(); return ( this.attributes & ATTR_HIDDEN ) == ATTR_HIDDEN; } /** * If the path of this SmbFile falls within a DFS volume, * this method will return the referral path to which it maps. Otherwise * null is returned. * * @return URL to the DFS volume * @throws SmbException */ public String getDfsPath () throws SmbException { resolveDfs(null); if ( this.dfsReferral == null ) { return null; } String path = "smb:/" + this.dfsReferral.server + "/" + this.dfsReferral.share + this.unc; path = path.replace('\\', '/'); if ( isDirectory() ) { path += '/'; } return path; } /** * Retrieve the time this SmbFile was created. The value * returned is suitable for constructing a {@link java.util.Date} object * (i.e. seconds since Epoch 1970). Times should be the same as those * reported using the properties dialog of the Windows Explorer program. * * For Win95/98/Me this is actually the last write time. It is currently * not possible to retrieve the create time from files on these systems. * * @return The number of milliseconds since the 00:00:00 GMT, January 1, * 1970 as a long value * @throws SmbException */ public long createTime () throws SmbException { if ( getUncPath0().length() > 1 ) { exists(); return this.createTime; } return 0L; } /** * Retrieve the last time the file represented by this * SmbFile was modified. The value returned is suitable for * constructing a {@link java.util.Date} object (i.e. seconds since Epoch * 1970). Times should be the same as those reported using the properties * dialog of the Windows Explorer program. * * @return The number of milliseconds since the 00:00:00 GMT, January 1, * 1970 as a long value * @throws SmbException */ public long lastModified () throws SmbException { if ( getUncPath0().length() > 1 ) { exists(); return this.lastModified; } return 0L; } /** * Retrieve the last acces time of the file represented by this SmbFile * * @return The number of milliseconds since the 00:00:00 GMT, January 1, * 1970 as a long value * @throws SmbException */ public long lastAccess () throws SmbException { if ( getUncPath0().length() > 1 ) { exists(); return this.lastAccess; } return 0L; } /** * List the contents of this SMB resource. The list returned by this * method will be; * *

    *
  • files and directories contained within this resource if the * resource is a normal disk file directory, *
  • all available NetBIOS workgroups or domains if this resource is * the top level URL smb://, *
  • all servers registered as members of a NetBIOS workgroup if this * resource refers to a workgroup in a smb://workgroup/ URL, *
  • all browseable shares of a server including printers, IPC * services, or disk volumes if this resource is a server URL in the form * smb://server/, *
  • or null if the resource cannot be resolved. *
* * @return A String[] array of files and directories, * workgroups, servers, or shares depending on the context of the * resource URL * @throws SmbException */ public String[] list () throws SmbException { return list("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless list() * method minus filenames filtered by the specified filter. * * @param filter * a filename filter to exclude filenames from the results * @return String[] array of matching files and directories, * workgroups, servers, or shares depending on the context of the * resource URL * @throws SmbException * # @return An array of filenames */ public String[] list ( SmbFilenameFilter filter ) throws SmbException { return list("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null); } /** * List the contents of this SMB resource as an array of * SmbFile objects. This method is much more efficient than * the regular list method when querying attributes of each * file in the result set. *

* The list of SmbFiles returned by this method will be; * *

    *
  • files and directories contained within this resource if the * resource is a normal disk file directory, *
  • all available NetBIOS workgroups or domains if this resource is * the top level URL smb://, *
  • all servers registered as members of a NetBIOS workgroup if this * resource refers to a workgroup in a smb://workgroup/ URL, *
  • all browseable shares of a server including printers, IPC * services, or disk volumes if this resource is a server URL in the form * smb://server/, *
  • or null if the resource cannot be resolved. *
* * @return An array of SmbFile objects representing file * and directories, workgroups, servers, or shares depending on the context * of the resource URL * @throws SmbException */ public SmbFile[] listFiles () throws SmbException { return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null); } /** * The CIFS protocol provides for DOS "wildcards" to be used as * a performance enhancement. The client does not have to filter * the names and the server does not have to return all directory * entries. *

* The wildcard expression may consist of two special meta * characters in addition to the normal filename characters. The '*' * character matches any number of characters in part of a name. If * the expression begins with one or more '?'s then exactly that * many characters will be matched whereas if it ends with '?'s * it will match that many characters or less. *

* Wildcard expressions will not filter workgroup names or server names. * *

* *
     * winnt> ls c?o*
     * clock.avi                  -rw--      82944 Mon Oct 14 1996 1:38 AM
     * Cookies                    drw--          0 Fri Nov 13 1998 9:42 PM
     * 2 items in 5ms
     * 
* *
* * @param wildcard * a wildcard expression * @throws SmbException * @return An array of SmbFile objects representing file * and directories, workgroups, servers, or shares depending on the context * of the resource URL */ public SmbFile[] listFiles ( String wildcard ) throws SmbException { return listFiles(wildcard, ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless listFiles() * method minus files filtered by the specified filename filter. * * @param filter * a filter to exclude files from the results * @return An array of SmbFile objects * @throws SmbException */ public SmbFile[] listFiles ( SmbFilenameFilter filter ) throws SmbException { return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless listFiles() * method minus filenames filtered by the specified filter. * * @param filter * a file filter to exclude files from the results * @return An array of SmbFile objects * @throws SmbException */ public SmbFile[] listFiles ( SmbFileFilter filter ) throws SmbException { return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, filter); } String[] list ( String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException { List list = new ArrayList<>(); doEnum(list, false, wildcard, searchAttributes, fnf, ff); return list.toArray(new String[list.size()]); } SmbFile[] listFiles ( String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException { List list = new ArrayList<>(); doEnum(list, true, wildcard, searchAttributes, fnf, ff); return list.toArray(new SmbFile[list.size()]); } void doEnum ( List list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException { if ( ff != null && ff instanceof DosFileFilter ) { DosFileFilter dff = (DosFileFilter) ff; if ( dff.wildcard != null ) wildcard = dff.wildcard; searchAttributes = dff.attributes; } try { int hostlen = this.url.getHost().length(); if ( hostlen == 0 || getType() == TYPE_WORKGROUP ) { doNetServerEnum(list, files, wildcard, searchAttributes, fnf, ff); } else if ( this.share == null ) { doShareEnum(list, files, wildcard, searchAttributes, fnf, ff); } else { doFindFirstNext(list, files, wildcard, searchAttributes, fnf, ff); } } catch ( UnknownHostException uhe ) { throw new SmbException(this.url.toString(), uhe); } catch ( MalformedURLException mue ) { throw new SmbException(this.url.toString(), mue); } } void doShareEnum ( List list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException, UnknownHostException, MalformedURLException { String p = this.url.getPath(); IOException last = null; FileEntry[] entries; UniAddress addr; Set set; if ( p.lastIndexOf('/') != ( p.length() - 1 ) ) throw new SmbException(this.url.toString() + " directory must end with '/'"); if ( getType() != TYPE_SERVER ) throw new SmbException("The requested list operations is invalid: " + this.url.toString()); set = new HashSet<>(); if ( getTransportContext().getDfs().isTrustedDomain(getTransportContext(), getServer()) ) { /* * The server name is actually the name of a trusted * domain. Add DFS roots to the list. */ try { entries = doDfsRootEnum(); for ( int ei = 0; ei < entries.length; ei++ ) { FileEntry e = entries[ ei ]; if ( set.contains(e) == false ) set.add(e); } } catch ( IOException ioe ) { log.debug("DS enumeration failed", ioe); } } addr = getFirstAddress(); while ( addr != null ) { try { doConnect(); try { entries = doMsrpcShareEnum(); } catch ( IOException ioe ) { log.debug("doMsrpcShareEnum failed", ioe); entries = doNetShareEnum(); } for ( int ei = 0; ei < entries.length; ei++ ) { FileEntry e = entries[ ei ]; if ( set.contains(e) == false ) set.add(e); } break; } catch ( IOException ioe ) { log.debug("doNetShareEnum failed", ioe); last = ioe; } addr = getNextAddress(); } if ( last != null && set.isEmpty() ) { if ( last instanceof SmbException == false ) throw new SmbException(this.url.toString(), last); throw (SmbException) last; } for ( FileEntry e : set ) { String name = e.getName(); if ( fnf != null && fnf.accept(this, name) == false ) continue; if ( name.length() > 0 ) { // if !files we don't need to create SmbFiles here SmbFile f = new SmbFile(this, name, false, e.getType(), ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L, 0L, 0L); if ( ff != null && ff.accept(f) == false ) continue; if ( files ) { list.add(f); } else { list.add(name); } } } } FileEntry[] doDfsRootEnum () throws IOException { MsrpcDfsRootEnum rpc; try ( DcerpcHandle handle = DcerpcHandle .getHandle("ncacn_np:" + getAddress().getHostAddress() + "[\\PIPE\\netdfs]", this.getTransportContext()) ) { rpc = new MsrpcDfsRootEnum(getServer()); handle.sendrecv(rpc); if ( rpc.retval != 0 ) throw new SmbException(rpc.retval, true); return rpc.getEntries(); } } FileEntry[] doMsrpcShareEnum () throws IOException { MsrpcShareEnum rpc = new MsrpcShareEnum(this.url.getHost()); /* * JCIFS will build a composite list of shares if the target host has * multiple IP addresses such as when domain-based DFS is in play. Because * of this, to ensure that we query each IP individually without re-resolving * the hostname and getting a different IP, we must use the current addresses * IP rather than just url.getHost() like we were using prior to 1.2.16. */ try ( DcerpcHandle handle = DcerpcHandle .getHandle("ncacn_np:" + getAddress().getHostAddress() + "[\\PIPE\\srvsvc]", this.getTransportContext()) ) { handle.sendrecv(rpc); if ( rpc.retval != 0 ) throw new SmbException(rpc.retval, true); return rpc.getEntries(); } } FileEntry[] doNetShareEnum () throws SmbException { SmbComTransaction req = new NetShareEnum(getSession().getConfig()); SmbComTransactionResponse resp = new NetShareEnumResponse(getSession().getConfig()); send(req, resp); if ( resp.status != WinError.ERROR_SUCCESS ) throw new SmbException(resp.status, true); return resp.results; } void doNetServerEnum ( List list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException, UnknownHostException, MalformedURLException { int listType = this.url.getHost().length() == 0 ? 0 : getType(); SmbComTransaction req; SmbComTransactionResponse resp; if ( listType == 0 ) { connect0(); req = new NetServerEnum2(getSession().getConfig(), getSession().getTransport().server.oemDomainName, NetServerEnum2.SV_TYPE_DOMAIN_ENUM); resp = new NetServerEnum2Response(getSession().getConfig()); } else if ( listType == TYPE_WORKGROUP ) { req = new NetServerEnum2(getSession().getConfig(), this.url.getHost(), NetServerEnum2.SV_TYPE_ALL); resp = new NetServerEnum2Response(getSession().getConfig()); } else { throw new SmbException("The requested list operations is invalid: " + this.url.toString()); } boolean more; do { int n; send(req, resp); if ( resp.status != WinError.ERROR_SUCCESS && resp.status != WinError.ERROR_MORE_DATA ) { throw new SmbException(resp.status, true); } more = resp.status == WinError.ERROR_MORE_DATA; n = more ? resp.numEntries - 1 : resp.numEntries; for ( int i = 0; i < n; i++ ) { FileEntry e = resp.results[ i ]; String name = e.getName(); if ( fnf != null && fnf.accept(this, name) == false ) continue; if ( name.length() > 0 ) { // if !files we don't need to create SmbFiles here SmbFile f = new SmbFile(this, name, false, e.getType(), ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L, 0L, 0L); if ( ff != null && ff.accept(f) == false ) continue; if ( files ) { list.add(f); } else { list.add(name); } } } if ( getType() != TYPE_WORKGROUP ) { break; } req.subCommand = (byte) SmbComTransaction.NET_SERVER_ENUM3; req.reset(0, ( (NetServerEnum2Response) resp ).lastName); resp.reset(); } while ( more ); } void doFindFirstNext ( List list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException, UnknownHostException, MalformedURLException { SmbComTransaction req; Trans2FindFirst2Response resp; int sid; String path = getUncPath0(); String p = this.url.getPath(); if ( p.lastIndexOf('/') != ( p.length() - 1 ) ) { throw new SmbException(this.url.toString() + " directory must end with '/'"); } connect0(); req = new Trans2FindFirst2(getSession().getConfig(), path, wildcard, searchAttributes); resp = new Trans2FindFirst2Response(getSession().getConfig()); if ( log.isDebugEnabled() ) { log.debug("doFindFirstNext: " + req.path); } send(req, resp); sid = resp.sid; req = new Trans2FindNext2(getSession().getConfig(), sid, resp.resumeKey, resp.lastName); /* * The only difference between first2 and next2 responses is subCommand * so let's recycle the response object. */ resp.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; for ( ;; ) { for ( int i = 0; i < resp.numEntries; i++ ) { FileEntry e = resp.results[ i ]; String name = e.getName(); if ( name.length() < 3 ) { int h = name.hashCode(); if ( h == HASH_DOT || h == HASH_DOT_DOT ) { if ( name.equals(".") || name.equals("..") ) continue; } } if ( fnf != null && fnf.accept(this, name) == false ) { continue; } if ( name.length() > 0 ) { SmbFile f = new SmbFile( this, name, true, TYPE_FILESYSTEM, e.getAttributes(), e.createTime(), e.lastModified(), e.lastAccess(), e.length()); if ( ff != null && ff.accept(f) == false ) { continue; } if ( files ) { list.add(f); } else { list.add(name); } } } if ( resp.isEndOfSearch || resp.numEntries == 0 ) { break; } req.reset(resp.resumeKey, resp.lastName); resp.reset(); send(req, resp); } try { send(new SmbComFindClose2(getSession().getConfig(), sid), blank_resp()); } catch ( SmbException se ) { log.debug("SmbComFindClose2 failed", se); } } /** * Changes the name of the file this SmbFile represents to the name * designated by the SmbFile argument. *
* Remember: SmbFiles are immutible and therefore * the path associated with this SmbFile object will not * change). To access the renamed file it is necessary to construct a * new SmbFile. * * @param dest * An SmbFile that represents the new pathname * @throws SmbException * @throws NullPointerException * If the dest argument is null */ public void renameTo ( SmbFile dest ) throws SmbException { resolveDfs(null); dest.resolveDfs(null); if ( getUncPath0().length() == 1 || dest.getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } if ( !this.tree.equals(dest.tree) ) { // trigger requests to resolve the actual target this.exists(); dest.exists(); if ( !this.tree.equals(dest.tree) ) { if ( log.isDebugEnabled() ) { log.debug("Tree mismatch " + this.tree + " other " + dest.tree); } throw new SmbException("Invalid operation for workgroups, servers, or shares"); } } if ( log.isDebugEnabled() ) { log.debug("renameTo: " + this.unc + " -> " + dest.unc); } this.attrExpiration = this.sizeExpiration = 0; dest.attrExpiration = 0; /* * Rename Request / Response */ send(new SmbComRename(getSession().getConfig(), this.unc, dest.unc), blank_resp()); } class WriterThread extends Thread { byte[] b; int n; long off; boolean ready; SmbFile dest; SmbException e = null; boolean useNTSmbs; SmbComWriteAndX reqx; SmbComWrite req; ServerMessageBlock resp; WriterThread () throws SmbException { super("JCIFS-WriterThread"); this.useNTSmbs = getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS); if ( this.useNTSmbs ) { this.reqx = new SmbComWriteAndX(getSession().getConfig()); this.resp = new SmbComWriteAndXResponse(getSession().getConfig()); } else { this.req = new SmbComWrite(getSession().getConfig()); this.resp = new SmbComWriteResponse(getSession().getConfig()); } this.ready = false; } synchronized void write ( byte[] buffer, int len, SmbFile d, long offset ) { this.b = buffer; this.n = len; this.dest = d; this.off = offset; this.ready = false; notify(); } @Override public void run () { synchronized ( this ) { try { for ( ;; ) { notify(); this.ready = true; while ( this.ready ) { wait(); } if ( this.n == -1 ) { return; } if ( this.useNTSmbs ) { this.reqx.setParam(this.dest.fid, this.off, this.n, this.b, 0, this.n); this.dest.send(this.reqx, this.resp); } else { this.req.setParam(this.dest.fid, this.off, this.n, this.b, 0, this.n); this.dest.send(this.req, this.resp); } } } catch ( SmbException ex ) { this.e = ex; } catch ( Exception x ) { this.e = new SmbException("WriterThread", x); } notify(); } } } void copyTo0 ( SmbFile dest, byte[][] b, int bsize, WriterThread w, SmbComReadAndX req, SmbComReadAndXResponse resp ) throws SmbException { int i; if ( this.attrExpiration < System.currentTimeMillis() ) { this.attributes = ATTR_READONLY | ATTR_DIRECTORY; this.createTime = 0L; this.lastModified = 0L; this.isExists = false; Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO); this.attributes = info.getAttributes(); this.createTime = info.getCreateTime(); this.lastModified = info.getLastWriteTime(); /* * If any of the above fails, isExists will not be set true */ this.isExists = true; this.attrExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); } if ( isDirectory() ) { SmbFile[] files; SmbFile ndest; String path = dest.getUncPath0(); if ( path.length() > 1 ) { try { dest.mkdir(); dest.setPathInformation(this.attributes, this.createTime, this.lastModified, this.lastAccess); } catch ( SmbException se ) { log.trace("copyTo0", se); if ( se.getNtStatus() != NtStatus.NT_STATUS_ACCESS_DENIED && se.getNtStatus() != NtStatus.NT_STATUS_OBJECT_NAME_COLLISION ) { throw se; } } } files = listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null); try { for ( i = 0; i < files.length; i++ ) { ndest = new SmbFile( dest, files[ i ].getName(), true, files[ i ].type, files[ i ].attributes, files[ i ].createTime, files[ i ].lastModified, files[ i ].lastAccess, files[ i ].size); files[ i ].copyTo0(ndest, b, bsize, w, req, resp); } } catch ( UnknownHostException uhe ) { throw new SmbException(this.url.toString(), uhe); } catch ( MalformedURLException mue ) { throw new SmbException(this.url.toString(), mue); } } else { long off; try { open(SmbFile.O_RDONLY, 0, ATTR_NORMAL, 0); try { dest.open(SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, this.attributes, 0); } catch ( SmbAuthException sae ) { log.trace("copyTo0", sae); if ( ( dest.attributes & ATTR_READONLY ) != 0 ) { /* * Remove READONLY and try again */ dest.setPathInformation(dest.attributes & ~ATTR_READONLY, 0L, 0L, 0L); dest.open(SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, this.attributes, 0); } else { throw sae; } } i = 0; off = 0L; for ( ;; ) { req.setParam(this.fid, off, bsize); resp.setParam(b[ i ], 0); send(req, resp); synchronized ( w ) { if ( w.e != null ) { throw w.e; } while ( !w.ready ) { try { w.wait(); } catch ( InterruptedException ie ) { throw new SmbException(dest.url.toString(), ie); } } if ( w.e != null ) { throw w.e; } if ( resp.dataLength <= 0 ) { break; } w.write(b[ i ], resp.dataLength, dest, off); } i = i == 1 ? 0 : 1; off += resp.dataLength; } dest.send( new Trans2SetFileInformation( getSession().getConfig(), dest.fid, this.attributes, this.createTime, this.lastModified, this.lastAccess), new Trans2SetFileInformationResponse(getSession().getConfig())); dest.close(0L); } catch ( SmbException se ) { if ( !getTransportContext().getConfig().isIgnoreCopyToException() ) { throw new SmbException("Failed to copy file from [" + this.toString() + "] to [" + dest.toString() + "]", se); } log.debug("Copy failed", se); } finally { close(); } } } /** * This method will copy the file or directory represented by this * SmbFile and it's sub-contents to the location specified by the * dest parameter. This file and the destination file do not * need to be on the same host. This operation does not copy extended * file attibutes such as ACLs but it does copy regular attributes as * well as create and last write times. This method is almost twice as * efficient as manually copying as it employs an additional write * thread to read and write data concurrently. *
* It is not possible (nor meaningful) to copy entire workgroups or * servers. * * @param dest * the destination file or directory * @throws SmbException */ public void copyTo ( SmbFile dest ) throws SmbException { SmbComReadAndX req; SmbComReadAndXResponse resp; WriterThread w; int bsize; byte[][] b; resolveDfs(null); dest.resolveDfs(null); /* * Should be able to copy an entire share actually */ if ( this.share == null || dest.share == null ) { throw new SmbException("Invalid operation for workgroups or servers"); } req = new SmbComReadAndX(getSession().getConfig()); resp = new SmbComReadAndXResponse(getSession().getConfig()); /* * At this point the maxBufferSize values are from the server * exporting the volumes, not the one that we will actually * end up performing IO with. If the server hosting the * actual files has a smaller maxBufSize this could be * incorrect. To handle this properly it is necessary * to redirect the tree to the target server first before * establishing buffer size. These exists() calls facilitate * that. */ resolveDfs(null); /* * It is invalid for the source path to be a child of the destination * path or visa versa. */ try { if ( getAddress().equals(dest.getAddress()) && this.canon.regionMatches(true, 0, dest.canon, 0, Math.min(this.canon.length(), dest.canon.length())) ) { throw new SmbException("Source and destination paths overlap."); } } catch ( UnknownHostException uhe ) { log.debug("Unknown host", uhe); } w = new WriterThread(); w.setDaemon(true); w.start(); /* * Downgrade one transport to the lower of the negotiated buffer sizes * so we can just send whatever is received. */ SmbTransport t1 = getSession().getTransport(); SmbTransport t2 = dest.getSession().getTransport(); if ( t1.snd_buf_size < t2.snd_buf_size ) { t2.snd_buf_size = t1.snd_buf_size; } else { t1.snd_buf_size = t2.snd_buf_size; } bsize = Math.min(t1.rcv_buf_size - 70, t1.snd_buf_size - 70); b = new byte[2][bsize]; try { copyTo0(dest, b, bsize, w, req, resp); } finally { w.write(null, -1, null, 0); } } /** * This method will delete the file or directory specified by this * SmbFile. If the target is a directory, the contents of * the directory will be deleted as well. If a file within the directory or * it's sub-directories is marked read-only, the read-only status will * be removed and the file will be deleted. * * @throws SmbException */ public void delete () throws SmbException { exists(); getUncPath0(); delete(this.unc); } void delete ( String fileName ) throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } if ( System.currentTimeMillis() > this.attrExpiration ) { this.attributes = ATTR_READONLY | ATTR_DIRECTORY; this.createTime = 0L; this.lastModified = 0L; this.lastAccess = 0L; this.isExists = false; Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO); this.attributes = info.getAttributes(); this.createTime = info.getCreateTime(); this.lastModified = info.getLastWriteTime(); this.lastAccess = info.getLastAccessTime(); this.attrExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); this.isExists = true; } if ( ( this.attributes & ATTR_READONLY ) != 0 ) { setReadWrite(); } /* * Delete or Delete Directory Request / Response */ if ( log.isDebugEnabled() ) { log.debug("delete: " + fileName); } if ( ( this.attributes & ATTR_DIRECTORY ) != 0 ) { /* * Recursively delete directory contents */ try { SmbFile[] l = listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null); for ( int i = 0; i < l.length; i++ ) { l[ i ].delete(); } } catch ( SmbException se ) { /* * Oracle FilesOnline version 9.0.4 doesn't send '.' and '..' so * listFiles may generate undesireable "cannot find * the file specified". */ log.debug("delete", se); if ( se.getNtStatus() != NtStatus.NT_STATUS_NO_SUCH_FILE ) { throw se; } } send(new SmbComDeleteDirectory(getSession().getConfig(), fileName), blank_resp()); } else { send(new SmbComDelete(getSession().getConfig(), fileName), blank_resp()); } this.attrExpiration = this.sizeExpiration = 0; } /** * Returns the length of this SmbFile in bytes. If this object * is a TYPE_SHARE the total capacity of the disk shared in * bytes is returned. If this object is a directory or a type other than * TYPE_SHARE, 0L is returned. * * @return The length of the file in bytes or 0 if this * SmbFile is not a file. * @throws SmbException */ public long length () throws SmbException { if ( this.sizeExpiration > System.currentTimeMillis() ) { return this.size; } if ( getType() == TYPE_SHARE ) { Trans2QueryFSInformationResponse response; int level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION; response = new Trans2QueryFSInformationResponse(getSession().getConfig(), level); send(new Trans2QueryFSInformation(getSession().getConfig(), level), response); this.size = response.info.getCapacity(); } else if ( getUncPath0().length() > 1 && this.type != TYPE_NAMED_PIPE ) { Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_STANDARD_INFO); this.size = info.getSize(); } else { this.size = 0L; } this.sizeExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); return this.size; } /** * This method returns the free disk space in bytes of the drive this share * represents or the drive on which the directory or file resides. Objects * other than TYPE_SHARE or TYPE_FILESYSTEM will result * in 0L being returned. * * @return the free disk space in bytes of the drive on which this file or * directory resides * @throws SmbException */ public long getDiskFreeSpace () throws SmbException { if ( getType() == TYPE_SHARE || this.type == TYPE_FILESYSTEM ) { int level = Trans2QueryFSInformationResponse.SMB_FS_FULL_SIZE_INFORMATION; try { return queryFSInformation(level); } catch ( SmbException ex ) { log.debug("getDiskFreeSpace", ex); switch ( ex.getNtStatus() ) { case NtStatus.NT_STATUS_INVALID_INFO_CLASS: case NtStatus.NT_STATUS_UNSUCCESSFUL: // NetApp Filer // SMB_FS_FULL_SIZE_INFORMATION not supported by the server. level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION; return queryFSInformation(level); } throw ex; } } return 0L; } private long queryFSInformation ( int level ) throws SmbException { Trans2QueryFSInformationResponse response; connect0(); response = new Trans2QueryFSInformationResponse(getSession().getConfig(), level); send(new Trans2QueryFSInformation(getSession().getConfig(), level), response); if ( this.type == TYPE_SHARE ) { this.size = response.info.getCapacity(); this.sizeExpiration = System.currentTimeMillis() + getTransportContext().getConfig().getAttributeCacheTimeout(); } return response.info.getFree(); } /** * Creates a directory with the path specified by this * SmbFile. For this method to be successful, the target * must not already exist. This method will fail when * used with smb://, smb://workgroup/, * smb://server/, or smb://server/share/ URLs * because workgroups, servers, and shares cannot be dynamically created * (although in the future it may be possible to create shares). * * @throws SmbException */ public void mkdir () throws SmbException { String path = getUncPath0(); if ( path.length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } resolveDfs(null); // get the path again, this may have changed through DFS referrals path = getUncPath0(); /* * Create Directory Request / Response */ if ( log.isDebugEnabled() ) { log.debug("mkdir: " + path); } send(new SmbComCreateDirectory(getSession().getConfig(), path), blank_resp()); this.attrExpiration = this.sizeExpiration = 0; } /** * Creates a directory with the path specified by this SmbFile * and any parent directories that do not exist. This method will fail * when used with smb://, smb://workgroup/, * smb://server/, or smb://server/share/ URLs * because workgroups, servers, and shares cannot be dynamically created * (although in the future it may be possible to create shares). * * @throws SmbException */ public void mkdirs () throws SmbException { SmbFile parent; connect0(); try { parent = new SmbFile(getParent(), getTransportContext()); } catch ( IOException ioe ) { log.debug("mkdirs", ioe); return; } if ( parent.exists() == false ) { parent.mkdirs(); } try { mkdir(); } catch ( SmbException e ) { log.debug("mkdirs", e); // Ignore "Cannot create a file when that file already exists." errors for now as // they seem to be show up under some cinditions most likely due to timing issues. if ( e.getNtStatus() != NtStatus.NT_STATUS_OBJECT_NAME_COLLISION ) { throw e; } } } /** * Create a new file but fail if it already exists. The check for * existance of the file and it's creation are an atomic operation with * respect to other filesystem activities. * * @throws SmbException */ public void createNewFile () throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } try { close(open0(O_RDWR | O_CREAT | O_EXCL, 0, ATTR_NORMAL, 0), 0L); } catch ( SmbException e ) { throw e; } } void setPathInformation ( int attrs, long ctime, long mtime, long atime ) throws SmbException { int f, dir; exists(); if ( !this.getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { // should implement SMB_COM_SET_INFORMATION? throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS"); } dir = this.attributes & ATTR_DIRECTORY; f = open0(O_RDONLY, FILE_WRITE_ATTRIBUTES, dir, dir != 0 ? 0x0001 : 0x0040); try { send( new Trans2SetFileInformation(getSession().getConfig(), f, attrs | dir, ctime, mtime, atime), new Trans2SetFileInformationResponse(getSession().getConfig())); } finally { close(f, 0L); } this.attrExpiration = 0; } /** * Set the create time of the file. The time is specified as milliseconds * from Jan 1, 1970 which is the same as that which is returned by the * createTime() method. *
* This method does not apply to workgroups, servers, or shares. * * @param time * the create time as milliseconds since Jan 1, 1970 * @throws SmbException */ public void setCreateTime ( long time ) throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } setPathInformation(0, time, 0L, 0L); } /** * Set the last modified time of the file. The time is specified as milliseconds * from Jan 1, 1970 which is the same as that which is returned by the * lastModified(), getLastModified(), and getDate() methods. *
* This method does not apply to workgroups, servers, or shares. * * @param time * the last modified time as milliseconds since Jan 1, 1970 * @throws SmbException */ public void setLastModified ( long time ) throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } setPathInformation(0, 0L, time, 0L); } /** * Set the last accesss time of the file. The time is specified as milliseconds * from Jan 1, 1970 which is the same as that which is returned by the * lastModified(), getLastModified(), and getDate() methods. *
* This method does not apply to workgroups, servers, or shares. * * @param time * the last access time as milliseconds since Jan 1, 1970 * @throws SmbException */ public void setLastAccess ( long time ) throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } setPathInformation(0, 0L, 0L, time); } /** * Return the attributes of this file. Attributes are represented as a * bitset that must be masked with ATTR_* constants to determine * if they are set or unset. The value returned is suitable for use with * the setAttributes() method. * * @return the ATTR_* attributes associated with this file * @throws SmbException */ public int getAttributes () throws SmbException { if ( getUncPath0().length() == 1 ) { return 0; } exists(); return this.attributes & ATTR_GET_MASK; } /** * Set the attributes of this file. Attributes are composed into a * bitset by bitwise ORing the ATTR_* constants. Setting the * value returned by getAttributes will result in both files * having the same attributes. * * @param attrs * attribute flags * * @throws SmbException */ public void setAttributes ( int attrs ) throws SmbException { if ( getUncPath0().length() == 1 ) { throw new SmbException("Invalid operation for workgroups, servers, or shares"); } setPathInformation(attrs & ATTR_SET_MASK, 0L, 0L, 0L); } /** * Make this file read-only. This is shorthand for setAttributes( * getAttributes() | ATTR_READ_ONLY ). * * @throws SmbException */ public void setReadOnly () throws SmbException { setAttributes(getAttributes() | ATTR_READONLY); } /** * Turn off the read-only attribute of this file. This is shorthand for * setAttributes( getAttributes() & ~ATTR_READONLY ). * * @throws SmbException */ public void setReadWrite () throws SmbException { setAttributes(getAttributes() & ~ATTR_READONLY); } /** * Returns a {@link java.net.URL} for this SmbFile. The * URL may be used as any other URL might to * access an SMB resource. Currently only retrieving data and information * is supported (i.e. no doOutput). * * @deprecated Use getURL() instead * @return A new {@link java.net.URL} for this SmbFile */ @Deprecated public URL toURL () { return this.getURL(); } /** * Computes a hashCode for this file based on the URL string and IP * address if the server. The hashing function uses the hashcode of the * server address, the canonical representation of the URL, and does not * compare authentication information. In essance, two * SmbFile objects that refer to * the same file should generate the same hashcode provided it is possible * to make such a determination. * * @return A hashcode for this abstract file */ @Override public int hashCode () { int hash; try { hash = getAddress().hashCode(); } catch ( UnknownHostException uhe ) { hash = getServer().toUpperCase().hashCode(); } getUncPath0(); return hash + this.canon.toUpperCase().hashCode(); } protected boolean pathNamesPossiblyEqual ( String path1, String path2 ) { int p1, p2, l1, l2; // if unsure return this method returns true p1 = path1.lastIndexOf('/'); p2 = path2.lastIndexOf('/'); l1 = path1.length() - p1; l2 = path2.length() - p2; // anything with dots voids comparison if ( l1 > 1 && path1.charAt(p1 + 1) == '.' ) return true; if ( l2 > 1 && path2.charAt(p2 + 1) == '.' ) return true; return l1 == l2 && path1.regionMatches(true, p1, path2, p2, l1); } /** * Tests to see if two SmbFile objects are equal. Two * SmbFile objects are equal when they reference the same SMB * resource. More specifically, two SmbFile objects are * equals if their server IP addresses are equal and the canonicalized * representation of their URLs, minus authentication parameters, are * case insensitivly and lexographically equal. *
* For example, assuming the server angus resolves to the * 192.168.1.15 IP address, the below URLs would result in * SmbFiles that are equal. * *

*

* *
     * smb://192.168.1.15/share/DIR/foo.txt
     * smb://angus/share/data/../dir/foo.txt
     * 
* *
* * @param obj * Another SmbFile object to compare for equality * @return true if the two objects refer to the same SMB resource * and false otherwise */ @Override public boolean equals ( Object obj ) { if ( obj instanceof SmbFile ) { SmbFile f = (SmbFile) obj; boolean ret; if ( this == f ) return true; /* * If uncertain, pathNamesPossiblyEqual returns true. * Comparing canonical paths is definitive. */ if ( pathNamesPossiblyEqual(this.url.getPath(), f.url.getPath()) ) { getUncPath0(); f.getUncPath0(); if ( this.canon.equalsIgnoreCase(f.canon) ) { try { ret = getAddress().equals(f.getAddress()); } catch ( UnknownHostException uhe ) { log.debug("Unknown host", uhe); ret = getServer().equalsIgnoreCase(f.getServer()); } return ret; } } } return false; } /** * Returns the string representation of this SmbFile object. This will * be the same as the URL used to construct this SmbFile. * This method will return the same value * as getPath. * * @return The original URL representation of this SMB resource */ @Override public String toString () { return this.url.toString(); } /* URLConnection implementation */ /** * This URLConnection method just returns the result of length(). * * @return the length of this file or 0 if it refers to a directory */ @Deprecated @Override public int getContentLength () { try { return (int) ( length() & 0xFFFFFFFFL ); } catch ( SmbException se ) { log.debug("getContentLength", se); } return 0; } /** * {@inheritDoc} * * @see java.net.URLConnection#getContentLengthLong() */ @Override public long getContentLengthLong () { try { return length(); } catch ( SmbException se ) { log.debug("getContentLength", se); } return 0; } /** * This URLConnection method just returns the result of lastModified. * * @return the last modified data as milliseconds since Jan 1, 1970 */ @Override public long getDate () { try { return lastModified(); } catch ( SmbException se ) { log.debug("getDate", se); } return 0L; } /** * This URLConnection method just returns the result of lastModified. * * @return the last modified data as milliseconds since Jan 1, 1970 */ @Override public long getLastModified () { try { return lastModified(); } catch ( SmbException se ) { log.debug("getLastModified", se); } return 0L; } /** * This URLConnection method just returns a new SmbFileInputStream created with this file. * * @throws IOException * thrown by SmbFileInputStream constructor */ @Override public InputStream getInputStream () throws IOException { return new SmbFileInputStream(this); } /** * This URLConnection method just returns a new SmbFileOutputStream created with this file. * * @throws IOException * thrown by SmbFileOutputStream constructor */ @Override public OutputStream getOutputStream () throws IOException { return new SmbFileOutputStream(this); } private void processAces ( ACE[] aces, boolean resolveSids ) throws IOException { String server = getServerWithDfs(); int ai; if ( resolveSids ) { SID[] sids = new SID[aces.length]; for ( ai = 0; ai < aces.length; ai++ ) { sids[ ai ] = aces[ ai ].sid; } for ( int off = 0; off < sids.length; off += 64 ) { int len = sids.length - off; if ( len > 64 ) len = 64; getTransportContext().getSIDResolver().resolveSids(getTransportContext(), server, sids, off, len); } } else { for ( ai = 0; ai < aces.length; ai++ ) { aces[ ai ].sid.origin_server = server; aces[ ai ].sid.origin_ctx = getTransportContext(); } } } /** * Get the file index * * @return server side file index, 0 if unavailable * @throws SmbException */ public long fileIndex () throws SmbException { return 0; } /** * Return an array of Access Control Entry (ACE) objects representing * the security descriptor associated with this file or directory. * If no DACL is present, null is returned. If the DACL is empty, an array with 0 elements is returned. * * @param resolveSids * Attempt to resolve the SIDs within each ACE form * their numeric representation to their corresponding account names. * @return array of ACEs * @throws IOException */ public ACE[] getSecurity ( boolean resolveSids ) throws IOException { connect0(); if ( !this.getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS"); } int f; ACE[] aces; f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0); /* * NtTrans Query Security Desc Request / Response */ NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(getSession().getConfig(), f, 0x04); NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse(getSession().getConfig()); try { send(request, response); } finally { close(f, 0L); } aces = response.securityDescriptor.aces; if ( aces != null ) processAces(aces, resolveSids); return aces; } /** * Return the resolved owner user SID for this file or directory * * @return the owner user SID, null if not present * @throws IOException */ public SID getOwnerUser () throws IOException { return getOwnerUser(true); } /** * Return the owner user SID for this file or directory * * @param resolve * whether to resolve the user name * @return the owner user SID, null if not present * @throws IOException */ public SID getOwnerUser ( boolean resolve ) throws IOException { connect0(); if ( !this.getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS"); } NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse(getTransportContext().getConfig()); int f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0); try { /* * NtTrans Query Security Desc Request / Response */ NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(getTransportContext().getConfig(), f, 0x01); send(request, response); } finally { close(f, 0L); } SID ownerUser = response.securityDescriptor.owner_user; if ( ownerUser == null ) { return null; } ownerUser.resolve(getServerWithDfs(), getTransportContext()); return ownerUser; } /** * Return the resolved owner group SID for this file or directory * * @return the owner group SID, null if not present * @throws IOException */ public SID getOwnerGroup () throws IOException { return getOwnerGroup(true); } /** * Return the owner group SID for this file or directory * * @param resolve * whether to resolve the group name * @return the owner group SID, null if not present * @throws IOException */ public SID getOwnerGroup ( boolean resolve ) throws IOException { connect0(); if ( !this.getSession().getTransport().hasCapability(SmbConstants.CAP_NT_SMBS) ) { throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS"); } int f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0); NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse(getTransportContext().getConfig()); try { /* * NtTrans Query Security Desc Request / Response */ NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(getTransportContext().getConfig(), f, 0x02); send(request, response); } finally { close(f, 0L); } SID ownerGroup = response.securityDescriptor.owner_group; if ( ownerGroup == null ) { return null; } ownerGroup.resolve(getServerWithDfs(), getTransportContext()); return ownerGroup; } /** * Return an array of Access Control Entry (ACE) objects representing * the share permissions on the share exporting this file or directory. * If no DACL is present, null is returned. If the DACL is empty, an array with 0 elements is returned. *

* Note that this is different from calling getSecurity on a * share. There are actually two different ACLs for shares - the ACL on * the share and the ACL on the folder being shared. * Go to Computer Management * > System Tools > Shared Folders > Shares and * look at the Properties for a share. You will see two tabs - one * for "Share Permissions" and another for "Security". These correspond to * the ACLs returned by getShareSecurity and getSecurity * respectively. * * @param resolveSids * Attempt to resolve the SIDs within each ACE form * their numeric representation to their corresponding account names. * @return array of ACEs * @throws IOException */ public ACE[] getShareSecurity ( boolean resolveSids ) throws IOException { MsrpcShareGetInfo rpc; ACE[] aces; resolveDfs(null); String server = getServerWithDfs(); rpc = new MsrpcShareGetInfo(server, this.tree.share); try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + server + "[\\PIPE\\srvsvc]", getTransportContext()) ) { handle.sendrecv(rpc); if ( rpc.retval != 0 ) throw new SmbException(rpc.retval, true); aces = rpc.getSecurity(); if ( aces != null ) processAces(aces, resolveSids); } return aces; } /** * Return an array of Access Control Entry (ACE) objects representing * the security descriptor associated with this file or directory. *

* Initially, the SIDs within each ACE will not be resolved however when * getType(), getDomainName(), getAccountName(), * or toString() is called, the names will attempt to be * resolved. If the names cannot be resolved (e.g. due to temporary * network failure), the said methods will return default values (usually * S-X-Y-Z strings of fragments of). *

* Alternatively getSecurity(true) may be used to resolve all * SIDs together and detect network failures. * * @return array of ACEs * @throws IOException */ public ACE[] getSecurity () throws IOException { return getSecurity(false); } }