Logo Search packages:      
Sourcecode: bareftp version File versions  Download package

FTPClient.cs

// FTPClient.cs
//
//  Copyright (C) 2008-2009 Christian Eide
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

using System;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
using bareFTP.Preferences;

namespace bareFTP.Protocol.Ftp
{     
      public class FTPClient
      {
        internal const string EOL = "\r\n";
            private static int DATA_PORT_RANGE_FROM = 1500;
            private static int DATA_PORT_RANGE_TO = 65000;
            private static int BLOCK_SIZE = 512;
            private List<string> features;
            private Config conf;
            private bool read;
            private bool abort = false;
            
            // default encoding
            //private System.Text.Encoding readerenc = System.Text.Encoding.ASCII;
            
            TcpClient tcpClient;
            StreamReader reader = null;
            StreamWriter writer = null;
            string charset = string.Empty;
            FTPMode ftpMode;
            bool SSL;
            string system;
            string prot_level = string.Empty;
            
            public string RemoteSystem
            {
                  set { system = value; }
            }

            public List<string> Features
            {
                  get { return features; }
            }
            
            public string RemoteCharset
            {
                  set { charset = value; }
            }
            
            public FTPClient(FTPMode mode, bool SSL, Config conf)
            {
                  this.conf = conf;
                  tcpClient = new TcpClient();
                  tcpClient.SendTimeout = conf.NetworkTimeout;
                  tcpClient.ReceiveTimeout = conf.NetworkTimeout;
                  ftpMode = mode;
                  this.SSL = SSL;
            }
            
            public void Connect(string remoteHost, int remotePort, string user, string password)
            {
                  
                  tcpClient.Connect(bareFTP.Protocol.HostNameResolver.GetAddress(remoteHost), remotePort);
                  Stream stream = tcpClient.GetStream();
                  
            writer = new StreamWriter(stream, System.Text.Encoding.ASCII);
                  reader = new StreamReader(stream, System.Text.Encoding.ASCII);
                  
                  CheckReply(GetReply(), 220);
                  
                  try
                  {
                        reader = new StreamReader(stream, GetEncoding());
                        writer = new StreamWriter(stream, System.Text.Encoding.ASCII);
                  }
                  catch(Exception ex)
                  {
                        this.Close();
                        throw ex;
                  }
                  
                  // Check if we need to use SSL
                  if(SSL)
                  {
                        string plevel;
                        if(!string.IsNullOrEmpty(ProtLevel))
                              plevel = ProtLevel;
                        else
                              plevel = conf.FTPSDataChannelProtectionLevel;
                           
                        CheckReply(SendCommand("AUTH TLS"), 234);
                        
                        SslStream sslstream = new SslStream(tcpClient.GetStream(), false, CertificateValidation);
                        
                        
                        try
                        {
                              //sslstream.AuthenticateAsClient(remoteHost, 
                              sslstream.AuthenticateAsClient(remoteHost);
                        }
                        catch
                        {
                              
                              reader.Close();
                              reader.Dispose();
                              reader = null;
                              writer.Close();
                              writer.Dispose();
                              writer = null;
                              tcpClient.Close();
                              sslstream.Close();
                              sslstream.Dispose();
                              sslstream = null;
                              
                              throw new Exception("Certificate Error");
                        }
            
                        writer = new StreamWriter(sslstream, GetEncoding());
                        reader = new StreamReader(sslstream, GetEncoding());
                        
                        CheckReply(SendCommand("PBSZ 0"), 200);
                        CheckReply(SendCommand(string.Format("PROT {0}", plevel)), 200);
                  }
                  
                  try
                  {
                        CheckReply(SendCommand("USER " + user), 331);
                        CheckReply(SendCommand("PASS " + password), 230);
                  }
                  catch(Exception e)
                  {
                        
                        stream.Close();
                        stream.Dispose();
                        stream = null;
                        writer = null;
                        reader = null;
                        tcpClient.Close();
                        tcpClient = null;
                        throw e;
                  }

                  GetSiteFeatures();
                  SetTransferType(Ftp.FTPFileTransferType.Binary);
            }
            
            public bool CertificateValidation (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
                  if(sslPolicyErrors == SslPolicyErrors.None)
                  {
                        return true;
                  }
            else
                  {
                        // TODO: Check out how to inspect the chain better...
                        if(conf.FTPSVerifyServerSertificate)
                        {
                              OnLogTextEmitted(new LogTextEmittedArgs(-2, String.Format("Server certificate error: {0}", sslPolicyErrors)));
                              OnLogTextEmitted(new LogTextEmittedArgs(-2, "Try disabling Server Certificate Validation"));
                              return false;
                        }                             
                        else
                              return true;
                  }
        }
            
            public void Close()
            {
                  if(tcpClient.Connected)
                        CheckReply(SendCommand("QUIT"), 221);
                  if(tcpClient.Connected)
                        tcpClient.Close();
            }
            
            public bool Connected
            {
                  get {
                        if(tcpClient != null)
                              return tcpClient.Connected;
                        else
                              return false;
                  }
            }

            public string ProtLevel {
                  get {
                        return prot_level;
                  }
                  set {
                        prot_level = value;
                  }
            }
            
            public string GetCurrentDirectory()
            {
                  List<FTPReply> replies = CheckReply(SendCommand("PWD"), 257);
                  return replies[0].Message;
            }
            
            public List<FTPReply> SendCommand(string command)
            {
                        
                  Byte[] cmdBytes = GetEncoding().GetBytes((command+EOL).ToCharArray());
                  OnLogTextEmitted(new LogTextEmittedArgs(-1, command));
                  writer.BaseStream.Write(cmdBytes, 0, cmdBytes.Length);
                  writer.Flush();
                  return GetReply();
                  
            }
            
            public void ChangeDir(string dir)
            {
                  CheckReply(SendCommand("CWD " + dir), 250);
            }
            
            public bool IsDir(string cwd, string dir)
            {
                  bool isdir = false;
                  foreach(FTPReply reply in SendCommand("CWD " + dir))
                  {
                        if(reply.ReplyCode == 250)
                              isdir = true;
                  }
                  SendCommand("CWD " + cwd);
                  return isdir;
            }
            
            public void MakeDir(string directoryName)
            {
                  CheckReply(SendCommand("MKD " + directoryName), 257);
            }
            
            public List<string> XDir()
            {
                  List<string> lines = new List<string>();
                  
                  SetTransferType(FTPFileTransferType.ASCII);
                  
                  if(ftpMode == FTPMode.Passive)
                  {
                        if(features.Contains("PRET") || features.Contains("pret"))
                              CheckReply(SendCommand("PRET LIST"), true, 200);
                  }
                  
                  DataSocket dsocket = CreateDataSocket();
                  
                  // Send the list command. Server should reply with 150
                  string list_command = "LIST -La";
                  //if(features.Contains("MLSD"))
                  //    list_command = "MLSD";
                  
                  if(system.IndexOf("VMS") >= 0 || system.Contains("MultiNet Unix Emulation"))
                        list_command = "LIST";
                  
                  List<FTPReply> replies = SendCommand(list_command);
                  
                  CheckReply(replies, 150, 125);
                  
                  lines = ReadLines(dsocket.GetStream());
                  
                  // Uncomment the following to dump directory listing in console
                  //foreach(string l in lines)
                  //    Console.WriteLine(l);
                  
                  dsocket.Close();              
                  // Read reply from control socket. Should be 226
                  CheckReply(GetReply(), 226);
                  
                  return lines;
            }
            
            public List<string> Dir()
            {
                  List<string> lines = new List<string>();
                  
                  SetTransferType(FTPFileTransferType.ASCII);
                  if(ftpMode == FTPMode.Passive)
                  {
                        if(features.Contains("PRET") || features.Contains("pret"))
                              CheckReply(SendCommand("PRET NLST"), true, 200);
                  }
                  
                  DataSocket dsocket = CreateDataSocket();
                  CheckReply(SendCommand("NLST"), 150, 125, 550);
                  lines = ReadLines(dsocket.GetStream());
                  dsocket.Close();
                  // Read reply from control socket. Should be 226
                  CheckReply(GetReply(), 226);
            
                  return lines;
            }
            
            public void RetrieveFile(XferFile file, FileAction action, System.IO.Stream fstream)
            {
                  if(ftpMode == FTPMode.Passive)
                  {
                        if(features.Contains("PRET") || features.Contains("pret"))
                        {
                                    CheckReply(SendCommand("PRET RETR " + file.Path.FileNameRemoteAbs), true, 200);
                        }     
                  }
                  DataSocket dsocket = CreateDataSocket();
                  
                  SetTransferType(FTPFileTransferType.Binary);
                  
                  if(action == FileAction.Resume)
                  {
                        file.TransferedBytes = fstream.Length;
                        CheckReply(SendCommand("REST " + fstream.Length.ToString()), 350);
                  }
                                          
                  CheckReply(SendCommand("RETR " + file.Path.FileNameRemoteAbs), 125, 150);
                  
                  file.Status = bareFTP.Protocol.DownloadStatus.Downloading;
                  
                  Stream networkStream = dsocket.GetStream();
                  Byte[] buffer = new Byte[BLOCK_SIZE];
                  int bytes = 0;

                  read = true;
                  
                  while(read)
                  {
                        bytes = (int)networkStream.Read(buffer, 0, buffer.Length);
                        fstream.Write(buffer, 0, bytes);
                        file.TransferedBytes += (long)bytes;

                        if(bytes == 0)
                              read = false;
                  }
                  
                  if(abort)
                  {
                        networkStream.Close();
                        dsocket.Close();
                        file.Status = bareFTP.Protocol.DownloadStatus.Aborted;
                        abort = false;
                        throw new ReconnectException();
                  }
                  else
                  {
                        file.Status = bareFTP.Protocol.DownloadStatus.Finished;     
                        networkStream.Close();
                        dsocket.Close();
                        CheckReply(GetReply(), 226);
                  }
            }
            
            public void StoreFile(XferFile file, FileAction action, System.IO.Stream fstream)
            {
                  
                  SetTransferType(FTPFileTransferType.Binary);
                  
                  if(ftpMode == FTPMode.Passive)
                  {
                        if(features.Contains("PRET") || features.Contains("pret"))
                        {
                              if(action == FileAction.Resume || action == FileAction.Append)
                                    CheckReply(SendCommand("PRET APPE " + file.Path.FileNameRemoteAbs), true, 200);
                              else
                                    CheckReply(SendCommand("PRET STOR " + file.Path.FileNameRemoteAbs), true, 200);
                        }
                  }
                  
                  DataSocket dsocket = CreateDataSocket();
                  long fslength = fstream.Length;
                  if(action == FileAction.Resume)
                  {
                        CheckReply(SendCommand("APPE " + file.Path.FileNameRemoteAbs), 125, 150);
                        fstream.Position = file.Marker;
                        fslength = fslength - file.Marker;
                        file.TransferedBytes = file.Marker;
                  }
                  else if(action == FileAction.Append)
                  {
                        CheckReply(SendCommand("APPE " + file.Path.FileNameRemoteAbs), 125, 150);
                  }
                  else
                        CheckReply(SendCommand("STOR " + file.Path.FileNameRemoteAbs), 125, 150);
                  
                  Stream networkStream = dsocket.GetStream();

                  Byte[] buffer = new Byte[BLOCK_SIZE];
                  int bytes = 0;
                  
                  file.Status = bareFTP.Protocol.DownloadStatus.Uploading;
            
                  while(file.TransferedBytes < fslength && file.Status != DownloadStatus.Aborted && !abort)
                  {
                        bytes = (int)fstream.Read(buffer, 0, BLOCK_SIZE);
                        networkStream.Write(buffer, 0, bytes);
                        file.TransferedBytes += (long)bytes;
                  }
                  
                  networkStream.Close();
                  dsocket.Close();
                  
                  if(abort)
                  {
                        file.Status = DownloadStatus.Aborted;
                        abort = false;
                        try
                        {
                              GetReply();
                        }
                        catch(NullReferenceException)
                        {
                              throw new ReconnectException();     
                        }
                  }
                  else
                  {
                        file.Status = DownloadStatus.Finished;
                        CheckReply(GetReply(), 226);
                  }
            }
            
            public List<FTPReply> GetReply()
            {
                  try
                  {
                        List<FTPReply> replies = new List<FTPReply>();
                        
                        // read first line
                        //if(reader == null)
                        //    return replies;
                        
                        string line = reader.ReadLine();
                        
                        while (line != null && line.Length == 0)
                        {                             
                              System.Threading.Thread.Sleep(100);
                              line = reader.ReadLine();
                        }
                        if (line == null)
                              throw new SystemException("Unexpected null reply received");
            
                        if (line.Length < 3)
                              throw new SystemException("Short reply received");
            
                        string replyCode = line.Substring(0, 3);
                  
                        string reply = "";
                  
                        if (line.Length > 4)
                              reply = line.Substring(4);
                  
                        // check for multiline response and build up
                        // the reply
                        if (line[3] == '-')
                        {
                              bool complete = false;
                              //reply += Environment.NewLine;
                              while (!complete && line != null)
                              {
                                    line = reader.ReadLine();
                                    if (line == null)
                                          throw new SystemException("Unexpected null reply received");
                              
                                    if (line.Length == 0)
                                          continue;
                    
                                    if(line.Length > 3)
                                    {
                                          if (line.Substring(0,3) == replyCode && line[3] == ' ')
                                          {
                                                complete = true;
                                                reply += "\r\n" + line.Substring(4);
                                                //replies.Add(new FTPReply(Convert.ToInt32(tmpreplyCode), tmpreply));
                                          }
                                          else
                                                reply += "\r\n" + line;
                                    }
                                    else
                                          reply += "\r\n" + line;// + Environment.NewLine;
                              }
                        }
                        
                        replies.Add(new FTPReply(Convert.ToInt32(replyCode), reply));
                        return replies;
                  
                  }
                  catch(Exception ex)
                  {
                        throw ex;
                  }
        }
            
            public void Delete(string filename)
            {
                  CheckReply(SendCommand("DELE " + filename), 250);     
            }
            
            public void Abort()
            {
                  read = false;
                  abort = true;
            }
            
            public void RemoveDir(string dirname)
            {
                  CheckReply(SendCommand("RMD " + dirname), 250);
            }

            public void Chmod(string mode, string filename)
            {
                  Chmod(mode, filename, false);
            }
            
            public void Chmod(string mode, string filename, bool silent)
            {
                  CheckReply(SendCommand(String.Format("SITE CHMOD {0} {1}", mode, filename)), silent, 200);
            }
            
            public void RenameFile(string fromRemoteFileName, string toRemoteFileName)
            {
            
                  CheckReply(SendCommand("RNFR " + fromRemoteFileName), 350);
                  CheckReply(SendCommand("RNTO " + toRemoteFileName), 250);
            }
            
            internal List<string> ReadLines(Stream stream)
            {
                  List<string> lines = new List<string>();
                  
                  using(StreamReader input = new StreamReader(stream, GetEncoding()))
                  {
                        
                        string line = null;
                        while ((line = ReadLine(input)) != null)
                              lines.Add(line);
            
                        input.Close();
                  }
                  return lines;
            }
            
            internal virtual string ReadLine(TextReader input)
        {
                  return input.ReadLine();
        }
            
            internal DataSocket CreateDataSocket()
        {            
                  
                  string hostIP = string.Empty;
                  int port = 0;
                  
                  if(ftpMode == FTPMode.Passive)
                  {
                        List<FTPReply> replies = CheckReply(SendCommand("PASV"), 227);
                        
                        FTPReply replyObj = null;
                        foreach(FTPReply repObj in replies)
                        {
                              if(repObj.ReplyCode == 227)
                                    replyObj = repObj;
                        }
                        // If we have null here then throw exeption
                        if(replyObj == null)
                              throw new Exception("Did not receive proper response to PASV command");
                        
                  string reply = replyObj.Message;
                  
                  Regex regEx = new Regex(@"(?<a0>\d{1,3}),(?<a1>\d{1,3}),(?<a2>\d{1,3}),(?<a3>\d{1,3}),(?<p0>\d{1,3}),(?<p1>\d{1,3})");
                  Match m1 = regEx.Match(reply);

                  string ipAddress = m1.Groups["a0"].Value + "." + m1.Groups["a1"].Value + "." + m1.Groups["a2"].Value + "." + m1.Groups["a3"].Value;
                  
                  int[] portParts = new int[2];
                  portParts[0] = Int32.Parse(m1.Groups["p0"].Value);
                  portParts[1] = Int32.Parse(m1.Groups["p1"].Value);
                  port = (portParts[0] << 8) + portParts[1];
                           
                        hostIP = ipAddress;

                  }
                  else if(ftpMode == FTPMode.Active)
                  {
                        Random rnd = new Random((int)DateTime.Now.Ticks);
                        port = DATA_PORT_RANGE_FROM + rnd.Next(DATA_PORT_RANGE_TO - DATA_PORT_RANGE_FROM);
                        
                        int portHigh = port >> 8;
                        int portLow = port & 255;
                        
                        CheckReply(SendCommand("PORT " + GetLocalAddressList()[0].ToString().Replace(".", ",")
                                                 + "," + portHigh.ToString() + "," + portLow), 200);
                        
                  }
                  
                  string plevel;
                        if(!string.IsNullOrEmpty(ProtLevel))
                              plevel = ProtLevel;
                        else
                              plevel = conf.FTPSDataChannelProtectionLevel;
                  
                  return new DataSocket(hostIP, port, ftpMode, SSL, plevel, conf);
        }
            
            internal void SetTransferType(FTPFileTransferType type)
            {
                  switch (type)
                  {
                        case FTPFileTransferType.ASCII:
                              SetMode("TYPE A");
                              break;
                        case FTPFileTransferType.Binary:
                              SetMode("TYPE I");
                              break;
                        default:
                              throw new Exception("Invalid File Transfer Type");
                  }
            }
                     
            internal void SetMode(string mode)
            {
                  CheckReply(SendCommand(mode), 200);
            }
            
            private IPAddress[] GetLocalAddressList()
            {
                  return Dns.GetHostEntry(Dns.GetHostName()).AddressList;
            }
            
            private void GetSiteFeatures()
            {
                  features = new List<string>();
                  try
                  {
                  
                        foreach(FTPReply reply in CheckReply(SendCommand("FEAT"), true, 211))
                        {
                              if(reply.ReplyCode == 211)
                              {
                                    using(StringReader sr = new StringReader(reply.Message))
                                    {
                                          string line = sr.ReadLine();
                                          while(line != null)
                                          {
                                                if(line.IndexOf("Features") < 0 && line.IndexOf("End") < 0)
                                                      features.Add(line.Trim());
                                                line = sr.ReadLine();
                                          }
                                    }
                              }
                              
                        }
                  }
                  catch(Exception)
                  {
                        features = new List<string>();
                  }
            }
            
            public List<FTPReply> CheckReply(List<FTPReply> reply, params int[] codes)
            {
                  return CheckReply(reply, false, codes);
            }
            
            public List<FTPReply> CheckReply(List<FTPReply> reply, bool silent, params int[] codes)
            {
                  if(reply == null)
                        return null;
                  
                  List<int> codelist = new List<int>(codes);
                  
                  if(silent)
                  {
                        foreach(FTPReply r in reply)
                              r.Silent = silent;
                  }
                  
                  OnLogTextEmitted(new LogTextEmittedArgs(reply));
                  
                  string msgstr = string.Empty;
                  foreach(FTPReply r in reply)
                  {
                        msgstr += r.Message + System.Environment.NewLine;
                        if(codelist.Contains(r.ReplyCode))
                              return reply;
                  }
                  
                  if(!silent)
                        throw new FtpException(msgstr);
                  else
                        return reply;
            }
            
            private System.Text.Encoding GetEncoding()
            {
                  if(!string.IsNullOrEmpty(charset))
                        return System.Text.Encoding.GetEncoding(charset);
                  if(!string.IsNullOrEmpty(conf.General_RemoteCharset))
                        return System.Text.Encoding.GetEncoding(conf.General_RemoteCharset);
                  if(features != null && features.Contains("UTF8"))
                        return System.Text.Encoding.UTF8;
                  
                  return System.Text.Encoding.Default;
            }
            
            public event EventHandler LogTextEmitted;
            public virtual void OnLogTextEmitted(LogTextEmittedArgs e)
            {
                  LogTextEmitted(this, e);
            }
            
            
      }
}

Generated by  Doxygen 1.6.0   Back to index