View Javadoc

1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util.secure;
17  
18  import java.io.BufferedOutputStream;
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.lang.reflect.InvocationTargetException;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Properties;
29  
30  import org.apache.commons.beanutils.BeanUtils;
31  import org.apache.commons.io.FileUtils;
32  import org.apache.commons.io.FilenameUtils;
33  import org.apache.commons.io.IOUtils;
34  import org.apache.commons.lang3.StringUtils;
35  import org.kuali.common.util.Assert;
36  import org.kuali.common.util.CollectionUtils;
37  import org.kuali.common.util.LocationUtils;
38  import org.kuali.common.util.PropertyUtils;
39  import org.kuali.common.util.Str;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  import com.jcraft.jsch.Channel;
44  import com.jcraft.jsch.ChannelExec;
45  import com.jcraft.jsch.ChannelSftp;
46  import com.jcraft.jsch.JSch;
47  import com.jcraft.jsch.JSchException;
48  import com.jcraft.jsch.Session;
49  import com.jcraft.jsch.SftpATTRS;
50  import com.jcraft.jsch.SftpException;
51  
52  public class DefaultSecureChannel implements SecureChannel {
53  
54  	private static final Logger logger = LoggerFactory.getLogger(DefaultSecureChannel.class);
55  	private static final String SFTP = "sftp";
56  	private static final String EXEC = "exec";
57  	private static final String FORWARDSLASH = "/";
58  	private static final int DEFAULT_SLEEP_MILLIS = 10;
59  	private static final String DEFAULT_ENCODING = "UTF-8";
60  
61  	File knownHosts = SSHUtils.DEFAULT_KNOWN_HOSTS;
62  	File config = SSHUtils.DEFAULT_CONFIG_FILE;
63  	boolean useConfigFile = true;
64  	boolean includeDefaultPrivateKeyLocations = true;
65  	boolean strictHostKeyChecking = true;
66  	int port = SSHUtils.DEFAULT_PORT;
67  	int waitForClosedSleepMillis = DEFAULT_SLEEP_MILLIS;
68  	String encoding = DEFAULT_ENCODING;
69  	String username;
70  	String hostname;
71  	Integer connectTimeout;
72  	List<File> privateKeys;
73  	List<String> privateKeyStrings;
74  	Properties options;
75  
76  	protected Session session;
77  	protected ChannelSftp sftp;
78  
79  	@Override
80  	public synchronized void open() throws IOException {
81  		logOpen();
82  		validate();
83  		try {
84  			JSch jsch = getJSch();
85  			this.session = openSession(jsch);
86  			this.sftp = openSftpChannel(session, connectTimeout);
87  		} catch (JSchException e) {
88  			throw new IOException("Unexpected error opening secure channel", e);
89  		}
90  	}
91  
92  	@Override
93  	public synchronized void close() {
94  		logger.info("Closing secure channel [{}]", ChannelUtils.getLocation(username, hostname));
95  		closeQuietly(sftp);
96  		closeQuietly(session);
97  	}
98  
99  	@Override
100 	public Result executeCommand(String command) {
101 		return executeCommand(command, null);
102 	}
103 
104 	@Override
105 	public Result executeCommand(String command, String stdin) {
106 		Assert.notBlank(command);
107 		ChannelExec exec = null;
108 		InputStream stdoutStream = null;
109 		ByteArrayOutputStream stderrStream = null;
110 		InputStream stdinStream = null;
111 		try {
112 			// Preserve start time
113 			long start = System.currentTimeMillis();
114 			// Open an exec channel
115 			exec = (ChannelExec) session.openChannel(EXEC);
116 			// Convert the command string to bytes
117 			byte[] commandBytes = Str.getBytes(command, encoding);
118 			// Store the command on the exec channel
119 			exec.setCommand(commandBytes);
120 			// Prepare the stdin stream
121 			stdinStream = getInputStream(stdin, encoding);
122 			// Prepare the stderr stream
123 			stderrStream = new ByteArrayOutputStream();
124 			// Get the stdout stream from the ChannelExec object
125 			stdoutStream = exec.getInputStream();
126 			// Update the ChannelExec object with the stdin stream
127 			exec.setInputStream(stdinStream);
128 			// Update the ChannelExec object with the stderr stream
129 			exec.setErrStream(stderrStream);
130 			// Execute the command.
131 			// This consumes anything from stdin and stores output in stdout/stderr
132 			connect(exec, null);
133 			// Convert stdout and stderr to String's
134 			String stdout = Str.getString(IOUtils.toByteArray(stdoutStream), encoding);
135 			String stderr = Str.getString(stderrStream.toByteArray(), encoding);
136 			// Make sure the channel is closed
137 			waitForClosed(exec, waitForClosedSleepMillis);
138 			// Return the result of executing the command
139 			return ChannelUtils.getExecutionResult(exec.getExitStatus(), start, command, stdin, stdout, stderr, encoding);
140 		} catch (Exception e) {
141 			throw new IllegalStateException(e);
142 		} finally {
143 			// Cleanup
144 			IOUtils.closeQuietly(stdinStream);
145 			IOUtils.closeQuietly(stdoutStream);
146 			IOUtils.closeQuietly(stderrStream);
147 			closeQuietly(exec);
148 		}
149 	}
150 
151 	@Override
152 	public void executeNoWait(String command) {
153 		Assert.notBlank(command);
154 		ChannelExec exec = null;
155 		try {
156 			// Open an exec channel
157 			exec = (ChannelExec) session.openChannel(EXEC);
158 			// Convert the command string to bytes
159 			byte[] commandBytes = Str.getBytes(command, encoding);
160 			// Store the command on the exec channel
161 			exec.setCommand(commandBytes);
162 			// Execute the command.
163 			// This consumes anything from stdin and stores output in stdout/stderr
164 			connect(exec, null);
165 		} catch (Exception e) {
166 			throw new IllegalStateException(e);
167 		} finally {
168 			closeQuietly(exec);
169 		}
170 	}
171 
172 	protected InputStream getInputStream(String s, String encoding) {
173 		if (s == null) {
174 			return null;
175 		} else {
176 			return new ByteArrayInputStream(Str.getBytes(s, encoding));
177 		}
178 	}
179 
180 	protected void waitForClosed(ChannelExec exec, long millis) {
181 		while (!exec.isClosed()) {
182 			sleep(millis);
183 		}
184 	}
185 
186 	protected void sleep(long millis) {
187 		try {
188 			Thread.sleep(millis);
189 		} catch (InterruptedException e) {
190 			throw new IllegalStateException(e);
191 		}
192 	}
193 
194 	@Override
195 	public RemoteFile getWorkingDirectory() {
196 		try {
197 			String workingDirectory = sftp.pwd();
198 			return getMetaData(workingDirectory);
199 		} catch (SftpException e) {
200 			throw new IllegalStateException(e);
201 		}
202 	}
203 
204 	protected void validate() {
205 		Assert.isTrue(SSHUtils.isValidPort(port));
206 		Assert.notBlank(hostname);
207 		Assert.notBlank(encoding);
208 	}
209 
210 	protected void logOpen() {
211 		logger.info("Opening secure channel [{}] encoding={}", ChannelUtils.getLocation(username, hostname), encoding);
212 		logger.debug("Private key files - {}", CollectionUtils.toEmptyList(privateKeys).size());
213 		logger.debug("Private key strings - {}", CollectionUtils.toEmptyList(privateKeyStrings).size());
214 		logger.debug("Private key config file - {}", config);
215 		logger.debug("Private key config file use - {}", useConfigFile);
216 		logger.debug("Include default private key locations - {}", includeDefaultPrivateKeyLocations);
217 		logger.debug("Known hosts file - {}", knownHosts);
218 		logger.debug("Port - {}", port);
219 		logger.debug("Connect timeout - {}", connectTimeout);
220 		logger.debug("Strict host key checking - {}", strictHostKeyChecking);
221 		logger.debug("Configuring channel with {} custom options", PropertyUtils.toEmpty(options).size());
222 		if (options != null) {
223 			PropertyUtils.debug(options);
224 		}
225 	}
226 
227 	protected ChannelSftp openSftpChannel(Session session, Integer timeout) throws JSchException {
228 		ChannelSftp sftp = (ChannelSftp) session.openChannel(SFTP);
229 		connect(sftp, timeout);
230 		return sftp;
231 	}
232 
233 	protected void connect(Channel channel, Integer timeout) throws JSchException {
234 		if (timeout == null) {
235 			channel.connect();
236 		} else {
237 			channel.connect(timeout);
238 		}
239 	}
240 
241 	protected void closeQuietly(Session session) {
242 		if (session != null) {
243 			session.disconnect();
244 		}
245 	}
246 
247 	protected void closeQuietly(Channel channel) {
248 		if (channel != null) {
249 			channel.disconnect();
250 		}
251 	}
252 
253 	protected Properties getSessionProperties(Properties options, boolean strictHostKeyChecking) {
254 		Properties properties = new Properties();
255 		if (options != null) {
256 			properties.putAll(options);
257 		}
258 		if (!strictHostKeyChecking) {
259 			properties.setProperty(SSHUtils.STRICT_HOST_KEY_CHECKING, SSHUtils.NO);
260 		}
261 		return properties;
262 	}
263 
264 	protected Session openSession(JSch jsch) throws JSchException {
265 		Session session = jsch.getSession(username, hostname, port);
266 		session.setConfig(getSessionProperties(options, strictHostKeyChecking));
267 		if (connectTimeout == null) {
268 			session.connect();
269 		} else {
270 			session.connect(connectTimeout);
271 		}
272 		return session;
273 	}
274 
275 	protected JSch getJSch() throws JSchException {
276 		List<File> uniquePrivateKeyFiles = getUniquePrivateKeyFiles();
277 		logger.debug("Located {} private keys on the file system", uniquePrivateKeyFiles.size());
278 		JSch jsch = getJSch(uniquePrivateKeyFiles, privateKeyStrings);
279 		if (strictHostKeyChecking && knownHosts != null) {
280 			String path = LocationUtils.getCanonicalPath(knownHosts);
281 			jsch.setKnownHosts(path);
282 		}
283 		return jsch;
284 	}
285 
286 	protected JSch getJSch(List<File> privateKeys, List<String> privateKeyStrings) throws JSchException {
287 		JSch jsch = new JSch();
288 		for (File privateKey : privateKeys) {
289 			String path = LocationUtils.getCanonicalPath(privateKey);
290 			jsch.addIdentity(path);
291 		}
292 		int count = 0;
293 		for (String privateKeyString : CollectionUtils.toEmptyList(privateKeyStrings)) {
294 			String name = "privateKeyString-" + Integer.toString(count++);
295 			byte[] bytes = Str.getBytes(privateKeyString, encoding);
296 			jsch.addIdentity(name, bytes, null, null);
297 		}
298 		return jsch;
299 	}
300 
301 	protected List<File> getUniquePrivateKeyFiles() {
302 		List<String> paths = new ArrayList<String>();
303 		if (privateKeys != null) {
304 			for (File privateKey : privateKeys) {
305 				paths.add(LocationUtils.getCanonicalPath(privateKey));
306 			}
307 		}
308 		if (useConfigFile) {
309 			for (String path : SSHUtils.getFilenames(config)) {
310 				paths.add(path);
311 			}
312 		}
313 		if (includeDefaultPrivateKeyLocations) {
314 			for (String path : SSHUtils.PRIVATE_KEY_DEFAULTS) {
315 				paths.add(path);
316 			}
317 		}
318 		List<String> uniquePaths = CollectionUtils.getUniqueStrings(paths);
319 		return SSHUtils.getExistingAndReadable(uniquePaths);
320 	}
321 
322 	@Override
323 	public RemoteFile getMetaData(String absolutePath) {
324 		Assert.hasLength(absolutePath);
325 		RemoteFile file = new RemoteFile();
326 		file.setAbsolutePath(absolutePath);
327 		fillInAttributes(file, absolutePath);
328 		return file;
329 	}
330 
331 	@Override
332 	public void deleteFile(String absolutePath) {
333 		RemoteFile file = getMetaData(absolutePath);
334 		if (isStatus(file, Status.MISSING)) {
335 			return;
336 		}
337 		if (file.isDirectory()) {
338 			throw new IllegalArgumentException("[" + ChannelUtils.getLocation(username, hostname, file) + "] is a directory.");
339 		}
340 		try {
341 			sftp.rm(absolutePath);
342 		} catch (SftpException e) {
343 			throw new IllegalStateException(e);
344 		}
345 	}
346 
347 	@Override
348 	public boolean exists(String absolutePath) {
349 		RemoteFile file = getMetaData(absolutePath);
350 		return isStatus(file, Status.EXISTS);
351 	}
352 
353 	@Override
354 	public boolean isDirectory(String absolutePath) {
355 		RemoteFile file = getMetaData(absolutePath);
356 		return isStatus(file, Status.EXISTS) && file.isDirectory();
357 	}
358 
359 	protected void fillInAttributes(RemoteFile file) {
360 		fillInAttributes(file, file.getAbsolutePath());
361 	}
362 
363 	protected void fillInAttributes(RemoteFile file, String path) {
364 		try {
365 			SftpATTRS attributes = sftp.stat(path);
366 			fillInAttributes(file, attributes);
367 		} catch (SftpException e) {
368 			handleNoSuchFileException(file, e);
369 		}
370 	}
371 
372 	protected void fillInAttributes(RemoteFile file, SftpATTRS attributes) {
373 		file.setDirectory(attributes.isDir());
374 		file.setPermissions(attributes.getPermissions());
375 		file.setUserId(attributes.getUId());
376 		file.setGroupId(attributes.getGId());
377 		file.setSize(attributes.getSize());
378 		file.setStatus(Status.EXISTS);
379 	}
380 
381 	@Override
382 	public void copyFile(File source, RemoteFile destination) {
383 		Assert.notNull(source);
384 		Assert.isTrue(source.exists());
385 		Assert.isTrue(!source.isDirectory());
386 		Assert.isTrue(source.canRead());
387 		copyLocationToFile(LocationUtils.getCanonicalURLString(source), destination);
388 	}
389 
390 	@Override
391 	public void copyFileToDirectory(File source, RemoteFile destination) {
392 		RemoteFile clone = clone(destination);
393 		String filename = source.getName();
394 		addFilenameToPath(clone, filename);
395 		copyFile(source, clone);
396 	}
397 
398 	protected RemoteFile clone(RemoteFile file) {
399 		try {
400 			RemoteFile clone = new RemoteFile();
401 			BeanUtils.copyProperties(clone, file);
402 			return clone;
403 		} catch (IllegalAccessException e) {
404 			throw new IllegalStateException(e);
405 		} catch (InvocationTargetException e) {
406 			throw new IllegalStateException(e);
407 		}
408 	}
409 
410 	@Override
411 	public void copyLocationToFile(String location, RemoteFile destination) {
412 		Assert.notNull(location);
413 		Assert.isTrue(LocationUtils.exists(location), location + " does not exist");
414 		InputStream in = null;
415 		try {
416 			in = LocationUtils.getInputStream(location);
417 			copyInputStreamToFile(in, destination);
418 		} catch (Exception e) {
419 			throw new IllegalStateException(e);
420 		} finally {
421 			IOUtils.closeQuietly(in);
422 		}
423 	}
424 
425 	@Override
426 	public void copyStringToFile(String string, RemoteFile destination) {
427 		Assert.notNull(string);
428 		Assert.notBlank(encoding);
429 		InputStream in = new ByteArrayInputStream(Str.getBytes(string, encoding));
430 		copyInputStreamToFile(in, destination);
431 		IOUtils.closeQuietly(in);
432 	}
433 
434 	@Override
435 	public String toString(RemoteFile source) {
436 		Assert.notNull(source);
437 		Assert.hasText(source.getAbsolutePath());
438 		Assert.notBlank(encoding);
439 		ByteArrayOutputStream out = new ByteArrayOutputStream();
440 		try {
441 			copyFile(source, out);
442 			return out.toString(encoding);
443 		} catch (IOException e) {
444 			throw new IllegalStateException("Unexpected IO error", e);
445 		} finally {
446 			IOUtils.closeQuietly(out);
447 		}
448 	}
449 
450 	@Override
451 	public void copyInputStreamToFile(InputStream source, RemoteFile destination) {
452 		Assert.notNull(source);
453 		try {
454 			createDirectories(destination);
455 			sftp.put(source, destination.getAbsolutePath());
456 		} catch (SftpException e) {
457 			throw new IllegalStateException(e);
458 		}
459 	}
460 
461 	protected String getAbsolutePath(String absolutePath, String filename) {
462 		if (StringUtils.endsWith(absolutePath, FORWARDSLASH)) {
463 			return absolutePath + filename;
464 		} else {
465 			return absolutePath + FORWARDSLASH + filename;
466 		}
467 	}
468 
469 	protected void addFilenameToPath(RemoteFile destination, String filename) {
470 		String newAbsolutePath = getAbsolutePath(destination.getAbsolutePath(), filename);
471 		destination.setAbsolutePath(newAbsolutePath);
472 		destination.setDirectory(false);
473 	}
474 
475 	@Override
476 	public void copyLocationToDirectory(String location, RemoteFile destination) {
477 		RemoteFile clone = clone(destination);
478 		String filename = LocationUtils.getFilename(location);
479 		addFilenameToPath(clone, filename);
480 		copyLocationToFile(location, clone);
481 	}
482 
483 	@Override
484 	public void copyFile(RemoteFile source, File destination) {
485 		OutputStream out = null;
486 		try {
487 			out = new BufferedOutputStream(FileUtils.openOutputStream(destination));
488 			copyFile(source, out);
489 		} catch (Exception e) {
490 			throw new IllegalStateException(e);
491 		} finally {
492 			IOUtils.closeQuietly(out);
493 		}
494 	}
495 
496 	@Override
497 	public void copyRemoteFile(String absolutePath, OutputStream out) throws IOException {
498 		try {
499 			sftp.get(absolutePath, out);
500 		} catch (SftpException e) {
501 			throw new IOException("Unexpected IO error", e);
502 		}
503 	}
504 
505 	@Override
506 	public void copyFile(RemoteFile source, OutputStream out) throws IOException {
507 		copyRemoteFile(source.getAbsolutePath(), out);
508 	}
509 
510 	@Override
511 	public void copyFileToDirectory(RemoteFile source, File destination) {
512 		String filename = FilenameUtils.getName(source.getAbsolutePath());
513 		File newDestination = new File(destination, filename);
514 		copyFile(source, newDestination);
515 	}
516 
517 	@Override
518 	public void createDirectory(RemoteFile dir) {
519 		Assert.isTrue(dir.isDirectory());
520 		try {
521 			createDirectories(dir);
522 		} catch (SftpException e) {
523 			throw new IllegalStateException(e);
524 		}
525 	}
526 
527 	protected void createDirectories(RemoteFile file) throws SftpException {
528 		boolean directoryIndicator = file.isDirectory();
529 		fillInAttributes(file);
530 		validate(file, directoryIndicator);
531 		List<String> directories = LocationUtils.getNormalizedPathFragments(file.getAbsolutePath(), file.isDirectory());
532 		for (String directory : directories) {
533 			RemoteFile parentDir = new RemoteFile(directory);
534 			fillInAttributes(parentDir);
535 			validate(parentDir, true);
536 			if (!isStatus(parentDir, Status.EXISTS)) {
537 				mkdir(parentDir);
538 			}
539 		}
540 	}
541 
542 	protected boolean isStatus(RemoteFile file, Status status) {
543 		return file.getStatus().equals(status);
544 	}
545 
546 	protected void validate(RemoteFile file, Status... allowed) {
547 		for (Status status : allowed) {
548 			if (isStatus(file, status)) {
549 				return;
550 			}
551 		}
552 		throw new IllegalArgumentException("Invalid status - " + file.getStatus());
553 	}
554 
555 	protected boolean validate(RemoteFile file, boolean directoryIndicator) {
556 		// Make sure file is not in UNKNOWN status
557 		validate(file, Status.MISSING, Status.EXISTS);
558 
559 		// Convenience flags
560 		boolean missing = isStatus(file, Status.MISSING);
561 		boolean exists = isStatus(file, Status.EXISTS);
562 
563 		// Compare the actual file type to the file type it needs to be
564 		boolean correctFileType = file.isDirectory() == directoryIndicator;
565 
566 		// Is everything as it should be?
567 		boolean valid = missing || exists && correctFileType;
568 		if (valid) {
569 			return true;
570 		} else {
571 			// Something has gone awry
572 			throw new IllegalArgumentException(getInvalidExistingFileMessage(file));
573 		}
574 	}
575 
576 	protected String getInvalidExistingFileMessage(RemoteFile existing) {
577 		if (existing.isDirectory()) {
578 			return "[" + ChannelUtils.getLocation(username, hostname, existing) + "] is an existing directory. Unable to create file.";
579 		} else {
580 			return "[" + ChannelUtils.getLocation(username, hostname, existing) + "] is an existing file. Unable to create directory.";
581 		}
582 	}
583 
584 	protected void mkdir(RemoteFile dir) {
585 		try {
586 			String path = dir.getAbsolutePath();
587 			logger.debug("Creating [{}]", path);
588 			sftp.mkdir(path);
589 			setAttributes(dir);
590 		} catch (SftpException e) {
591 			throw new IllegalStateException(e);
592 		}
593 	}
594 
595 	protected void setAttributes(RemoteFile file) throws SftpException {
596 		String path = file.getAbsolutePath();
597 		if (file.getPermissions() != null) {
598 			sftp.chmod(file.getPermissions(), path);
599 		}
600 		if (file.getGroupId() != null) {
601 			sftp.chgrp(file.getGroupId(), path);
602 		}
603 		if (file.getUserId() != null) {
604 			sftp.chown(file.getUserId(), path);
605 		}
606 	}
607 
608 	protected void handleNoSuchFileException(RemoteFile file, SftpException e) {
609 		if (isNoSuchFileException(e)) {
610 			file.setStatus(Status.MISSING);
611 		} else {
612 			throw new IllegalStateException(e);
613 		}
614 	}
615 
616 	protected boolean isNoSuchFileException(SftpException exception) {
617 		return exception.id == ChannelSftp.SSH_FX_NO_SUCH_FILE;
618 	}
619 
620 	public File getKnownHosts() {
621 		return knownHosts;
622 	}
623 
624 	public void setKnownHosts(File knownHosts) {
625 		this.knownHosts = knownHosts;
626 	}
627 
628 	public File getConfig() {
629 		return config;
630 	}
631 
632 	public void setConfig(File config) {
633 		this.config = config;
634 	}
635 
636 	public boolean isIncludeDefaultPrivateKeyLocations() {
637 		return includeDefaultPrivateKeyLocations;
638 	}
639 
640 	public void setIncludeDefaultPrivateKeyLocations(boolean includeDefaultPrivateKeyLocations) {
641 		this.includeDefaultPrivateKeyLocations = includeDefaultPrivateKeyLocations;
642 	}
643 
644 	public boolean isStrictHostKeyChecking() {
645 		return strictHostKeyChecking;
646 	}
647 
648 	public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
649 		this.strictHostKeyChecking = strictHostKeyChecking;
650 	}
651 
652 	public String getUsername() {
653 		return username;
654 	}
655 
656 	public void setUsername(String username) {
657 		this.username = username;
658 	}
659 
660 	public String getHostname() {
661 		return hostname;
662 	}
663 
664 	public void setHostname(String hostname) {
665 		this.hostname = hostname;
666 	}
667 
668 	public int getPort() {
669 		return port;
670 	}
671 
672 	public void setPort(int port) {
673 		this.port = port;
674 	}
675 
676 	public int getConnectTimeout() {
677 		return connectTimeout;
678 	}
679 
680 	public void setConnectTimeout(int connectTimeout) {
681 		this.connectTimeout = connectTimeout;
682 	}
683 
684 	public List<File> getPrivateKeys() {
685 		return privateKeys;
686 	}
687 
688 	public void setPrivateKeys(List<File> privateKeys) {
689 		this.privateKeys = privateKeys;
690 	}
691 
692 	public Properties getOptions() {
693 		return options;
694 	}
695 
696 	public void setOptions(Properties options) {
697 		this.options = options;
698 	}
699 
700 	public void setConnectTimeout(Integer connectTimeout) {
701 		this.connectTimeout = connectTimeout;
702 	}
703 
704 	public int getWaitForClosedSleepMillis() {
705 		return waitForClosedSleepMillis;
706 	}
707 
708 	public void setWaitForClosedSleepMillis(int waitForClosedSleepMillis) {
709 		this.waitForClosedSleepMillis = waitForClosedSleepMillis;
710 	}
711 
712 	public String getEncoding() {
713 		return encoding;
714 	}
715 
716 	public void setEncoding(String encoding) {
717 		this.encoding = encoding;
718 	}
719 
720 	public List<String> getPrivateKeyStrings() {
721 		return privateKeyStrings;
722 	}
723 
724 	public void setPrivateKeyStrings(List<String> privateKeyStrings) {
725 		this.privateKeyStrings = privateKeyStrings;
726 	}
727 
728 	public boolean isUseConfigFile() {
729 		return useConfigFile;
730 	}
731 
732 	public void setUseConfigFile(boolean useConfigFile) {
733 		this.useConfigFile = useConfigFile;
734 	}
735 
736 }