1   package com.trendmicro.grid.acl.ds.cifs;
2   
3   import jcifs.smb.SmbException;
4   import jcifs.smb.SmbFile;
5   import jcifs.smb.SmbFileInputStream;
6   import jcifs.smb.SmbFileOutputStream;
7   import jcifs.util.transport.TransportException;
8   import net.sf.tinyjee.config.Connection;
9   import net.sf.tinyjee.config.PropertySection;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  import org.springframework.stereotype.Repository;
13  
14  import java.io.*;
15  import java.net.MalformedURLException;
16  import java.net.UnknownHostException;
17  import java.security.NoSuchAlgorithmException;
18  import java.util.Collection;
19  import java.util.Date;
20  
21  import static com.trendmicro.grid.acl.ds.cifs.CIFSPropertySection.*;
22  import static jcifs.smb.NtStatus.NT_STATUS_NO_SUCH_FILE;
23  import static jcifs.smb.NtStatus.NT_STATUS_OBJECT_PATH_NOT_FOUND;
24  
25  /**
26   * Implements the FileRepository API using a CIFS / Samba backend.
27   *
28   * @author juergen_kellerer, 2010-05-05
29   * @version 1.0
30   */
31  @Repository
32  public class CIFSFileRepository extends AbstractStructuredFileRepository {
33  
34  	private static final Logger log = LoggerFactory.getLogger(CIFSFileRepository.class);
35  
36  	/**
37  	 * Is the NT status code when a rename fails as 2 paths are on different devices.
38  	 */
39  	public static final int NT_NOT_SAME_DEVICE = 0xC00000D4;
40  
41  	private CIFSLoginHandler loginHandler;
42  
43  	public CIFSFileRepository() {
44  	}
45  
46  	public CIFSFileRepository(String hostname, String pathPrefix, String domain, String username, String password) {
47  		Collection<Connection> connections = getCIFSConnections();
48  		connections.clear();
49  		connections.add(createCIFSConnection(hostname, pathPrefix, domain, username, password));
50  	}
51  
52  
53  	/**
54  	 * {@inheritDoc}
55  	 */
56  	@Override
57  	protected void initialize(PropertySection section) throws NoSuchAlgorithmException {
58  		super.initialize(section);
59  
60  		for (Connection connection : getCIFSConnections()) {
61  
62  			final String hostname = connection.getAddressValue();
63  			String pathPrefix = connection.getPropertyValue(KEY_CONNECTION_PATH_PREFIX, "").replace('\\', '/');
64  			if (!pathPrefix.isEmpty() && !pathPrefix.endsWith("/"))
65  				pathPrefix = pathPrefix.concat("/");
66  
67  
68  			if (hostname == null || hostname.isEmpty()) {
69  				log.error("TMACL-01410:No cifs host was defined, CIFS file repository will be unavailable.");
70  				continue;
71  			}
72  
73  			loginHandler = new CIFSLoginHandler(hostname, pathPrefix);
74  
75  			String domain = connection.getPropertyValue(KEY_CONNECTION_DOMAIN, null);
76  			String username = connection.getPropertyValue(KEY_CONNECTION_USERNAME, null);
77  			String password = connection.getPropertyValue(KEY_CONNECTION_PASSWORD, "");
78  			loginHandler.loginAsynchronously(domain, username, password);
79  
80  			break;
81  		}
82  	}
83  
84  	/**
85  	 * Fixes the bad exception design the the JCIFS library that prevents a full log output when exceptions occure.
86  	 *
87  	 * @param e the exception to fix.
88  	 */
89  	static void fixExceptionChain(Throwable e) {
90  		if (e == null)
91  			return;
92  
93  		if (e instanceof SmbException) {
94  			SmbException se = (SmbException) e;
95  			if (se.getRootCause() != null && se.getCause() == null)
96  				se.initCause(se.getRootCause());
97  		} else if (e instanceof TransportException) {
98  			TransportException te = (TransportException) e;
99  			if (te.getRootCause() != null && te.getCause() == null)
100 				te.initCause(te.getRootCause());
101 		}
102 
103 		fixExceptionChain(e.getCause());
104 	}
105 
106 	private static FileNotFoundException createFileNotFound(Throwable cause) throws FileNotFoundException {
107 		fixExceptionChain(cause);
108 		FileNotFoundException e = new FileNotFoundException(cause.getMessage());
109 		e.initCause(cause);
110 		return e;
111 	}
112 
113 	/**
114 	 * {@inheritDoc}
115 	 */
116 	@Override
117 	protected String getBaseURI(String baseURI) {
118 		try {
119 			return baseURI.isEmpty() ? loginHandler.getAuthenticatedRepositoryBaseURI() : baseURI;
120 		} catch (Exception e) {
121 			throw new RuntimeException(e);
122 		}
123 	}
124 
125 	private SmbFile toTargetFile(String path, boolean createPathIfMissing) throws FileNotFoundException {
126 		try {
127 			final SmbFile basePath = loginHandler.getAuthenticatedRepositoryBasePath();
128 			final SmbFile file = new SmbFile(basePath, path);
129 
130 			if (createPathIfMissing) {
131 				String parent = file.getParent();
132 				SmbFile parentFile = new SmbFile(basePath, parent);
133 				if (!parentFile.isDirectory()) {
134 					if (log.isTraceEnabled())
135 						log.trace("Creating directory on CIFS host with address '{}'", parentFile.getURL());
136 					parentFile.mkdirs();
137 				}
138 			}
139 
140 			return file;
141 
142 		} catch (MalformedURLException e) {
143 			throw createFileNotFound(e);
144 		} catch (UnknownHostException e) {
145 			loginHandler.forceReLogin();
146 			throw createFileNotFound(e);
147 		} catch (SmbException e) {
148 			loginHandler.forceReLogin();
149 			throw createFileNotFound(e);
150 		}
151 	}
152 
153 	/**
154 	 * {@inheritDoc}
155 	 */
156 	@Override
157 	protected boolean isExisting(String path) {
158 		try {
159 			// !! don't use "exists()" it spawns lot of "Query Threads" !!
160 			return !"".equals(path) && toTargetFile(path, false).isFile();
161 		} catch (Exception e) {
162 			return false;
163 		}
164 	}
165 
166 	/**
167 	 * {@inheritDoc}
168 	 */
169 	@Override
170 	protected Date getLastModified(String path) {
171 		try {
172 			if (path.equals(""))
173 				return null;
174 			long lm = toTargetFile(path, false).lastModified();
175 			return lm == 0L ? null : new Date(lm);
176 		} catch (Exception e) {
177 			return null;
178 		}
179 	}
180 
181 	/**
182 	 * {@inheritDoc}
183 	 */
184 	@Override
185 	protected OutputStream openForWrite(String path)
186 			throws IllegalStateException, FileNotFoundException {
187 		try {
188 			return new BufferedOutputStream(new SmbFileOutputStream(toTargetFile(path, true)));
189 		} catch (MalformedURLException e) {
190 			log.error("TMACL-01230:Can't open '" + path + "' for writing, the path is invalid.", e);
191 			throw createFileNotFound(e);
192 		} catch (UnknownHostException e) {
193 			throw createFileNotFound(e);
194 		} catch (SmbException e) {
195 			log.error("TMACL-01240:Can't open '" + path + "' for writing.", e);
196 			throw createFileNotFound(e);
197 		}
198 	}
199 
200 	/**
201 	 * {@inheritDoc}
202 	 */
203 	@Override
204 	protected InputStream openForRead(String path) throws FileNotFoundException {
205 		try {
206 			return new BufferedInputStream(new SmbFileInputStream(toTargetFile(path, false)));
207 		} catch (MalformedURLException e) {
208 			log.error("TMACL-01250:Can't open '" + path + "' for reading, the path is invalid.", e);
209 			throw createFileNotFound(e);
210 		} catch (UnknownHostException e) {
211 			throw createFileNotFound(e);
212 		} catch (SmbException e) {
213 			if (e.getNtStatus() == NT_STATUS_NO_SUCH_FILE || e.getNtStatus() == NT_STATUS_OBJECT_PATH_NOT_FOUND) {
214 				log.error("TMACL-01261:Can't open '" + path + "' for reading, the file does not exist.");
215 				throw new FileNotFoundException(e.getMessage());
216 			} else
217 				log.error("TMACL-01260:Can't open '" + path + "' for reading.", e);
218 			throw createFileNotFound(e);
219 		}
220 	}
221 
222 	/**
223 	 * {@inheritDoc}
224 	 */
225 	@Override
226 	protected void rename(String fromPath, String toPath) throws FileNotFoundException {
227 		SmbFile from = toTargetFile(fromPath, false);
228 		try {
229 			if (from.isFile()) {
230 				SmbFile to = toTargetFile(toPath, true);
231 				try {
232 					from.renameTo(to);
233 				} catch (SmbException e) {
234 					if (e.getNtStatus() == NT_NOT_SAME_DEVICE) {
235 						if (log.isDebugEnabled())
236 							log.debug("Failed renaming file '{}' to '{}', falling back to file copy.", from, to);
237 						from.copyTo(to);
238 						if (log.isDebugEnabled())
239 							log.debug("Successfully copied file '{}' to '{}', deleting the source now.", from, to);
240 						from.delete();
241 					} else
242 						throw e;
243 				}
244 			} else
245 				throw new FileNotFoundException(from + " is not a file or does not exist.");
246 		} catch (FileNotFoundException e) {
247 			log.error("TMACL-01270:Can't rename '" + fromPath + "' to '" + toPath + "'.", e);
248 			throw e;
249 		} catch (SmbException e) {
250 			log.error("TMACL-01280:Can't rename '" + fromPath + "' to '" + toPath + "'.", e);
251 			throw createFileNotFound(e);
252 		}
253 	}
254 }