using System; using Tamir.SharpSsh.jsch; using System.IO; using System.Text; using System.Collections; /* * Scp.cs * * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * 3. The names of the authors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **/ namespace Tamir.SharpSsh { /// /// Class for handling SCP file transfers over SSH connection. /// public class Scp : SshTransferProtocolBase { private bool m_recursive = false; private bool m_verbos = false; private bool m_cancelled = false; public Scp(string host, string user, string password) : base(host, user, password) { } public Scp(string host, string user) : base(host, user) { } protected override string ChannelType { get { return "exec"; } } /// ///This function is empty, so no channel is connected ///on session connect /// protected override void ConnectChannel() { } /// /// Gets or sets a value indicating the default recursive transfer behaviour /// public bool Recursive { get{return m_recursive;} set{m_recursive=value;} } /// /// Gets or sets a value indicating whether trace information should be printed /// public bool Verbos { get{return m_verbos;} set{m_verbos=value;} } public override void Cancel() { m_cancelled = true; } /// /// Creates a directory on the remot server /// /// The new directory public override void Mkdir(string dir) { SCP_CheckConnectivity(); Channel channel=null; Stream server = null; m_cancelled=false; SCP_ConnectTo(out channel, out server, dir, true); SCP_EnterIntoDir(server, dir); channel.disconnect(); } public override void Put(string fromFilePath, string toFilePath) { this.To(fromFilePath, toFilePath); } public override void Get(string fromFilePath, string toFilePath) { this.From(fromFilePath, toFilePath); } /// /// Copies a file from local machine to a remote SSH machine. /// /// The local file path. /// The path of the remote file. public void To(string localPath, string remotePath) { this.To(localPath, remotePath, Recursive); } /// /// Copies a file from local machine to a remote SSH machine. /// /// The local file path. /// The path of the remote file. public void To(string localPath, string remotePath, bool _recursive) { SCP_CheckConnectivity(); Channel channel=null; Stream server = null; m_cancelled=false; try { //if we are sending a single file if(File.Exists(localPath)) { SCP_ConnectTo(out channel, out server, remotePath, _recursive); SCP_SendFile(server, localPath, remotePath); channel.disconnect(); } //else, if we are sending a local directory else if(Directory.Exists(localPath)) { if(!_recursive) { throw new SshTransferException(Path.GetFileName("'"+localPath)+"' is a directory, you should use recursive transfer."); } SCP_ConnectTo(out channel, out server, remotePath, true); ToRecursive(server, localPath, remotePath); channel.disconnect(); } else { throw new SshTransferException("File not found: "+localPath); } } catch(Exception e) { if(Verbos) Console.WriteLine("Error: "+e.Message); //SendEndMessage(remoteFile, localFile, filesize,filesize, "Transfer ended with an error."); try{channel.disconnect();} catch{} throw e; } } /// /// Copies files and directories from local machine to a remote SSH machine using SCP. /// /// I/O Stream for the remote server /// Source to copy /// Destination path private void ToRecursive(Stream server, string src, string dst) { if(Directory.Exists(src)) { SCP_EnterIntoDir(server, Path.GetFileName(dst)); foreach(string file in Directory.GetFiles(src)) { SCP_SendFile(server, file, Path.GetFileName( file)); } if(m_cancelled) { return; } foreach(string dir in Directory.GetDirectories(src)) { ToRecursive(server, dir, Path.GetFileName(dir)); } SCP_EnterIntoParent(server); } else if(File.Exists(src)) { SCP_SendFile(server, src, Path.GetFileName(src)); } else { throw new SshTransferException("File not found: "+src); } } /// /// Copies a file from a remote SSH machine to the local machine using SCP. /// /// The remmote file name /// The local destination path public void From(string remoteFile, string localPath) { this.From(remoteFile, localPath, Recursive); } /// /// Copies a file from a remote SSH machine to the local machine using SCP. /// /// The remmote file name /// The local destination path /// Value indicating whether a recursive transfer should take place public void From(string remoteFile, string localPath, bool _recursive) { SCP_CheckConnectivity(); Channel channel=null; Stream server = null; m_cancelled=false; int filesize=0; String filename=null; string cmd = null; try { String dir=null; if(Directory.Exists(localPath)) { dir= Path.GetFullPath( localPath ); } SCP_ConnectFrom(out channel, out server, remoteFile, _recursive); byte[] buf=new byte[1024]; // send '\0' SCP_SendAck(server); int c=SCP_CheckAck(server); //parse scp commands while((c=='D')||(c=='C')||(c=='E')) { if(m_cancelled) break; cmd = ""+(char)c; if(c=='E') { c=SCP_CheckAck(server); dir = Path.GetDirectoryName(dir); if(Verbos) Console.WriteLine("E"); //send '\0' SCP_SendAck(server); c=(char)SCP_CheckAck(server); continue; } // read '0644 ' or '0755 ' server.Read(buf, 0, 5); for(int i=0;i<5;i++) cmd+=(char)buf[i]; //reading file size filesize=0; while(true) { server.Read(buf, 0, 1); if(buf[0]==' ')break; filesize=filesize*10+(buf[0]-'0'); } //reading file name for(int i=0;;i++) { server.Read(buf, i, 1); if(buf[i]==(byte)0x0a) { filename=Util.getString(buf, 0, i); break; } } cmd += " "+filesize+" "+filename; // send '\0' SCP_SendAck(server); //Receive file if(c=='C') { if(Verbos) Console.WriteLine("Sending file modes: "+cmd); SCP_ReceiveFile(server, remoteFile, dir==null?localPath:dir+"/"+filename, filesize); if(m_cancelled) break; // send '\0' SCP_SendAck(server); } //Enter directory else if(c=='D') { if(dir==null) { if(File.Exists(localPath)) throw new SshTransferException("'"+localPath+"' is not a directory"); dir = localPath; Directory.CreateDirectory(dir); } if(Verbos) Console.WriteLine("Entering directory: "+cmd); dir += "/"+filename; Directory.CreateDirectory(dir); } c=SCP_CheckAck(server); } channel.disconnect(); } catch(Exception e) { if(Verbos) Console.WriteLine("Error: "+e.Message); try { channel.disconnect(); } catch{} throw e; } } #region SCP private functions /// /// Checks is a channel is already connected by this instance /// protected void SCP_CheckConnectivity() { if(!Connected) throw new Exception("Channel is down."); } /// /// Connect a channel to the remote server using the 'SCP TO' command ('scp -t') /// /// Will contain the new connected channel /// Will contaun the new connected server I/O stream /// The remote path on the server /// Idicate a recursive scp transfer protected void SCP_ConnectTo(out Channel channel, out Stream server, string rfile, bool recursive) { string scpCommand = "scp -p -t "; if(recursive) scpCommand += "-r "; scpCommand += "\""+rfile+"\""; channel = (ChannelExec)m_session.openChannel(ChannelType); ((ChannelExec)channel).setCommand(scpCommand); server = new Tamir.Streams.CombinedStream (channel.getInputStream(), channel.getOutputStream()); channel.connect(); SCP_CheckAck(server); } /// /// Connect a channel to the remote server using the 'SCP From' command ('scp -f') /// /// Will contain the new connected channel /// Will contaun the new connected server I/O stream /// The remote path on the server /// Idicate a recursive scp transfer protected void SCP_ConnectFrom(out Channel channel, out Stream server, string rfile, bool recursive) { string scpCommand = "scp -f "; if(recursive) scpCommand += "-r "; scpCommand += "\""+rfile+"\""; channel = (ChannelExec)m_session.openChannel(ChannelType); ((ChannelExec)channel).setCommand(scpCommand); server = new Tamir.Streams.CombinedStream (channel.getInputStream(), channel.getOutputStream()); channel.connect(); //SCP_CheckAck(server); } /// /// Transfer a file to the remote server /// /// A connected server I/O stream /// The source file to copy /// The remote destination path protected void SCP_SendFile(Stream server, string src, string dst) { int filesize = 0; int copied = 0; filesize=(int)(new FileInfo(src)).Length; byte[] tmp=new byte[1]; // send "C0644 filesize filename", where filename should not include '/' string command="C0644 "+filesize+" "+Path.GetFileName(dst)+"\n"; if(Verbos) Console.WriteLine("Sending file modes: "+command); SendStartMessage(src, dst, filesize, "Starting transfer."); byte[] buff = Util.getBytes(command); server.Write(buff, 0, buff.Length); server.Flush(); if(SCP_CheckAck(server)!=0) { throw new SshTransferException("Error openning communication channel."); } // send a content of lfile SendProgressMessage(src, dst, copied, filesize, "Transferring..."); FileStream fis=File.OpenRead(src); byte[] buf=new byte[1024*10*2]; while(!m_cancelled) { int len=fis.Read(buf, 0, buf.Length); if(len<=0) break; server.Write(buf, 0, len); server.Flush(); copied += len; SendProgressMessage(src, dst, copied, filesize, "Transferring..."); } fis.Close(); if(m_cancelled) return; // send '\0' buf[0]=0; server.Write(buf, 0, 1); server.Flush(); SendProgressMessage(src, dst, copied, filesize, "Verifying transfer..."); if(SCP_CheckAck(server)!=0) { SendEndMessage(src, dst,copied,filesize, "Transfer ended with an error."); throw new SshTransferException("Unknow error during file transfer."); } SendEndMessage(src, dst, copied, filesize, "Transfer completed successfuly ("+copied+" bytes)."); } /// /// Transfer a file from the remote server /// /// A connected server I/O stream /// The remote file to copy /// The local destination path protected void SCP_ReceiveFile(Stream server, string rfile, string lfile, int size) { int copied = 0; SendStartMessage(rfile, lfile, size, "Connected, starting transfer."); // read a content of lfile FileStream fos=File.OpenWrite(lfile); int foo; int filesize=size; byte[] buf = new byte[1024]; while(!m_cancelled) { if(buf.Length /// Instructs the remote server to enter into a directory /// /// A connected server I/O stream /// The directory name/param> protected void SCP_EnterIntoDir(Stream server, string dir) { try { byte[] tmp=new byte[1]; // send "C0644 filesize filename", where filename should not include '/' string command="D0755 0 "+Path.GetFileName(dir)+"\n"; if(Verbos) Console.WriteLine("Enter directory: "+command); byte[] buff = Util.getBytes(command); server.Write(buff, 0, buff.Length); server.Flush(); if(SCP_CheckAck(server)!=0) { throw new SshTransferException("Error openning communication channel."); } } catch{} } /// /// Instructs the remote server to go up one level /// /// A connected server I/O stream protected void SCP_EnterIntoParent(Stream server) { try { byte[] tmp=new byte[1]; // send "C0644 filesize filename", where filename should not include '/' string command="E\n"; if(Verbos) Console.WriteLine(command); byte[] buff = Util.getBytes(command); server.Write(buff, 0, buff.Length); server.Flush(); if(SCP_CheckAck(server)!=0) { throw new SshTransferException("Error openning communication channel."); } } catch{} } /// /// Gets server acknowledgment /// /// A connected server I/O stream private int SCP_CheckAck(Stream ins) { int b=ins.ReadByte(); // b may be 0 for success, // 1 for error, // 2 for fatal error, // -1 if(b==0) return b; if(b==-1) return b; if(b==1 || b==2) { StringBuilder sb=new StringBuilder(); int c; do { c=ins.ReadByte(); sb.Append((char)c); } while(c!='\n'); if(b==1) { // error //Console.WriteLine(sb.ToString()); throw new SshTransferException(sb.ToString()); } if(b==2) { // fatal error //Console.WriteLine(sb.ToString()); throw new SshTransferException(sb.ToString()); } } return b; } /// /// Sends acknowledgment to remote server /// /// A connected server I/O stream private void SCP_SendAck(Stream server) { server.WriteByte(0); server.Flush(); } #endregion SCP private functions } }