Skip to content

Instantly share code, notes, and snippets.

@guange2015
Created April 1, 2014 06:23
Show Gist options
  • Select an option

  • Save guange2015/9908726 to your computer and use it in GitHub Desktop.

Select an option

Save guange2015/9908726 to your computer and use it in GitHub Desktop.

Revisions

  1. guange2015 created this gist Apr 1, 2014.
    403 changes: 403 additions & 0 deletions SharpFtpServer.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,403 @@
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Net.Sockets;
    using System.Net;
    using System.IO;
    using System.Threading;

    namespace Demo
    {
    class SharpFtpServer
    {
    TcpListener _listener;
    public SharpFtpServer()
    {
    _listener = new TcpListener(IPAddress.Any, 21);
    }

    public void Start()
    {
    _listener.Start();
    _listener.BeginAcceptTcpClient(AcceptClientHandle, _listener);
    }

    void AcceptClientHandle(IAsyncResult ar)
    {
    TcpClient client = _listener.EndAcceptTcpClient(ar);
    _listener.BeginAcceptTcpClient(AcceptClientHandle, ar.AsyncState);

    ClientConnection connectionClient = new ClientConnection(client);
    ThreadPool.QueueUserWorkItem(connectionClient.HandleClient, client);
    }
    }

    class ClientConnection
    {
    TcpClient _controlClient;
    NetworkStream _controlStream;
    StreamWriter _controlWriter;
    StreamReader _controlReader;
    TcpListener _passiveListener;

    enum DataConnectionType { Active, Passive };

    DataConnectionType _dataConnectionType = DataConnectionType.Passive;

    string _currentDirectory = @"c:\prj";

    public string _transferType { get; set; }

    public ClientConnection(TcpClient client)
    {
    _controlClient = client;
    _controlStream = _controlClient.GetStream();
    _controlWriter = new StreamWriter(_controlStream, Encoding.ASCII);
    _controlReader = new StreamReader(_controlStream, Encoding.ASCII);
    }

    public void HandleClient(object obj)
    {
    _controlWriter.WriteLine("220 Service Ready.");
    _controlWriter.Flush();

    string line;
    while (!string.IsNullOrEmpty(line = _controlReader.ReadLine()))
    {
    string response = null;
    string[] command = line.Split(' ');
    string cmd = command[0].ToUpperInvariant();

    string arguments = command.Length > 1 ? line.Substring(command[0].Length + 1) : null;
    if (string.IsNullOrWhiteSpace(arguments)) arguments = null;

    Console.WriteLine("{0} {1}", cmd, arguments);

    if (response == null)
    {
    switch (cmd)
    {
    case "USER":
    response = User(arguments);
    break;
    case "PASS":
    response = Password(arguments);
    break;
    case "QUIT":
    response = "221 Goodbye";
    break;
    case "PWD":
    response = "257 \"/\" is current directory.";
    break;
    case "TYPE":
    string[] splitArgs = arguments.Split(' ');
    response = Type(splitArgs[0], splitArgs.Length > 1 ? splitArgs[1] : null);
    break;
    case "PASV":
    response = Passive();
    break;
    case "LIST":
    response = List(arguments);
    break;
    case "SIZE":
    response = FileSize(arguments);
    break;
    case "RETR":
    response = Retrieve(arguments);
    break;
    case "STOR":
    response = Store(arguments);
    break;
    case "CWD":
    response = "250 OK.";
    break;
    default:
    response = "502 Command not implemented";
    break;
    }
    }

    Console.WriteLine(response);
    _controlWriter.WriteLine(response);
    _controlWriter.Flush();
    if (response.StartsWith("221"))
    {
    break;
    }
    }

    _controlClient.Close();
    }

    private int CopyStream(Stream input, Stream output)
    {
    byte[] buffer = new byte[4096];
    int count = 0;
    int total = 0;
    while ((count = input.Read(buffer, 0, buffer.Length)) > 0)
    {
    output.Write(buffer, 0, count);
    total += count;
    }
    return total;
    }


    #region FTP commands
    private string User(string arguments)
    {
    return "331 OK.";
    }

    private string Password(string arguments)
    {
    return "230 OK.";
    }

    private string Store(string pathname)
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    if (pathname == null)
    {
    pathname = string.Empty;
    }
    string path = NormalizeFilename(pathname);

    _passiveListener.BeginAcceptTcpClient(DoStore, path);
    return string.Format("150 Opening {0} mode data transfer for STOR", _dataConnectionType);
    }
    return "450 Requested file action not taken";
    }

    private void DoStore(IAsyncResult ar)
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    TcpClient dataClient = _passiveListener.EndAcceptTcpClient(ar);
    string pathname = (string)ar.AsyncState;
    using (FileStream fs = new FileStream(pathname, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    using (NetworkStream dataStream = dataClient.GetStream())
    {
    CopyStream(dataStream, fs);
    dataClient.Close();
    dataClient = null;
    _controlWriter.WriteLine("226 Closing data connection, file transfer successful");
    _controlWriter.Flush();
    }

    }
    }

    private string Retrieve(string pathname)
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    if (pathname == null)
    {
    pathname = string.Empty;
    }
    string path = NormalizeFilename(pathname);
    if (File.Exists(path))
    {
    _passiveListener.BeginAcceptTcpClient(DoRetrieve, path);
    return string.Format("150 Opening {0} mode data transfer for RETR", _dataConnectionType);
    }

    }
    return "450 Requested file action not taken";
    }

    private void DoRetrieve(IAsyncResult ar)
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    TcpClient dataClient = _passiveListener.EndAcceptTcpClient(ar);
    string pathname = (string)ar.AsyncState;
    using (FileStream fs = new FileStream(pathname, FileMode.Open, FileAccess.Read))
    using (NetworkStream dataStream = dataClient.GetStream())
    {
    CopyStream(fs, dataStream);
    dataClient.Close();
    dataClient = null;
    _controlWriter.WriteLine("226 Closing data connection, file transfer successful");
    _controlWriter.Flush();
    }

    }
    }

    private string FileSize(string filename)
    {
    string filepath = NormalizeFilename(filename);
    long filelen = 0;
    if (File.Exists(filepath))
    {
    FileInfo info = new FileInfo(filepath);
    filelen = info.Length;
    }

    return string.Format("213 {0}", filelen);
    }

    private string List(string pathname)
    {
    if (pathname == null)
    {
    pathname = string.Empty;
    }
    pathname = new DirectoryInfo(Path.Combine(_currentDirectory, pathname)).FullName;

    if (Directory.Exists(pathname))
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    _passiveListener.BeginAcceptTcpClient(DoList, pathname);
    }
    return string.Format("150 Opening {0} mode data transfer for LIST", _dataConnectionType);
    }

    return "450 Requested file action not taken";
    }


    private void DoList(IAsyncResult result)
    {
    if (_dataConnectionType == DataConnectionType.Passive)
    {
    TcpClient dataClient = _passiveListener.EndAcceptTcpClient(result);
    string pathname = (string)result.AsyncState;
    using (NetworkStream dataStream = dataClient.GetStream())
    {
    StreamReader dataReader = new StreamReader(dataStream, Encoding.ASCII);
    StreamWriter dataWriter = new StreamWriter(dataStream, Encoding.ASCII);

    IEnumerable<string> directories = Directory.EnumerateFileSystemEntries(pathname);
    foreach (string dir in directories)
    {

    DirectoryInfo d = new DirectoryInfo(dir);

    string date = d.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
    d.LastWriteTime.ToString("MM dd yyyy") :
    d.LastWriteTime.ToString("MM dd HH:mm");

    string fullmode = "rwxrwxrwx";
    string filesize = "4096";
    if (!IsDir(dir))
    {
    fullmode = "-" + fullmode;
    FileInfo info = new FileInfo(dir);
    filesize = string.Format("{0}", info.Length);
    }
    else
    {
    fullmode = "d" + fullmode;
    }
    string line = string.Format("{3} 1 user group {0,8} {1} {2}", filesize, date, d.Name, fullmode);

    dataWriter.WriteLine(line);
    dataWriter.Flush();
    }

    dataClient.Close();
    dataClient = null;

    _controlWriter.WriteLine("226 Transfer complete");
    _controlWriter.Flush();
    }
    }
    }

    private string Passive()
    {
    IPAddress localAress = ((IPEndPoint)_controlClient.Client.LocalEndPoint).Address;
    _passiveListener = new TcpListener(localAress, 0);
    _passiveListener.Start();

    IPEndPoint localEndpoint = ((IPEndPoint)_passiveListener.LocalEndpoint);
    byte[] address = localAress.GetAddressBytes();
    short port = (short)localEndpoint.Port;

    byte[] portArray = BitConverter.GetBytes(port);
    if (BitConverter.IsLittleEndian)
    Array.Reverse(portArray);

    return string.Format("227 Entering Passive Mode ({0},{1},{2},{3},{4},{5})",
    address[0], address[1], address[2], address[3], portArray[0], portArray[1]);
    }

    private string Type(string typeCode, string formatControl)
    {
    string response = "500 ERROR";
    switch (typeCode)
    {
    case "A":
    case "I":
    response = "200 OK";
    _transferType = typeCode;
    break;
    case "E":
    case "L":
    default:
    response = "504 Command not implemented for that parameter.";
    break;
    }

    if (formatControl != null)
    {
    switch (formatControl)
    {
    case "N":
    response = "200 OK";
    break;
    case "T":
    case "C":
    default:
    response = "504 Command not implemented for that parameter.";
    break;
    }
    }

    return response;
    }

    #endregion

    private string NormalizeFilename(string path)
    {
    if (path == null)
    {
    path = string.Empty;
    }

    if (path == "/")
    {
    return _currentDirectory;
    }
    else if (path.StartsWith("/"))
    {
    path = new FileInfo(Path.Combine(_currentDirectory, path.Substring(1))).FullName;
    }
    else
    {
    path = new FileInfo(Path.Combine(_currentDirectory, path)).FullName;
    }

    return path;
    }

    public static bool IsDir(string filepath)
    {
    FileInfo fi = new FileInfo(filepath);
    if ((fi.Attributes & FileAttributes.Directory) != 0)
    return true;
    else
    {
    return false;
    }
    }

    }
    }