TalAloni / SMBLibrary

Free, Open Source, User-Mode SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client library
GNU Lesser General Public License v3.0
712 stars 182 forks source link

Problem getting folder list #24

Closed vishwas-trivedi closed 5 years ago

vishwas-trivedi commented 5 years ago

Hello @TalAloni,

I'm trying to get the list of shared folders from SMB server using following code but I'm getting "STATUS_USER_SESSION_DELETED" from ListShares() :

var client = new SMB2Client();
var success = client.Connect(System.Net.IPAddress.Parse("172.21.1.41"), 
    SMBTransportType.DirectTCPTransport);
// Success
if (success)
{
    var status = client.Login(String.Empty, "CE", "pass");
    // Success
    if (status == NTStatus.STATUS_SUCCESS)
    {
        var shares = client.ListShares(out var actionStatus);
    // **FAILURE : SMBLibrary.NTStatus.STATUS_USER_SESSION_DELETED**
        foreach (var item in shares)
        {
            Console.WriteLine(item);
        }
    }
}

What I'm trying to do here is get the list of folders from SMB and if folder does not exists then create a new one.

Actual path from where I'm want to get directory list is as following : \172.21.1.41\Data_test$\

I've also attached the WireShark packet capture and I'm using the latest version(1.3.7.0). smbSpecific_20190419_1.zip Please confirm.

Any help would be much appreciated. Regards.

TalAloni commented 5 years ago

Hi, STATUS_USER_SESSION_DELETED means that the SessionID being used by the client is invalid. The SessionID is established during SESSION_SETUP, the process is defined in [MS-SMB2] 3.3.5.5.1 Authenticating a New Session: "A session object MUST be allocated for this request. [..] The SMB2 server MUST reserve -1 as an invalid SessionId and 0 as a SessionId for which no session exists."

When looking at the packet capture, it seems to me that you are using a server that does not conform to the specifications: upon receiving the first SESSION_SETUP request, your server return SESSION_SETUP response with SessionID set to 0!

(Only the second SESSION_SETUP response returned by your server contains a valid SessionID, but the specification says that the first one should already include it and that's what the client uses)

IIRC this was a bug in earlier versions of SAMBA and it was later fixed, you can either update your server to match the specifications, or you can modify the client to match this buggy behavior by moving line 162 in SMB2Client.cs to after the second response is received.

TalAloni commented 5 years ago

And also, ListShares will return the list of shares, the client cannot create a new share, it can only create a folder inside a share.

vishwas-trivedi commented 5 years ago

@TalAloni Thank you very much for the response. As I don't have control over the SMB server, I will try modifying SMB2Client's code.

And also, ListShares will return the list of shares, the client cannot create a new share, it can only create a folder inside a share.

I would be really really grateful if you could share sample code for getting list of folders from share and create and delete folder from share. (ex. : \172.21.1.41\Data_test$)

Regards, Vishwas

TalAloni commented 5 years ago

You will find a few samples here: https://github.com/TalAloni/SMBLibrary/issues/9

vishwas-trivedi commented 5 years ago

@TalAloni Thank you! I will try to use those samples.

Sorry to ask again, but could you please share any official link or any document to the bug that that you mentioned.

Regards, Vishwas

TalAloni commented 5 years ago

I'm not able to locate it, is this indeed a SAMBA server?

TalAloni commented 5 years ago

The official MS-SMB2 documentation: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5606ad47-5ee0-437a-817e-70c366052962

vishwas-trivedi commented 5 years ago

@TalAloni

It probably is, but I'm not sure as I don't have access to it. But I was able to get shares list after changing the code that you suggested.

Just wanted to confirm what I understood : To login, client sends 2 requests :

  1. Contains negotiation message
  2. Contains authentication message.

Now, according to the protocol both requests should contain session ID. But due to this bug on SMB server, negotiation request(1) has session ID 0 instead of actual session ID. So to fix it on client side(in my case) we have to take session ID from authentication request.

Is this understanding correct??

I will also confirm the official documentation just in case. Regards, Vishwas

TalAloni commented 5 years ago

Not exactly, first the client send a negotiation request and get a negotiation response from the server, then the client send the first authentication request and get an initial authentication response from the server, then the client send another authentication request and get a final authentication response from the server. The first authentication response (SESSION_SETUP response) should already contain the SessionID. On your server the first SESSION_SETUP response has SessionID set to 0.

vishwas-trivedi commented 5 years ago

@TalAloni Okay, understood what you are saying but SMB2Client.Login() calls TrySendCommand() 2 times, 1st time with negotiateMessage(Line 149) and 2nd time with authenticateMessage(Line 165).

Both the calls are made with SMB2CommandName.SessionSetup (Line 151 and Line 167)

Am I misunderstanding the code somewhere?

vishwas-trivedi commented 5 years ago

@TalAloni

Also, I checked the sample code but I could not find any sample to get directory list. Is there any way I could get the directory list, create or delete directory?

That example is for creating file I believe.

Regards, Vishwas

TalAloni commented 5 years ago

Don't confuse between a NEGOTIATE request and a SESSION_SETUP request containing a negotiate message, those are two different things. the first negotiate the protocol and the second negotiate the authentication. The two SESSION_SETUP requests are sent in Line 150 and line 166 (in SMB2Client.cs)

To get a directory list you use QueryDirectory, the sample code contain an example. Creating a directory is very similar to creating a file, a single parameter needs to be changed, you'll figure it out.

TalAloni commented 5 years ago

To delete a file / directory you use SetFileInformation and set the FileDispositionInformation.

vishwas-trivedi commented 5 years ago

@TalAloni Thank you for the response!

To delete a file / directory you use SetFileInformation and set the FileDispositionInformation.

I was successfully able to create the folder and display the list of folder but I could not understand how to delete folder. I'm tried following code to delete a folder :

bool DeleteFolder(SMB2Client client, string folderName)
        {
            var res = false;
            // SUCCESS
            if (client.TreeConnect("Data_test$", out var status) is SMB2FileStore fileStore)
            {
                status = fileStore.CreateFile(out var handle, out var fs, folderName,
                    AccessMask.SYNCHRONIZE | (AccessMask)DirectoryAccessMask.GENERIC_READ | (AccessMask)DirectoryAccessMask.DELETE , 0,
                    ShareAccess.Read | ShareAccess.Delete, CreateDisposition.FILE_OPEN | CreateDisposition.FILE_CREATE,
                    CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT | CreateOptions.FILE_DIRECTORY_FILE , null);

                if (status == NTStatus.STATUS_SUCCESS)
                {  
                    // FAILURE : STATUS_NOT_SUPPORTED
                    status = fileStore.GetFileInformation(out var data, handle, FileInformationClass.FileDispositionInformation);
                    fileStore.CloseFile(handle);

                    if (status == NTStatus.STATUS_SUCCESS)
                    {
                        status = fileStore.SetFileInformation(handle, data);
                    }
                }
            }

            if (status == NTStatus.STATUS_SUCCESS)
            {
                res = true;
            }

            Console.WriteLine("----------Delete Folder----------");
            Console.WriteLine("NT Status : {0}", status.ToString());
            Console.WriteLine("----------Delete Folder----------");
            return res = true;
        }

I don't know what to set in FileDispositionInformation, so I tried reading FileDisposeInformation from server but I'm getting STATUS_NOT_SUPPORTED response.

Am I doing something wrong ? Is there any other way to delete the folder?

Regards, Vishwas Trivedi

TalAloni commented 5 years ago

There are 3 steps to deleting a file:

  1. You use CreateFile to obtain a handle.
  2. You use SetFileInformation with FileDispositionInformation (you set the DeletePending parameter to True).
  3. You close the handle.

You should not use GetFileInformation with FileDispositionInformation! it's invalid.

vishwas-trivedi commented 5 years ago

@TalAloni

Thank you for the response. I tried what you suggested and now my code looks like this :

static void DeleteFolder(SMB2Client client, string folderName)
        {
            if (client.TreeConnect("Data_test$", out var status) is SMB2FileStore fileStore)
            {
                status = fileStore.CreateFile(out var handle, out var fs, folderName,
                    AccessMask.SYNCHRONIZE | (AccessMask)DirectoryAccessMask.GENERIC_ALL, 0,
                    ShareAccess.Read | ShareAccess.Delete, CreateDisposition.FILE_OPEN | CreateDisposition.FILE_CREATE,
                    CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT | CreateOptions.FILE_DIRECTORY_FILE, null);

                if (status == NTStatus.STATUS_SUCCESS)
                {
                    var disppose = new FileDispositionInformation();
                    disppose.DeletePending = true;
                    status = fileStore.SetFileInformation(handle, disppose);
                    fileStore.CloseFile(handle);
                }
            }

            Console.WriteLine("----------Delete Folder----------");
            Console.WriteLine("NT Status : {0}", status.ToString());
            Console.WriteLine("----------Delete Folder----------");
        }

But I'm getting NT Status : STATUS_SHARING_VIOLATION from CreateFile()(I was able to delete in debug mode only one time).

Am I doing it wrong, I have given DirectoryAccessMask.GENERIC_ALL with CreateDisposition.FILE_OPEN | CreateDisposition.FILE_CREATE?

Regards, Vishwas Trivedi

vishwas-trivedi commented 5 years ago

@TalAloni

Sorry for the trouble, it was a stupid mistake! I was not closing the file after creation. Thank you for the help!

Regards, Vishwas Trivedi

FelipeTavaresc commented 6 months ago

@vishwas-trivedi Hi, can you show me the code that create a folder, please?

TalAloni commented 6 months ago

Same code as creating a file but with FILE_DIRECTORY_FILE flag, please don't hijack a closed issue.

FelipeTavaresc commented 6 months ago

Oh, Sorry. Thanks.