001    /**
002     * Copyright 2010-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.common.util.secure;
017    
018    import java.io.BufferedOutputStream;
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.OutputStream;
025    import java.lang.reflect.InvocationTargetException;
026    import java.util.ArrayList;
027    import java.util.List;
028    import java.util.Properties;
029    
030    import org.apache.commons.beanutils.BeanUtils;
031    import org.apache.commons.io.FileUtils;
032    import org.apache.commons.io.FilenameUtils;
033    import org.apache.commons.io.IOUtils;
034    import org.apache.commons.lang3.StringUtils;
035    import org.kuali.common.util.Assert;
036    import org.kuali.common.util.CollectionUtils;
037    import org.kuali.common.util.LocationUtils;
038    import org.kuali.common.util.PropertyUtils;
039    import org.kuali.common.util.Str;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    import com.jcraft.jsch.Channel;
044    import com.jcraft.jsch.ChannelExec;
045    import com.jcraft.jsch.ChannelSftp;
046    import com.jcraft.jsch.JSch;
047    import com.jcraft.jsch.JSchException;
048    import com.jcraft.jsch.Session;
049    import com.jcraft.jsch.SftpATTRS;
050    import com.jcraft.jsch.SftpException;
051    
052    public class DefaultSecureChannel implements SecureChannel {
053    
054            private static final Logger logger = LoggerFactory.getLogger(DefaultSecureChannel.class);
055            private static final String SFTP = "sftp";
056            private static final String EXEC = "exec";
057            private static final String FORWARDSLASH = "/";
058            private static final int DEFAULT_SLEEP_MILLIS = 10;
059            private static final String DEFAULT_ENCODING = "UTF-8";
060    
061            File knownHosts = SSHUtils.DEFAULT_KNOWN_HOSTS;
062            File config = SSHUtils.DEFAULT_CONFIG_FILE;
063            boolean useConfigFile = true;
064            boolean includeDefaultPrivateKeyLocations = true;
065            boolean strictHostKeyChecking = true;
066            int port = SSHUtils.DEFAULT_PORT;
067            int waitForClosedSleepMillis = DEFAULT_SLEEP_MILLIS;
068            String encoding = DEFAULT_ENCODING;
069            String username;
070            String hostname;
071            Integer connectTimeout;
072            List<File> privateKeys;
073            List<String> privateKeyStrings;
074            Properties options;
075    
076            protected Session session;
077            protected ChannelSftp sftp;
078    
079            @Override
080            public synchronized void open() throws IOException {
081                    logOpen();
082                    validate();
083                    try {
084                            JSch jsch = getJSch();
085                            this.session = openSession(jsch);
086                            this.sftp = openSftpChannel(session, connectTimeout);
087                    } catch (JSchException e) {
088                            throw new IOException("Unexpected error opening secure channel", e);
089                    }
090            }
091    
092            @Override
093            public synchronized void close() {
094                    logger.info("Closing secure channel [{}]", ChannelUtils.getLocation(username, hostname));
095                    closeQuietly(sftp);
096                    closeQuietly(session);
097            }
098    
099            @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    }