001: /*
002: * SSHTools - Java SSH2 API
003: *
004: * Copyright (C) 2002-2003 Lee David Painter and Contributors.
005: *
006: * Contributions made by:
007: *
008: * Brett Smith
009: * Richard Pernavas
010: * Erwin Bolwidt
011: *
012: * This program is free software; you can redistribute it and/or
013: * modify it under the terms of the GNU General Public License
014: * as published by the Free Software Foundation; either version 2
015: * of the License, or (at your option) any later version.
016: *
017: * This program is distributed in the hope that it will be useful,
018: * but WITHOUT ANY WARRANTY; without even the implied warranty of
019: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: * GNU General Public License for more details.
021: *
022: * You should have received a copy of the GNU General Public License
023: * along with this program; if not, write to the Free Software
024: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
025: */
026: package com.sshtools.daemon.scp;
027:
028: import com.sshtools.daemon.platform.*;
029: import com.sshtools.daemon.util.*;
030:
031: import com.sshtools.j2ssh.*;
032: import com.sshtools.j2ssh.io.*;
033: import com.sshtools.j2ssh.sftp.*;
034:
035: import org.apache.commons.logging.*;
036:
037: import java.io.*;
038:
039: import java.util.*;
040:
041: /**
042: *
043: *
044: * @author $author$
045: * @version $Revision: 1.8 $
046: */
047: public class ScpServer extends NativeProcessProvider implements
048: Runnable {
049: private static Log log = LogFactory.getLog(ScpServer.class);
050: private static int BUFFER_SIZE = 16384;
051:
052: // Private instance variables
053: private InputStream in;
054:
055: // Private instance variables
056: private InputStream err;
057: private OutputStream out;
058: private String destination;
059: private PipedOutputStream pipeIn;
060: private PipedOutputStream pipeErr;
061: private PipedInputStream pipeOut;
062: private SshThread scpServerThread;
063: private int verbosity = 0;
064: private int exitCode;
065: private boolean directory;
066: private boolean recursive;
067: private boolean from;
068: private boolean to;
069: private NativeFileSystemProvider nfs;
070: private byte[] buffer = new byte[BUFFER_SIZE];
071: private String currentDirectory;
072: private boolean preserveAttributes;
073:
074: /**
075: * Creates a new ScpServer object.
076: */
077: public ScpServer() {
078: nfs = NativeFileSystemProvider.getInstance();
079: }
080:
081: /* (non-Javadoc)
082: * @see com.sshtools.daemon.platform.NativeProcessProvider#allocatePseudoTerminal(java.lang.String, int, int, int, int, java.lang.String)
083: */
084: public boolean allocatePseudoTerminal(String term, int cols,
085: int rows, int width, int height, String modes) {
086: return false;
087: }
088:
089: /* (non-Javadoc)
090: * @see com.sshtools.daemon.platform.NativeProcessProvider#createProcess(java.lang.String, java.util.Map)
091: */
092: public boolean createProcess(String command, Map environment)
093: throws IOException {
094: log.info("Creating ScpServer");
095:
096: if (nfs == null) {
097: throw new IOException(
098: "NativeFileSystem was not instantiated. Please check logs");
099: }
100:
101: scp(command.substring(4));
102:
103: return true;
104: }
105:
106: /* (non-Javadoc)
107: * @see com.sshtools.daemon.platform.NativeProcessProvider#getDefaultTerminalProvider()
108: */
109: public String getDefaultTerminalProvider() {
110: return null;
111: }
112:
113: /* (non-Javadoc)
114: * @see com.sshtools.daemon.platform.NativeProcessProvider#getInputStream()
115: */
116: public InputStream getInputStream() throws IOException {
117: return in;
118: }
119:
120: /* (non-Javadoc)
121: * @see com.sshtools.daemon.platform.NativeProcessProvider#getStderrInputStream()
122: */
123: public InputStream getStderrInputStream() {
124: return err;
125: }
126:
127: /* (non-Javadoc)
128: * @see com.sshtools.daemon.platform.NativeProcessProvider#getOutputStream()
129: */
130: public OutputStream getOutputStream() throws IOException {
131: return out;
132: }
133:
134: /* (non-Javadoc)
135: * @see com.sshtools.daemon.platform.NativeProcessProvider#kill()
136: */
137: public void kill() {
138: log.info("Killing ScpServer");
139:
140: try {
141: if (pipeIn != null) {
142: pipeIn.close();
143: }
144: } catch (IOException ioe) {
145: }
146:
147: try {
148: if (pipeOut != null) {
149: pipeOut.close();
150: }
151: } catch (IOException ioe) {
152: }
153:
154: try {
155: if (pipeErr != null) {
156: pipeErr.close();
157: }
158: } catch (IOException ioe) {
159: }
160: }
161:
162: /* (non-Javadoc)
163: * @see com.sshtools.daemon.platform.NativeProcessProvider#start()
164: */
165: public void start() throws IOException {
166: log.debug("Starting ScpServer thread");
167: scpServerThread = SshThread.getCurrentThread().cloneThread(
168: this , "ScpServer");
169: scpServerThread.start();
170: }
171:
172: /* (non-Javadoc)
173: * @see com.sshtools.daemon.platform.NativeProcessProvider#stillActive()
174: */
175: public boolean stillActive() {
176: return false;
177: }
178:
179: /* (non-Javadoc)
180: * @see com.sshtools.daemon.platform.NativeProcessProvider#supportsPseudoTerminal(java.lang.String)
181: */
182: public boolean supportsPseudoTerminal(String term) {
183: return false;
184: }
185:
186: /* (non-Javadoc)
187: * @see com.sshtools.daemon.platform.NativeProcessProvider#waitForExitCode()
188: */
189: public int waitForExitCode() {
190: try {
191: synchronized (this ) {
192: wait();
193: }
194: } catch (InterruptedException ie) {
195: }
196:
197: log.debug("Returning exit code of " + exitCode);
198:
199: return exitCode;
200: }
201:
202: private void scp(String args) throws IOException {
203: log.debug("Parsing ScpServer options " + args);
204:
205: // Parse the command line for supported options
206: String[] a = StringUtil.current().allParts(args, " ");
207: destination = null;
208: directory = false;
209: from = false;
210: to = false;
211: recursive = false;
212: verbosity = 0;
213:
214: boolean remote = false;
215:
216: for (int i = 0; i < a.length; i++) {
217: if (a[i].startsWith("-")) {
218: String s = a[i].substring(1);
219:
220: for (int j = 0; j < s.length(); j++) {
221: char ch = s.charAt(j);
222:
223: switch (ch) {
224: case 't':
225: to = true;
226:
227: break;
228:
229: case 'd':
230: directory = true;
231:
232: break;
233:
234: case 'f':
235: from = true;
236:
237: break;
238:
239: case 'r':
240: recursive = true;
241:
242: break;
243:
244: case 'v':
245: verbosity++;
246:
247: break;
248:
249: case 'p':
250: preserveAttributes = true;
251:
252: break;
253:
254: default:
255: log
256: .warn("Unsupported argument, allowing to continue.");
257: }
258: }
259: } else {
260: if (destination == null) {
261: destination = a[i];
262: } else {
263: throw new IOException(
264: "More than one destination supplied "
265: + a[i]);
266: }
267: }
268: }
269:
270: if (!to && !from) {
271: throw new IOException("Must supply either -t or -f.");
272: }
273:
274: if (destination == null) {
275: throw new IOException("Destination not supplied.");
276: }
277:
278: log.debug("Destination is " + destination);
279: log.debug("Recursive is " + recursive);
280: log.debug("Directory is " + directory);
281: log.debug("Verbosity is " + verbosity);
282: log.debug("From is " + from);
283: log.debug("To is " + to);
284: log.debug("Preserve Attributes " + preserveAttributes);
285:
286: // Start the SCP server
287: log.debug("Creating pipes");
288: pipeIn = new PipedOutputStream();
289: pipeErr = new PipedOutputStream();
290: pipeOut = new PipedInputStream();
291: in = new PipedInputStream(pipeIn);
292: err = new PipedInputStream(pipeErr);
293: out = new PipedOutputStream(pipeOut);
294: }
295:
296: /**
297: * Send ok command to client
298: *
299: * @throws IOException on any error
300: */
301: private void writeOk() throws IOException {
302: log.debug("Sending client ok command");
303: pipeIn.write(0);
304: pipeIn.flush();
305: }
306:
307: /**
308: * Send command to client
309: *
310: * @param cmd command
311: *
312: * @throws IOException on any error
313: */
314: private void writeCommand(String cmd) throws IOException {
315: log.debug("Sending command '" + cmd + "'");
316: pipeIn.write(cmd.getBytes());
317:
318: if (!cmd.endsWith("\n")) {
319: pipeIn.write("\n".getBytes());
320: }
321:
322: pipeIn.flush();
323: }
324:
325: /**
326: * Send error message to client
327: *
328: * @param msg error message
329: *
330: * @throws IOException on any error
331: */
332: private void writeError(String msg) throws IOException {
333: writeError(msg, false);
334: }
335:
336: /**
337: * Send error message to client
338: *
339: * @param msg error message
340: * @param serious serious error
341: *
342: * @throws IOException on any error
343: */
344: private void writeError(String msg, boolean serious)
345: throws IOException {
346: log.debug("Sending error message '" + msg
347: + "' to client (serious=" + serious + ")");
348: pipeIn.write(serious ? 2 : 1);
349: pipeIn.write(msg.getBytes());
350:
351: if (!msg.endsWith("\n")) {
352: pipeIn.write('\n');
353: }
354:
355: pipeIn.flush();
356: }
357:
358: /* (non-Javadoc)
359: * @see java.lang.Runnable#run()
360: */
361: public void run() {
362: log.debug("Running ScpServer thread");
363:
364: try {
365: if (from) {
366: log.info("From mode");
367:
368: try {
369: waitForResponse();
370:
371: // Build a string pattern that may be used to match wildcards
372: StringPattern sp = new StringPattern(destination);
373:
374: /*If this looks like a wildcard, then attempt a simple expansion.
375: * This only work for the base part of the file name at the moment
376: */
377: if (sp.hasWildcard()) {
378: log.debug("Path contains wildcard");
379:
380: String base = destination;
381: String dir = ".";
382: int idx = base.lastIndexOf('/');
383:
384: if (idx != -1) {
385: if (idx > 0) {
386: dir = base.substring(0, idx);
387: }
388:
389: base = base.substring(idx + 1);
390: }
391:
392: log.debug("Looking for matches in " + dir
393: + " for " + base);
394: sp = new StringPattern(base);
395:
396: byte[] handle = null;
397:
398: try {
399: handle = nfs.openDirectory(dir);
400:
401: SftpFile[] files = nfs
402: .readDirectory(handle);
403:
404: for (int i = 0; i < files.length; i++) {
405: log.debug("Testing for match against "
406: + files[i].getFilename());
407:
408: if (sp.matches(files[i].getFilename())) {
409: log.debug("Matched");
410: writeFileToRemote(dir + "/"
411: + files[i].getFilename());
412: } else {
413: log.debug("No match");
414: }
415: }
416: } finally {
417: if (handle != null) {
418: try {
419: nfs.closeFile(handle);
420: } catch (Exception e) {
421: }
422: }
423: }
424: } else {
425: log.debug("No wildcards");
426: writeFileToRemote(destination);
427: }
428:
429: log.debug("File transfers complete");
430: } catch (FileNotFoundException fnfe) {
431: log.error(fnfe);
432: writeError(fnfe.getMessage(), true);
433: throw new IOException(fnfe.getMessage());
434: } catch (PermissionDeniedException pde) {
435: log.error(pde);
436: writeError(pde.getMessage(), true);
437: throw new IOException(pde.getMessage());
438: } catch (InvalidHandleException ihe) {
439: log.error(ihe);
440: writeError(ihe.getMessage(), true);
441: throw new IOException(ihe.getMessage());
442: } catch (IOException ioe) {
443: log.error(ioe);
444: writeError(ioe.getMessage(), true);
445: throw new IOException(ioe.getMessage());
446: }
447: } else {
448: log.info("To mode");
449: readFromRemote(destination);
450: }
451: } catch (Throwable t) {
452: t.printStackTrace();
453: log.error(t);
454: exitCode = 1;
455: }
456:
457: //
458: log
459: .debug("ScpServer stopped, notify block on waitForExitCode().");
460:
461: synchronized (this ) {
462: notify();
463: }
464: }
465:
466: private boolean writeDirToRemote(String path) throws IOException {
467: FileAttributes attr = nfs.getFileAttributes(path);
468:
469: if (attr.isDirectory() && !recursive) {
470: writeError("File " + path
471: + " is a directory, use recursive mode");
472:
473: return false;
474: }
475:
476: String basename = path;
477: int idx = path.lastIndexOf('/');
478:
479: if (idx != -1) {
480: basename = path.substring(idx + 1);
481: }
482:
483: writeCommand("D" + attr.getMaskString() + " 0 " + basename
484: + "\n");
485: waitForResponse();
486:
487: byte[] handle = null;
488:
489: try {
490: handle = nfs.openDirectory(path);
491:
492: SftpFile[] list = nfs.readDirectory(handle);
493:
494: for (int i = 0; i < list.length; i++) {
495: writeFileToRemote(path + "/" + list[i].getFilename());
496: }
497:
498: writeCommand("E");
499: } catch (InvalidHandleException ihe) {
500: throw new IOException(ihe.getMessage());
501: } catch (PermissionDeniedException e) {
502: throw new IOException(e.getMessage());
503: } finally {
504: if (handle != null) {
505: try {
506: nfs.closeFile(handle);
507: } catch (Exception e) {
508: log.error(e);
509: }
510: }
511: }
512:
513: return true;
514: }
515:
516: private void writeFileToRemote(String path) throws IOException,
517: PermissionDeniedException, InvalidHandleException {
518: FileAttributes attr = nfs.getFileAttributes(path);
519:
520: if (attr.isDirectory()) {
521: if (!writeDirToRemote(path)) {
522: return;
523: }
524: } else if (attr.isFile()) {
525: String basename = path;
526: int idx = basename.lastIndexOf('/');
527:
528: if (idx != -1) {
529: basename = path.substring(idx + 1);
530: }
531:
532: // TODO: Deal with permissions properly
533: writeCommand("C" + attr.getMaskString() + " "
534: + attr.getSize() + " " + basename + "\n");
535: waitForResponse();
536: log.debug("Opening file " + path);
537:
538: byte[] handle = null;
539:
540: try {
541: handle = nfs.openFile(path, new UnsignedInteger32(
542: NativeFileSystemProvider.OPEN_READ), attr);
543:
544: int count = 0;
545: log.debug("Sending file");
546:
547: while (count < attr.getSize().intValue()) {
548: try {
549: byte[] buf = nfs.readFile(handle,
550: new UnsignedInteger64(String
551: .valueOf(count)),
552: new UnsignedInteger32(BUFFER_SIZE));
553: count += buf.length;
554: log.debug("Writing block of " + buf.length
555: + " bytes");
556: pipeIn.write(buf);
557: } catch (EOFException eofe) {
558: log.debug("End of file - finishing transfer");
559:
560: break;
561: }
562: }
563:
564: pipeIn.flush();
565:
566: if (count < attr.getSize().intValue()) {
567: throw new IOException(
568: "File transfer terminated abnormally.");
569: } else {
570: log.info("File transfer complete.");
571: }
572:
573: writeOk();
574: } finally {
575: if (handle != null) {
576: try {
577: nfs.closeFile(handle);
578: } catch (Exception e) {
579: log.error(e);
580: }
581: }
582: }
583: } else {
584: throw new IOException(path + " not valid for SCP.");
585: }
586:
587: waitForResponse();
588: }
589:
590: private void waitForResponse() throws IOException {
591: log.debug("Waiting for response");
592:
593: int r = pipeOut.read();
594:
595: if (r == 0) {
596: log.debug("Got Ok");
597:
598: // All is well, no error
599: return;
600: }
601:
602: if (r == -1) {
603: throw new EOFException("SCP returned unexpected EOF");
604: }
605:
606: String msg = readString();
607: log.debug("Got error '" + msg + "'");
608:
609: if (r == (byte) '\02') {
610: log.debug("This is a serious error");
611: throw new IOException(msg);
612: }
613:
614: throw new IOException("SCP returned an unexpected error: "
615: + msg);
616: }
617:
618: private void readFromRemote(String path) throws IOException {
619: String cmd;
620: String[] cmdParts = new String[3];
621: writeOk();
622:
623: while (true) {
624: log.debug("Waiting for command");
625:
626: try {
627: cmd = readString();
628: } catch (EOFException e) {
629: return;
630: }
631:
632: log.debug("Got command '" + cmd + "'");
633:
634: char cmdChar = cmd.charAt(0);
635:
636: switch (cmdChar) {
637: case 'E':
638: writeOk();
639:
640: return;
641:
642: case 'T':
643: log.error("SCP time not currently supported");
644: writeError("WARNING: This server does not currently support the SCP time command");
645:
646: break;
647:
648: case 'C':
649: case 'D':
650: parseCommand(cmd, cmdParts);
651:
652: FileAttributes attr = null;
653:
654: try {
655: log
656: .debug("Getting attributes for current destination ("
657: + path + ")");
658: attr = nfs.getFileAttributes(path);
659: } catch (FileNotFoundException fnfe) {
660: log.debug("Current destination not found");
661: }
662:
663: String targetPath = path;
664: String name = cmdParts[2];
665:
666: if ((attr != null) && attr.isDirectory()) {
667: log.debug("Target is a directory");
668: targetPath += ('/' + name);
669: }
670:
671: FileAttributes targetAttr = null;
672:
673: try {
674: log
675: .debug("Getting attributes for target destination ("
676: + targetPath + ")");
677: targetAttr = nfs.getFileAttributes(targetPath);
678: } catch (FileNotFoundException fnfe) {
679: log.debug("Target destination not found");
680: }
681:
682: if (cmdChar == 'D') {
683: log.debug("Got directory request");
684:
685: if (targetAttr != null) {
686: if (!targetAttr.isDirectory()) {
687: String msg = "Invalid target " + name
688: + ", must be a directory";
689: writeError(msg);
690: throw new IOException(msg);
691: }
692: } else {
693: try {
694: log.debug("Creating directory "
695: + targetPath);
696:
697: if (!nfs.makeDirectory(targetPath)) {
698: String msg = "Could not create directory: "
699: + name;
700: writeError(msg);
701: throw new IOException(msg);
702: } else {
703: log
704: .debug("Setting permissions on directory");
705: attr
706: .setPermissionsFromMaskString(cmdParts[0]);
707: }
708: } catch (FileNotFoundException e1) {
709: writeError("File not found");
710: throw new IOException("File not found");
711: } catch (PermissionDeniedException e1) {
712: writeError("Permission denied");
713: throw new IOException("Permission denied");
714: }
715: }
716:
717: readFromRemote(targetPath);
718:
719: continue;
720: }
721:
722: log.debug("Opening file for writing");
723:
724: byte[] handle = null;
725:
726: try {
727: // Open the file
728: handle = nfs
729: .openFile(
730: targetPath,
731: new UnsignedInteger32(
732: NativeFileSystemProvider.OPEN_CREATE
733: | NativeFileSystemProvider.OPEN_WRITE
734: | NativeFileSystemProvider.OPEN_TRUNCATE),
735: attr);
736: log.debug("NFS file opened");
737: writeOk();
738: log.debug("Reading from client");
739:
740: int count = 0;
741: int read;
742: long length = Long.parseLong(cmdParts[1]);
743:
744: while (count < length) {
745: read = pipeOut
746: .read(
747: buffer,
748: 0,
749: (int) (((length - count) < buffer.length) ? (length - count)
750: : buffer.length));
751:
752: if (read == -1) {
753: throw new EOFException(
754: "ScpServer received an unexpected EOF during file transfer");
755: }
756:
757: log.debug("Got block of " + read);
758: nfs
759: .writeFile(handle,
760: new UnsignedInteger64(String
761: .valueOf(count)),
762: buffer, 0, read);
763: count += read;
764: }
765:
766: log.debug("File transfer complete");
767: } catch (InvalidHandleException ihe) {
768: writeError("Invalid handle.");
769: throw new IOException("Invalid handle.");
770: } catch (FileNotFoundException e) {
771: writeError("File not found");
772: throw new IOException("File not found");
773: } catch (PermissionDeniedException e) {
774: writeError("Permission denied");
775: throw new IOException("Permission denied");
776: } finally {
777: if (handle != null) {
778: try {
779: log.debug("Closing handle");
780: nfs.closeFile(handle);
781: } catch (Exception e) {
782: }
783: }
784: }
785:
786: waitForResponse();
787:
788: if (preserveAttributes) {
789: attr.setPermissionsFromMaskString(cmdParts[0]);
790: log.debug("Setting permissions on directory to "
791: + attr.getPermissionsString());
792:
793: try {
794: nfs.setFileAttributes(targetPath, attr);
795: } catch (Exception e) {
796: writeError("Failed to set file permissions.");
797:
798: break;
799: }
800: }
801:
802: writeOk();
803:
804: break;
805:
806: default:
807: writeError("Unexpected cmd: " + cmd);
808: throw new IOException("SCP unexpected cmd: " + cmd);
809: }
810: }
811: }
812:
813: private void parseCommand(String cmd, String[] cmdParts)
814: throws IOException {
815: int l;
816: int r;
817: l = cmd.indexOf(' ');
818: r = cmd.indexOf(' ', l + 1);
819:
820: if ((l == -1) || (r == -1)) {
821: writeError("Syntax error in cmd");
822: throw new IOException("Syntax error in cmd");
823: }
824:
825: cmdParts[0] = cmd.substring(1, l);
826: cmdParts[1] = cmd.substring(l + 1, r);
827: cmdParts[2] = cmd.substring(r + 1);
828: }
829:
830: private String readString() throws IOException {
831: int ch;
832: int i = 0;
833:
834: while (((ch = pipeOut.read()) != ((int) '\n')) && (ch >= 0)) {
835: buffer[i++] = (byte) ch;
836: }
837:
838: if (ch == -1) {
839: throw new EOFException("SCP returned unexpected EOF");
840: }
841:
842: if (buffer[0] == (byte) '\n') {
843: throw new IOException("Unexpected <NL>");
844: }
845:
846: if ((buffer[0] == (byte) '\02') || (buffer[0] == (byte) '\01')) {
847: String msg = new String(buffer, 1, i - 1);
848:
849: if (buffer[0] == (byte) '\02') {
850: throw new IOException(msg);
851: }
852:
853: throw new IOException("SCP returned an unexpected error: "
854: + msg);
855: }
856:
857: return new String(buffer, 0, i);
858: }
859: }
|