607 lines
17 KiB
C#
607 lines
17 KiB
C#
|
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
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Class for handling SCP file transfers over SSH connection.
|
||
|
/// </summary>
|
||
|
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"; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
///This function is empty, so no channel is connected
|
||
|
///on session connect
|
||
|
/// </summary>
|
||
|
protected override void ConnectChannel()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating the default recursive transfer behaviour
|
||
|
/// </summary>
|
||
|
public bool Recursive
|
||
|
{
|
||
|
get{return m_recursive;}
|
||
|
set{m_recursive=value;}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether trace information should be printed
|
||
|
/// </summary>
|
||
|
public bool Verbos
|
||
|
{
|
||
|
get{return m_verbos;}
|
||
|
set{m_verbos=value;}
|
||
|
}
|
||
|
|
||
|
public override void Cancel()
|
||
|
{
|
||
|
m_cancelled = true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a directory on the remot server
|
||
|
/// </summary>
|
||
|
/// <param name="dir">The new directory</param>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies a file from local machine to a remote SSH machine.
|
||
|
/// </summary>
|
||
|
/// <param name="localPath">The local file path.</param>
|
||
|
/// <param name="remotePath">The path of the remote file.</param>
|
||
|
public void To(string localPath, string remotePath)
|
||
|
{
|
||
|
this.To(localPath, remotePath, Recursive);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies a file from local machine to a remote SSH machine.
|
||
|
/// </summary>
|
||
|
/// <param name="localPath">The local file path.</param>
|
||
|
/// <param name="remotePath">The path of the remote file.</param>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies files and directories from local machine to a remote SSH machine using SCP.
|
||
|
/// </summary>
|
||
|
/// <param name="server">I/O Stream for the remote server</param>
|
||
|
/// <param name="src">Source to copy</param>
|
||
|
/// <param name="dst">Destination path</param>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies a file from a remote SSH machine to the local machine using SCP.
|
||
|
/// </summary>
|
||
|
/// <param name="remoteFile">The remmote file name</param>
|
||
|
/// <param name="localPath">The local destination path</param>
|
||
|
public void From(string remoteFile, string localPath)
|
||
|
{
|
||
|
this.From(remoteFile, localPath, Recursive);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies a file from a remote SSH machine to the local machine using SCP.
|
||
|
/// </summary>
|
||
|
/// <param name="remoteFile">The remmote file name</param>
|
||
|
/// <param name="localPath">The local destination path</param>
|
||
|
/// <param name="recursive">Value indicating whether a recursive transfer should take place</param>
|
||
|
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
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks is a channel is already connected by this instance
|
||
|
/// </summary>
|
||
|
protected void SCP_CheckConnectivity()
|
||
|
{
|
||
|
if(!Connected)
|
||
|
throw new Exception("Channel is down.");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Connect a channel to the remote server using the 'SCP TO' command ('scp -t')
|
||
|
/// </summary>
|
||
|
/// <param name="channel">Will contain the new connected channel</param>
|
||
|
/// <param name="server">Will contaun the new connected server I/O stream</param>
|
||
|
/// <param name="rfile">The remote path on the server</param>
|
||
|
/// <param name="recursive">Idicate a recursive scp transfer</param>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Connect a channel to the remote server using the 'SCP From' command ('scp -f')
|
||
|
/// </summary>
|
||
|
/// <param name="channel">Will contain the new connected channel</param>
|
||
|
/// <param name="server">Will contaun the new connected server I/O stream</param>
|
||
|
/// <param name="rfile">The remote path on the server</param>
|
||
|
/// <param name="recursive">Idicate a recursive scp transfer</param>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Transfer a file to the remote server
|
||
|
/// </summary>
|
||
|
/// <param name="server">A connected server I/O stream</param>
|
||
|
/// <param name="src">The source file to copy</param>
|
||
|
/// <param name="dst">The remote destination path</param>
|
||
|
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).");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Transfer a file from the remote server
|
||
|
/// </summary>
|
||
|
/// <param name="server">A connected server I/O stream</param>
|
||
|
/// <param name="rfile">The remote file to copy</param>
|
||
|
/// <param name="lfile">The local destination path</param>
|
||
|
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<filesize) foo=buf.Length;
|
||
|
else foo=filesize;
|
||
|
int len=server.Read(buf, 0, foo);
|
||
|
copied += len;
|
||
|
fos.Write(buf, 0, foo);
|
||
|
SendProgressMessage(rfile, lfile, copied, size, "Transferring...");
|
||
|
filesize-=foo;
|
||
|
if(filesize==0) break;
|
||
|
}
|
||
|
fos.Close();
|
||
|
if(m_cancelled)
|
||
|
return;
|
||
|
SCP_CheckAck(server);
|
||
|
SendEndMessage(rfile, lfile, copied, size, "Transfer completed successfuly ("+filesize+" bytes).");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Instructs the remote server to enter into a directory
|
||
|
/// </summary>
|
||
|
/// <param name="server">A connected server I/O stream</param>
|
||
|
/// <param name="dir">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{}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Instructs the remote server to go up one level
|
||
|
/// </summary>
|
||
|
/// <param name="server">A connected server I/O stream</param>
|
||
|
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{}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets server acknowledgment
|
||
|
/// </summary>
|
||
|
/// <param name="ins">A connected server I/O stream</param>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sends acknowledgment to remote server
|
||
|
/// </summary>
|
||
|
/// <param name="server">A connected server I/O stream</param>
|
||
|
private void SCP_SendAck(Stream server)
|
||
|
{
|
||
|
server.WriteByte(0);
|
||
|
server.Flush();
|
||
|
}
|
||
|
|
||
|
#endregion SCP private functions
|
||
|
}
|
||
|
}
|