1
2
3
4
5
6
7 package org.mule.transport.sftp;
8
9 import org.mule.api.endpoint.ImmutableEndpoint;
10 import org.mule.transport.sftp.notification.SftpNotifier;
11
12 import com.jcraft.jsch.Channel;
13 import com.jcraft.jsch.ChannelSftp;
14 import com.jcraft.jsch.ChannelSftp.LsEntry;
15 import com.jcraft.jsch.JSch;
16 import com.jcraft.jsch.JSchException;
17 import com.jcraft.jsch.Session;
18 import com.jcraft.jsch.SftpATTRS;
19 import com.jcraft.jsch.SftpException;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Properties;
27 import java.util.Vector;
28
29 import org.apache.commons.lang.NotImplementedException;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_DELETE_ACTION;
34 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_GET_ACTION;
35 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_PUT_ACTION;
36 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_RENAME_ACTION;
37
38
39
40
41
42
43 public class SftpClient
44 {
45 private Log logger = LogFactory.getLog(getClass());
46
47 public static final String CHANNEL_SFTP = "sftp";
48
49 public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
50
51 private ChannelSftp channelSftp;
52
53 private JSch jsch;
54 private SftpNotifier notifier;
55
56 private Session session;
57
58 private final String host;
59
60 private int port = 22;
61
62 private String home;
63
64
65 private String currentDirectory = "";
66
67 private static final Object lock = new Object();
68
69 public SftpClient(String host)
70 {
71 this(host, null);
72 }
73
74 public SftpClient(String host, SftpNotifier notifier)
75 {
76 this.host = host;
77 this.notifier = notifier;
78
79 jsch = new JSch();
80 }
81
82 public void changeWorkingDirectory(String wd) throws IOException
83 {
84 currentDirectory = wd;
85
86 try
87 {
88 wd = getAbsolutePath(wd);
89 if (logger.isDebugEnabled())
90 {
91 logger.debug("Attempting to cwd to: " + wd);
92 }
93 channelSftp.cd(wd);
94 }
95 catch (SftpException e)
96 {
97 String message = "Error '" + e.getMessage() + "' occurred when trying to CDW to '" + wd + "'.";
98 logger.error(message);
99 throw new IOException(message);
100 }
101 }
102
103
104
105
106
107
108
109
110 public String getAbsolutePath(String path)
111 {
112 if (path.startsWith("/~"))
113 {
114 return home + path.substring(2, path.length());
115 }
116
117
118 return path;
119 }
120
121 public void login(String user, String password) throws IOException
122 {
123 try
124 {
125 Properties hash = new Properties();
126 hash.put(STRICT_HOST_KEY_CHECKING, "no");
127
128 session = jsch.getSession(user, host);
129 session.setConfig(hash);
130 session.setPort(port);
131 session.setPassword(password);
132 session.connect();
133
134 Channel channel = session.openChannel(CHANNEL_SFTP);
135 channel.connect();
136
137 channelSftp = (ChannelSftp) channel;
138 setHome(channelSftp.pwd());
139 }
140 catch (JSchException e)
141 {
142 logAndThrowLoginError(user, e);
143 }
144 catch (SftpException e)
145 {
146 logAndThrowLoginError(user, e);
147 }
148 }
149
150 public void login(String user, String identityFile, String passphrase) throws IOException
151 {
152
153 if (!new File(identityFile).exists())
154 {
155 throw new IOException("IdentityFile '" + identityFile + "' not found");
156 }
157
158 try
159 {
160 if (passphrase == null || "".equals(passphrase))
161 {
162 jsch.addIdentity(new File(identityFile).getAbsolutePath());
163 }
164 else
165 {
166 jsch.addIdentity(new File(identityFile).getAbsolutePath(), passphrase);
167 }
168
169 Properties hash = new Properties();
170 hash.put(STRICT_HOST_KEY_CHECKING, "no");
171
172 session = jsch.getSession(user, host);
173 session.setConfig(hash);
174 session.setPort(port);
175 session.connect();
176
177 Channel channel = session.openChannel(CHANNEL_SFTP);
178 channel.connect();
179
180 channelSftp = (ChannelSftp) channel;
181 setHome(channelSftp.pwd());
182 }
183 catch (JSchException e)
184 {
185 logAndThrowLoginError(user, e);
186 }
187 catch (SftpException e)
188 {
189 logAndThrowLoginError(user, e);
190 }
191 }
192
193 private void logAndThrowLoginError(String user, Exception e) throws IOException
194 {
195 logger.error("Error during login to " + user + "@" + host, e);
196 throw new IOException("Error during login to " + user + "@" + host + ": " + e.getMessage());
197 }
198
199 public void setPort(int port)
200 {
201 this.port = port;
202 }
203
204 public void rename(String filename, String dest) throws IOException
205 {
206
207 if (notifier != null)
208 {
209 notifier.notify(SFTP_RENAME_ACTION, "from: " + currentDirectory + "/" + filename + " - to: "
210 + dest);
211 }
212
213 String absolutePath = getAbsolutePath(dest);
214 try
215 {
216 if (logger.isDebugEnabled())
217 {
218 logger.debug("Will try to rename " + currentDirectory + "/" + filename + " to "
219 + absolutePath);
220 }
221 channelSftp.rename(filename, absolutePath);
222 }
223 catch (SftpException e)
224 {
225 throw new IOException(e.getMessage());
226
227
228
229 }
230 }
231
232 public void deleteFile(String fileName) throws IOException
233 {
234
235 if (notifier != null)
236 {
237 notifier.notify(SFTP_DELETE_ACTION, currentDirectory + "/" + fileName);
238 }
239
240 try
241 {
242 if (logger.isDebugEnabled())
243 {
244 logger.debug("Will try to delete " + fileName);
245 }
246 channelSftp.rm(fileName);
247 }
248 catch (SftpException e)
249 {
250 throw new IOException(e.getMessage());
251 }
252 }
253
254 public void disconnect()
255 {
256 if (channelSftp != null)
257 {
258 channelSftp.disconnect();
259 }
260 if ((session != null) && session.isConnected())
261 {
262 session.disconnect();
263 }
264 }
265
266 public boolean isConnected()
267 {
268 return (channelSftp != null) && channelSftp.isConnected() && !channelSftp.isClosed()
269 && (session != null) && session.isConnected();
270 }
271
272 public String[] listFiles() throws IOException
273 {
274 return listFiles(".");
275 }
276
277 public String[] listFiles(String path) throws IOException
278 {
279 return listDirectory(path, true, false);
280 }
281
282 public String[] listDirectories() throws IOException
283 {
284 return listDirectory(".", false, true);
285 }
286
287 public String[] listDirectories(String path) throws IOException
288 {
289 return listDirectory(path, false, true);
290 }
291
292 private String[] listDirectory(String path, boolean includeFiles, boolean includeDirectories)
293 throws IOException
294 {
295 try
296 {
297 Vector vv = channelSftp.ls(path);
298 if (vv != null)
299 {
300 List<String> ret = new ArrayList<String>();
301 for (int i = 0; i < vv.size(); i++)
302 {
303 Object obj = vv.elementAt(i);
304 if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry)
305 {
306 LsEntry entry = (LsEntry) obj;
307 if (includeFiles && !entry.getAttrs().isDir())
308 {
309 ret.add(entry.getFilename());
310 }
311 if (includeDirectories && entry.getAttrs().isDir())
312 {
313 if (!entry.getFilename().equals(".") && !entry.getFilename().equals(".."))
314 {
315 ret.add(entry.getFilename());
316 }
317 }
318 }
319 }
320 return ret.toArray(new String[ret.size()]);
321 }
322 }
323 catch (SftpException e)
324 {
325 throw new IOException(e.getMessage());
326 }
327 return null;
328 }
329
330
331
332
333
334
335 public InputStream retrieveFile(String fileName) throws IOException
336 {
337
338 long size = getSize(fileName);
339 if (notifier != null)
340 {
341 notifier.notify(SFTP_GET_ACTION, currentDirectory + "/" + fileName, size);
342 }
343
344 try
345 {
346 return channelSftp.get(fileName);
347 }
348 catch (SftpException e)
349 {
350 throw new IOException(e.getMessage() + ". Filename is " + fileName);
351 }
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365 public void storeFile(String fileName, InputStream stream) throws IOException
366 {
367 try
368 {
369
370
371 if (notifier != null)
372 {
373 notifier.notify(SFTP_PUT_ACTION, currentDirectory + "/" + fileName);
374 }
375
376 if (logger.isDebugEnabled())
377 {
378 logger.debug("Sending to SFTP service: Stream = " + stream + " , filename = " + fileName);
379 }
380
381 channelSftp.put(stream, fileName);
382 }
383 catch (SftpException e)
384 {
385 logger.error("Error writing data over SFTP service, error was: " + e.getMessage(), e);
386 throw new IOException(e.getMessage());
387 }
388 }
389
390 public void storeFile(String fileNameLocal, String fileNameRemote) throws IOException
391 {
392 try
393 {
394 channelSftp.put(fileNameLocal, fileNameRemote);
395 }
396 catch (SftpException e)
397 {
398 throw new IOException(e.getMessage());
399 }
400 }
401
402 public long getSize(String filename) throws IOException
403 {
404 try
405 {
406 return channelSftp.stat(filename).getSize();
407 }
408 catch (SftpException e)
409 {
410 throw new IOException(e.getMessage() + " (" + currentDirectory + "/" + filename + ")");
411 }
412 }
413
414
415
416
417
418
419 public long getLastModifiedTime(String filename) throws IOException
420 {
421 try
422 {
423 SftpATTRS attrs = channelSftp.stat("./" + filename);
424 return attrs.getMTime() * 1000L;
425 }
426 catch (SftpException e)
427 {
428 throw new IOException(e.getMessage());
429 }
430 }
431
432
433
434
435
436
437
438 public void mkdir(String directoryName) throws IOException
439 {
440 try
441 {
442 if (logger.isDebugEnabled())
443 {
444 logger.debug("Will try to create directory " + directoryName);
445 }
446 channelSftp.mkdir(directoryName);
447 }
448 catch (SftpException e)
449 {
450
451 throw new IOException("Could not create the directory '" + directoryName + "', caused by: "
452 + e.getMessage());
453
454
455
456 }
457 }
458
459 public void deleteDirectory(String path) throws IOException
460 {
461 path = getAbsolutePath(path);
462 try
463 {
464 if (logger.isDebugEnabled())
465 {
466 logger.debug("Will try to delete directory " + path);
467 }
468 channelSftp.rmdir(path);
469 }
470 catch (SftpException e)
471 {
472 throw new IOException(e.getMessage());
473 }
474 }
475
476
477
478
479
480
481 void setHome(String home)
482 {
483 this.home = home;
484 }
485
486
487
488
489 public ChannelSftp getChannelSftp()
490 {
491 return channelSftp;
492 }
493
494
495
496
497
498
499
500
501
502
503
504 public void createSftpDirIfNotExists(ImmutableEndpoint endpoint, String newDir) throws IOException
505 {
506 String newDirAbs = endpoint.getEndpointURI().getPath() + "/" + newDir;
507
508 String currDir = currentDirectory;
509
510 if (logger.isDebugEnabled())
511 {
512 logger.debug("CHANGE DIR FROM " + currentDirectory + " TO " + newDirAbs);
513 }
514
515
516
517 synchronized (lock)
518 {
519
520 try
521 {
522
523
524 changeWorkingDirectory(newDirAbs);
525 }
526 catch (IOException e)
527 {
528 logger.info("Got an exception when trying to change the working directory to the new dir. "
529 + "Will try to create the directory " + newDirAbs);
530 changeWorkingDirectory(endpoint.getEndpointURI().getPath());
531 mkdir(newDir);
532
533
534 changeWorkingDirectory(newDirAbs);
535 }
536 finally
537 {
538 changeWorkingDirectory(currDir);
539 if (logger.isDebugEnabled())
540 {
541 logger.debug("DIR IS NOW BACK TO " + currentDirectory);
542 }
543 }
544 }
545 }
546
547 public String duplicateHandling(String destDir, String filename, String duplicateHandling)
548 throws IOException
549 {
550 if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_ASS_SEQ_NO))
551 {
552 filename = createUniqueName(destDir, filename);
553
554 }
555 else if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE))
556 {
557
558 throw new NotImplementedException("Strategy "
559 + SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE
560 + " is not yet implemented");
561
562 }
563 else
564 {
565
566
567
568 }
569
570 return filename;
571 }
572
573 private String createUniqueName(String dir, String path) throws IOException
574 {
575 int fileIdx = 1;
576
577 String filename;
578 String fileType;
579 int fileTypeIdx = path.lastIndexOf('.');
580 if (fileTypeIdx == -1)
581 {
582
583 filename = path;
584 fileType = "";
585 }
586 else
587 {
588 fileType = path.substring(fileTypeIdx);
589
590 filename = path.substring(0, fileTypeIdx);
591 }
592
593 if (logger.isDebugEnabled())
594 {
595 logger.debug("Create a unique name for: " + path + " (" + dir + " - " + filename + " - "
596 + fileType + ")");
597 }
598
599 String uniqueFilename = filename;
600 String[] existingFiles = listFiles(getAbsolutePath(dir));
601
602 while (existsFile(existingFiles, uniqueFilename, fileType))
603 {
604 uniqueFilename = filename + '_' + fileIdx++;
605 }
606
607 uniqueFilename = uniqueFilename + fileType;
608 if (!path.equals(uniqueFilename) && logger.isInfoEnabled())
609 {
610 logger.info("A file with the original filename (" + dir + "/" + path
611 + ") already exists, new name: " + uniqueFilename);
612 }
613 if (logger.isDebugEnabled())
614 {
615 logger.debug("Unique name returned: " + uniqueFilename);
616 }
617 return uniqueFilename;
618 }
619
620 private boolean existsFile(String[] files, String filename, String fileType)
621 {
622 boolean existsFile = false;
623 filename += fileType;
624 for (String file : files)
625 {
626 if (file.equals(filename))
627 {
628 if (logger.isDebugEnabled())
629 {
630 logger.debug("Found existing file: " + file);
631 }
632 existsFile = true;
633 }
634 }
635 return existsFile;
636 }
637
638 public void chmod(String path, int permissions) throws SftpException
639 {
640 path = getAbsolutePath(path);
641 if (logger.isDebugEnabled())
642 {
643 logger.debug("Will try to chmod directory '" + path + "' to permission " + permissions);
644 }
645 channelSftp.chmod(permissions, path);
646 }
647
648 public void setNotifier(SftpNotifier notifier)
649 {
650 this.notifier = notifier;
651 }
652
653 public String getHost()
654 {
655 return host;
656 }
657 }