001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.plugins.repository.ssh;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028:
029: import com.jcraft.jsch.Channel;
030: import com.jcraft.jsch.ChannelExec;
031: import com.jcraft.jsch.JSchException;
032: import com.jcraft.jsch.Session;
033:
034: /**
035: * This class is using the scp client to transfer data and information for the repository.
036: * <p>
037: * It is based on the SCPClient from the ganymed ssh library from Christian Plattner,
038: * released under a BSD style license.
039: * <p>
040: * To minimize the dependency to the ssh library and because we needed some additional
041: * functionality, we decided to copy'n'paste the single class rather than to inherit or
042: * delegate it somehow.
043: * <p>
044: * Nevertheless credit should go to the original author.
045: */
046: public class Scp {
047: private static final int BUFFER_SIZE = 64 * 1024;
048:
049: /*
050: * Maximum length authorized for scp lines.
051: * This is a random limit - if your path names are longer, then adjust it.
052: */
053: private static final int MAX_SCP_LINE_LENGTH = 8192;
054:
055: private Session session;
056:
057: public class FileInfo {
058: private String filename;
059:
060: private long length;
061:
062: private long lastModified;
063:
064: /**
065: * @param filename
066: * The filename to set.
067: */
068: public void setFilename(String filename) {
069: this .filename = filename;
070: }
071:
072: /**
073: * @return Returns the filename.
074: */
075: public String getFilename() {
076: return filename;
077: }
078:
079: /**
080: * @param length
081: * The length to set.
082: */
083: public void setLength(long length) {
084: this .length = length;
085: }
086:
087: /**
088: * @return Returns the length.
089: */
090: public long getLength() {
091: return length;
092: }
093:
094: /**
095: * @param lastModified
096: * The lastModified to set.
097: */
098: public void setLastModified(long lastModified) {
099: this .lastModified = lastModified;
100: }
101:
102: /**
103: * @return Returns the lastModified.
104: */
105: public long getLastModified() {
106: return lastModified;
107: }
108: }
109:
110: public Scp(Session session) {
111: if (session == null) {
112: throw new IllegalArgumentException(
113: "Cannot accept null argument!");
114: }
115: this .session = session;
116: }
117:
118: private void readResponse(InputStream is) throws IOException,
119: RemoteScpException {
120: int c = is.read();
121:
122: if (c == 0) {
123: return;
124: }
125:
126: if (c == -1) {
127: throw new RemoteScpException(
128: "Remote scp terminated unexpectedly.");
129: }
130:
131: if ((c != 1) && (c != 2)) {
132: throw new RemoteScpException(
133: "Remote scp sent illegal error code.");
134: }
135:
136: if (c == 2) {
137: throw new RemoteScpException(
138: "Remote scp terminated with error.");
139: }
140:
141: String err = receiveLine(is);
142: throw new RemoteScpException(
143: "Remote scp terminated with error (" + err + ").");
144: }
145:
146: private String receiveLine(InputStream is) throws IOException,
147: RemoteScpException {
148: StringBuffer sb = new StringBuffer(30);
149:
150: while (true) {
151:
152: if (sb.length() > MAX_SCP_LINE_LENGTH) {
153: throw new RemoteScpException(
154: "Remote scp sent a too long line");
155: }
156:
157: int c = is.read();
158:
159: if (c < 0) {
160: throw new RemoteScpException(
161: "Remote scp terminated unexpectedly.");
162: }
163:
164: if (c == '\n') {
165: break;
166: }
167:
168: sb.append((char) c);
169:
170: }
171: return sb.toString();
172: }
173:
174: private void parseCLine(String line, FileInfo fileInfo)
175: throws RemoteScpException {
176: /* Minimum line: "xxxx y z" ---> 8 chars */
177:
178: long len;
179:
180: if (line.length() < 8) {
181: throw new RemoteScpException(
182: "Malformed C line sent by remote SCP binary, line too short.");
183: }
184:
185: if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) {
186: throw new RemoteScpException(
187: "Malformed C line sent by remote SCP binary.");
188: }
189:
190: int lengthNameSep = line.indexOf(' ', 5);
191:
192: if (lengthNameSep == -1) {
193: throw new RemoteScpException(
194: "Malformed C line sent by remote SCP binary.");
195: }
196:
197: String lengthSubstring = line.substring(5, lengthNameSep);
198: String nameSubstring = line.substring(lengthNameSep + 1);
199:
200: if ((lengthSubstring.length() <= 0)
201: || (nameSubstring.length() <= 0)) {
202: throw new RemoteScpException(
203: "Malformed C line sent by remote SCP binary.");
204: }
205:
206: if ((6 + lengthSubstring.length() + nameSubstring.length()) != line
207: .length()) {
208: throw new RemoteScpException(
209: "Malformed C line sent by remote SCP binary.");
210: }
211:
212: try {
213: len = Long.parseLong(lengthSubstring);
214: } catch (NumberFormatException e) {
215: throw new RemoteScpException(
216: "Malformed C line sent by remote SCP binary, cannot parse file length.");
217: }
218:
219: if (len < 0) {
220: throw new RemoteScpException(
221: "Malformed C line sent by remote SCP binary, illegal file length.");
222: }
223:
224: fileInfo.setLength(len);
225: fileInfo.setFilename(nameSubstring);
226: }
227:
228: private void parseTLine(String line, FileInfo fileInfo)
229: throws RemoteScpException {
230: /* Minimum line: "0 0 0 0" ---> 8 chars */
231:
232: long modtime;
233: long firstMsec;
234: long atime;
235: long secondMsec;
236:
237: if (line.length() < 8) {
238: throw new RemoteScpException(
239: "Malformed T line sent by remote SCP binary, line too short.");
240: }
241:
242: int firstMsecBegin = line.indexOf(" ") + 1;
243: if (firstMsecBegin == 0 || firstMsecBegin >= line.length()) {
244: throw new RemoteScpException(
245: "Malformed T line sent by remote SCP binary, line not enough data.");
246: }
247:
248: int atimeBegin = line.indexOf(" ", firstMsecBegin + 1) + 1;
249: if (atimeBegin == 0 || atimeBegin >= line.length()) {
250: throw new RemoteScpException(
251: "Malformed T line sent by remote SCP binary, line not enough data.");
252: }
253:
254: int secondMsecBegin = line.indexOf(" ", atimeBegin + 1) + 1;
255: if (secondMsecBegin == 0 || secondMsecBegin >= line.length()) {
256: throw new RemoteScpException(
257: "Malformed T line sent by remote SCP binary, line not enough data.");
258: }
259:
260: try {
261: modtime = Long.parseLong(line.substring(0,
262: firstMsecBegin - 1));
263: firstMsec = Long.parseLong(line.substring(firstMsecBegin,
264: atimeBegin - 1));
265: atime = Long.parseLong(line.substring(atimeBegin,
266: secondMsecBegin - 1));
267: secondMsec = Long
268: .parseLong(line.substring(secondMsecBegin));
269: } catch (NumberFormatException e) {
270: throw new RemoteScpException(
271: "Malformed C line sent by remote SCP binary, cannot parse file length.");
272: }
273:
274: if (modtime < 0 || firstMsec < 0 || atime < 0 || secondMsec < 0) {
275: throw new RemoteScpException(
276: "Malformed C line sent by remote SCP binary, illegal file length.");
277: }
278:
279: fileInfo.setLastModified(modtime);
280: }
281:
282: private void sendBytes(Channel channel, byte[] data,
283: String fileName, String mode) throws IOException,
284: RemoteScpException {
285: OutputStream os = channel.getOutputStream();
286: InputStream is = new BufferedInputStream(channel
287: .getInputStream(), 512);
288:
289: try {
290: if (channel.isConnected()) {
291: channel.start();
292: } else {
293: channel.connect();
294: }
295: } catch (JSchException e1) {
296: throw (IOException) new IOException(
297: "Channel connection problems").initCause(e1);
298: }
299:
300: readResponse(is);
301:
302: String cline = "C" + mode + " " + data.length + " " + fileName
303: + "\n";
304:
305: os.write(cline.getBytes());
306: os.flush();
307:
308: readResponse(is);
309:
310: os.write(data, 0, data.length);
311: os.write(0);
312: os.flush();
313:
314: readResponse(is);
315:
316: os.write("E\n".getBytes());
317: os.flush();
318: }
319:
320: private void sendFile(Channel channel, String localFile,
321: String remoteName, String mode) throws IOException,
322: RemoteScpException {
323: byte[] buffer = new byte[BUFFER_SIZE];
324:
325: OutputStream os = new BufferedOutputStream(channel
326: .getOutputStream(), 40000);
327: InputStream is = new BufferedInputStream(channel
328: .getInputStream(), 512);
329:
330: try {
331: if (channel.isConnected()) {
332: channel.start();
333: } else {
334: channel.connect();
335: }
336: } catch (JSchException e1) {
337: throw (IOException) new IOException(
338: "Channel connection problems").initCause(e1);
339: }
340:
341: readResponse(is);
342:
343: File f = new File(localFile);
344: long remain = f.length();
345:
346: String cline = "C" + mode + " " + remain + " " + remoteName
347: + "\n";
348:
349: os.write(cline.getBytes());
350: os.flush();
351:
352: readResponse(is);
353:
354: FileInputStream fis = null;
355:
356: try {
357: fis = new FileInputStream(f);
358:
359: while (remain > 0) {
360: int trans;
361: if (remain > buffer.length) {
362: trans = buffer.length;
363: } else {
364: trans = (int) remain;
365: }
366: if (fis.read(buffer, 0, trans) != trans) {
367: throw new IOException(
368: "Cannot read enough from local file "
369: + localFile);
370: }
371:
372: os.write(buffer, 0, trans);
373:
374: remain -= trans;
375: }
376:
377: fis.close();
378: } catch (IOException e) {
379: if (fis != null) {
380: fis.close();
381: }
382: throw (e);
383: }
384:
385: os.write(0);
386: os.flush();
387:
388: readResponse(is);
389:
390: os.write("E\n".getBytes());
391: os.flush();
392: }
393:
394: /**
395: * Receive a file via scp and store it in a stream
396: *
397: * @param channel
398: * ssh channel to use
399: * @param file
400: * to receive from remote
401: * @param target
402: * to store file into (if null, get only file info)
403: * @return file information of the file we received
404: * @throws IOException
405: * in case of network or protocol trouble
406: * @throws RemoteScpException
407: * in case of problems on the target system (connection is fine)
408: */
409: private FileInfo receiveStream(Channel channel, String file,
410: OutputStream targetStream) throws IOException,
411: RemoteScpException {
412: byte[] buffer = new byte[BUFFER_SIZE];
413:
414: OutputStream os = channel.getOutputStream();
415: InputStream is = channel.getInputStream();
416: try {
417: if (channel.isConnected()) {
418: channel.start();
419: } else {
420: channel.connect();
421: }
422: } catch (JSchException e1) {
423: throw (IOException) new IOException(
424: "Channel connection problems").initCause(e1);
425: }
426: os.write(0x0);
427: os.flush();
428:
429: FileInfo fileInfo = new FileInfo();
430:
431: while (true) {
432: int c = is.read();
433: if (c < 0) {
434: throw new RemoteScpException(
435: "Remote scp terminated unexpectedly.");
436: }
437:
438: String line = receiveLine(is);
439:
440: if (c == 'T') {
441: parseTLine(line, fileInfo);
442: os.write(0x0);
443: os.flush();
444: continue;
445: }
446: if ((c == 1) || (c == 2)) {
447: throw new RemoteScpException("Remote SCP error: "
448: + line);
449: }
450:
451: if (c == 'C') {
452: parseCLine(line, fileInfo);
453: break;
454: }
455: throw new RemoteScpException("Remote SCP error: "
456: + ((char) c) + line);
457: }
458: if (targetStream != null) {
459:
460: os.write(0x0);
461: os.flush();
462:
463: try {
464: long remain = fileInfo.getLength();
465:
466: while (remain > 0) {
467: int trans;
468: if (remain > buffer.length) {
469: trans = buffer.length;
470: } else {
471: trans = (int) remain;
472: }
473:
474: int this TimeReceived = is.read(buffer, 0, trans);
475:
476: if (this TimeReceived < 0) {
477: throw new IOException(
478: "Remote scp terminated connection unexpectedly");
479: }
480:
481: targetStream.write(buffer, 0, this TimeReceived);
482:
483: remain -= this TimeReceived;
484: }
485:
486: targetStream.close();
487: } catch (IOException e) {
488: if (targetStream != null) {
489: targetStream.close();
490: }
491: throw (e);
492: }
493:
494: readResponse(is);
495:
496: os.write(0x0);
497: os.flush();
498: }
499: return fileInfo;
500: }
501:
502: /**
503: * Copy a local file to a remote directory, uses mode 0600 when creating the file on the remote
504: * side.
505: *
506: * @param localFile
507: * Path and name of local file.
508: * @param remoteTargetDirectory
509: * Remote target directory where the file has to end up (optional)
510: * @param remoteName
511: * target filename to use
512: * @throws IOException
513: * in case of network problems
514: * @throws RemoteScpException
515: * in case of problems on the target system (connection ok)
516: */
517: public void put(String localFile, String remoteTargetDirectory,
518: String remoteName) throws IOException, RemoteScpException {
519: put(localFile, remoteTargetDirectory, remoteName, "0600");
520: }
521:
522: /**
523: * Create a remote file and copy the contents of the passed byte array into it. Uses mode 0600
524: * for creating the remote file.
525: *
526: * @param data
527: * the data to be copied into the remote file.
528: * @param remoteFileName
529: * The name of the file which will be created in the remote target directory.
530: * @param remoteTargetDirectory
531: * Remote target directory where the file has to end up (optional)
532: * @throws IOException
533: * in case of network problems
534: * @throws RemoteScpException
535: * in case of problems on the target system (connection ok)
536: */
537:
538: public void put(byte[] data, String remoteFileName,
539: String remoteTargetDirectory) throws IOException,
540: RemoteScpException {
541: put(data, remoteFileName, remoteTargetDirectory, "0600");
542: }
543:
544: /**
545: * Create a remote file and copy the contents of the passed byte array into it. The method use
546: * the specified mode when creating the file on the remote side.
547: *
548: * @param data
549: * the data to be copied into the remote file.
550: * @param remoteFileName
551: * The name of the file which will be created in the remote target directory.
552: * @param remoteTargetDirectory
553: * Remote target directory where the file has to end up (optional)
554: * @param mode
555: * a four digit string (e.g., 0644, see "man chmod", "man open")
556: * @throws IOException
557: * in case of network problems
558: * @throws RemoteScpException
559: * in case of problems on the target system (connection ok)
560: */
561: public void put(byte[] data, String remoteFileName,
562: String remoteTargetDirectory, String mode)
563: throws IOException, RemoteScpException {
564: ChannelExec channel = null;
565:
566: if ((remoteFileName == null) || (mode == null)) {
567: throw new IllegalArgumentException("Null argument.");
568: }
569:
570: if (mode.length() != 4) {
571: throw new IllegalArgumentException("Invalid mode.");
572: }
573:
574: for (int i = 0; i < mode.length(); i++) {
575: if (!Character.isDigit(mode.charAt(i))) {
576: throw new IllegalArgumentException("Invalid mode.");
577: }
578: }
579:
580: String cmd = "scp -t ";
581: if (remoteTargetDirectory != null
582: && remoteTargetDirectory.length() > 0) {
583: cmd = cmd + "-d " + remoteTargetDirectory;
584: }
585:
586: try {
587: channel = getExecChannel();
588: channel.setCommand(cmd);
589: sendBytes(channel, data, remoteFileName, mode);
590: // channel.disconnect();
591: } catch (JSchException e) {
592: if (channel != null) {
593: channel.disconnect();
594: }
595: throw (IOException) new IOException(
596: "Error during SCP transfer." + e.getMessage())
597: .initCause(e);
598: }
599: }
600:
601: /**
602: * @return
603: * @throws JSchException
604: */
605: private ChannelExec getExecChannel() throws JSchException {
606: ChannelExec channel;
607: channel = (ChannelExec) session.openChannel("exec");
608: return channel;
609: }
610:
611: /**
612: * Copy a local file to a remote site, uses the specified mode when creating the file on the
613: * remote side.
614: *
615: * @param localFile
616: * Path and name of local file.
617: * @param remoteTargetDir
618: * Remote target directory where the file has to end up (optional)
619: * @param remoteTargetName
620: * file name to use on the target system
621: * @param mode
622: * a four digit string (e.g., 0644, see "man chmod", "man open")
623: * @throws IOException
624: * in case of network problems
625: * @throws RemoteScpException
626: * in case of problems on the target system (connection ok)
627: */
628: public void put(String localFile, String remoteTargetDir,
629: String remoteTargetName, String mode) throws IOException,
630: RemoteScpException {
631: ChannelExec channel = null;
632:
633: if ((localFile == null) || (remoteTargetName == null)
634: || (mode == null)) {
635: throw new IllegalArgumentException("Null argument.");
636: }
637:
638: if (mode.length() != 4) {
639: throw new IllegalArgumentException("Invalid mode.");
640: }
641:
642: for (int i = 0; i < mode.length(); i++) {
643: if (!Character.isDigit(mode.charAt(i))) {
644: throw new IllegalArgumentException("Invalid mode.");
645: }
646: }
647:
648: String cmd = "scp -t ";
649: if (remoteTargetDir != null && remoteTargetDir.length() > 0) {
650: cmd = cmd + "-d " + remoteTargetDir;
651: }
652:
653: try {
654: channel = getExecChannel();
655: channel.setCommand(cmd);
656: sendFile(channel, localFile, remoteTargetName, mode);
657: channel.disconnect();
658: } catch (JSchException e) {
659: if (channel != null) {
660: channel.disconnect();
661: }
662: throw (IOException) new IOException(
663: "Error during SCP transfer." + e.getMessage())
664: .initCause(e);
665: }
666: }
667:
668: /**
669: * Download a file from the remote server to a local file.
670: *
671: * @param remoteFile
672: * Path and name of the remote file.
673: * @param localTarget
674: * Local file where to store the data.
675: * @throws IOException
676: * in case of network problems
677: * @throws RemoteScpException
678: * in case of problems on the target system (connection ok)
679: */
680: public void get(String remoteFile, String localTarget)
681: throws IOException, RemoteScpException {
682: File f = new File(localTarget);
683: FileOutputStream fop = new FileOutputStream(f);
684: get(remoteFile, fop);
685: }
686:
687: /**
688: * Download a file from the remote server into an OutputStream
689: *
690: * @param remoteFile
691: * Path and name of the remote file.
692: * @param localTarget
693: * OutputStream to store the data.
694: * @throws IOException
695: * in case of network problems
696: * @throws RemoteScpException
697: * in case of problems on the target system (connection ok)
698: */
699: public void get(String remoteFile, OutputStream localTarget)
700: throws IOException, RemoteScpException {
701: ChannelExec channel = null;
702:
703: if ((remoteFile == null) || (localTarget == null)) {
704: throw new IllegalArgumentException("Null argument.");
705: }
706:
707: String cmd = "scp -p -f " + remoteFile;
708:
709: try {
710: channel = getExecChannel();
711: channel.setCommand(cmd);
712: receiveStream(channel, remoteFile, localTarget);
713: channel.disconnect();
714: } catch (JSchException e) {
715: if (channel != null) {
716: channel.disconnect();
717: }
718: throw (IOException) new IOException(
719: "Error during SCP transfer." + e.getMessage())
720: .initCause(e);
721: }
722: }
723:
724: /**
725: * Initiates an SCP sequence but stops after getting fileinformation header
726: *
727: * @param remoteFile
728: * to get information for
729: * @return the file information got
730: * @throws IOException
731: * in case of network problems
732: * @throws RemoteScpException
733: * in case of problems on the target system (connection ok)
734: */
735: public FileInfo getFileinfo(String remoteFile) throws IOException,
736: RemoteScpException {
737: ChannelExec channel = null;
738: FileInfo fileInfo = null;
739:
740: if (remoteFile == null) {
741: throw new IllegalArgumentException("Null argument.");
742: }
743:
744: String cmd = "scp -p -f \"" + remoteFile + "\"";
745:
746: try {
747: channel = getExecChannel();
748: channel.setCommand(cmd);
749: fileInfo = receiveStream(channel, remoteFile, null);
750: channel.disconnect();
751: } catch (JSchException e) {
752: throw (IOException) new IOException(
753: "Error during SCP transfer." + e.getMessage())
754: .initCause(e);
755: } finally {
756: if (channel != null) {
757: channel.disconnect();
758: }
759: }
760: return fileInfo;
761: }
762: }
|