jborean93 / smbprotocol

Python SMBv2 and v3 Client
MIT License
322 stars 73 forks source link

Randomly getting a SMBAuthenticationError #287

Open pbuchholz123 opened 2 months ago

pbuchholz123 commented 2 months ago

Hello,

firstly, great lib. But we kinda face a problem we can't identify... So let me give u some context.

We have a python script running inside a docker container observing a local directory inside that container. If something goes in that directory, the script does different things. That script observes that directory 24/7 and one action is to copy the content of that local directory to an SMB share (Win 2k16 File Server). That whole process works like charm for exactly 7 days. After 7 days, we receive the following error/s in our logs...

{"log":"Traceback (most recent call last):\n","stream":"stderr","time":"2024-07-01T07:07:05.925727243Z"}
{"log":"  File \"/app/__main__.py\", line 81, in run_task\n","stream":"stderr","time":"2024-07-01T07:07:05.92591959Z"}
{"log":"    result = action_function(spool, step)\n","stream":"stderr","time":"2024-07-01T07:07:05.925933336Z"}
{"log":"             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.925941642Z"}
{"log":"  File \"/app/actions.py\", line 148, in save_string_to_file_pdf\n","stream":"stderr","time":"2024-07-01T07:07:05.925949237Z"}
{"log":"    if not exists_function(target_dir):\n","stream":"stderr","time":"2024-07-01T07:07:05.925957195Z"}
{"log":"           ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.925964997Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/our_package/smbinterface.py\", line 94, in exists\n","stream":"stderr","time":"2024-07-01T07:07:05.926060623Z"}
{"log":"    return smbclientpath.exists(path)\n","stream":"stderr","time":"2024-07-01T07:07:05.926073998Z"}
{"log":"           ^^^^^^^^^^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.926081953Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/path.py\", line 20, in exists\n","stream":"stderr","time":"2024-07-01T07:07:05.926089868Z"}
{"log":"    return _exists(path, False, True, **kwargs)\n","stream":"stderr","time":"2024-07-01T07:07:05.926097957Z"}
{"log":"           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.926105813Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/path.py\", line 134, in _exists\n","stream":"stderr","time":"2024-07-01T07:07:05.926113564Z"}
{"log":"    stat(path, follow_symlinks=follow_symlinks, **kwargs)\n","stream":"stderr","time":"2024-07-01T07:07:05.926121627Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/_os.py\", line 592, in stat\n","stream":"stderr","time":"2024-07-01T07:07:05.926129475Z"}
{"log":"    raw = SMBRawIO(\n","stream":"stderr","time":"2024-07-01T07:07:05.92614111Z"}
{"log":"          ^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.926152175Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/_io.py\", line 362, in __init__\n","stream":"stderr","time":"2024-07-01T07:07:05.926163445Z"}
{"log":"    tree, fd_path = get_smb_tree(path, **kwargs)\n","stream":"stderr","time":"2024-07-01T07:07:05.926177662Z"}
{"log":"                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.926189426Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/_pool.py\", line 304, in get_smb_tree\n","stream":"stderr","time":"2024-07-01T07:07:05.92620187Z"}
{"log":"    session = register_session(\n","stream":"stderr","time":"2024-07-01T07:07:05.926214589Z"}
{"log":"              ^^^^^^^^^^^^^^^^^\n","stream":"stderr","time":"2024-07-01T07:07:05.926240735Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbclient/_pool.py\", line 422, in register_session\n","stream":"stderr","time":"2024-07-01T07:07:05.926253544Z"}
{"log":"    session.connect()\n","stream":"stderr","time":"2024-07-01T07:07:05.926265981Z"}
{"log":"  File \"/usr/local/lib/python3.12/site-packages/smbprotocol/session.py\", line 284, in connect\n","stream":"stderr","time":"2024-07-01T07:07:05.926276963Z"}
{"log":"    raise SMBAuthenticationError(f\"Failed to authenticate with server: {err}\") from err\n","stream":"stderr","time":"2024-07-01T07:07:05.926289911Z"}
{"log":"smbprotocol.exceptions.SMBAuthenticationError: Failed to authenticate with server: SpnegoError (1): SpnegoError (16): Operation not supported or available, Context: Retrieving NTLM store without NTLM_USER_FILE set to a filepath, Context: Unable to negotiate common mechanism\n","stream":"stderr","time":"2024-07-01T07:07:05.926302483Z"}
{"log":"\n","stream":"stderr","time":"2024-07-01T07:07:05.926316048Z"}
{"log":"During handling of the above exception, another exception occurred:\n","stream":"stderr","time":"2024-07-01T07:07:05.926326587Z"}
{"log":"\n","stream":"stderr","time":"2024-07-01T07:07:05.926337706Z"}
{"log":"Traceback (most recent call last):\n","stream":"stderr","time":"2024-07-01T07:07:05.926353751Z"}

Extracted traceback

Traceback (most recent call last):
  File "/app/__main__.py", line 81, in run_task
    result = action_function(spool, step)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/actions.py", line 148, in save_string_to_file_pdf
    if not exists_function(target_dir):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/our_package/smbinterface.py", line 94, in exists
    return smbclientpath.exists(path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/smbclient/path.py", line 20, in exists
    return _exists(path, False, True, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/smbclient/path.py", line 134, in _exists
    stat(path, follow_symlinks=follow_symlinks, **kwargs)
  File "/usr/local/lib/python3.12/site-packages/smbclient/_os.py", line 592, in stat
    raw = SMBRawIO(
          ^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/smbclient/_io.py", line 362, in __init__
    tree, fd_path = get_smb_tree(path, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/smbclient/_pool.py", line 304, in get_smb_tree
    session = register_session(
              ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/smbclient/_pool.py", line 422, in register_session
    session.connect()
  File "/usr/local/lib/python3.12/site-packages/smbprotocol/session.py", line 284, in connect
    raise SMBAuthenticationError(f"Failed to authenticate with server: {err}") from err
smbprotocol.exceptions.SMBAuthenticationError: Failed to authenticate with server: SpnegoError (1): SpnegoError (16): Operation not supported or available, Context: Retrieving NTLM store without NTLM_USER_FILE set to a filepath, Context: Unable to negotiate common mechanism

During handling of the above exception, another exception occurred:

Traceback (most recent call last):

The script continues running but throws that error over and over again. I have to restart the container, and it works again for the next 7 days...

Script runs with python version 3.12, smbprotocol version is 1.13.0

Now the weird part.. We have another python script running inside a container doing literally the same thing as described above. Copying a local file to a SMB share. But this script runs with python version 3.10 and smbprotocol 1.9.0. But here we don't have this problem.

Anyone else having this problem?

jborean93 commented 2 months ago

Are you by chance using Kerberos auth in your test environment? Have you set any credentials with the ClientConfig(). It sounds like the session is missing for this particular server and user and so it's trying to create a new one. SMB authentication by default tries to use Kerberos if available with a fallback to NTLM. In this case there is no username/password specified so if Kerberos fails then the NTLM fallback fails with that particular error.

pbuchholz123 commented 2 months ago

Hello,

no, i don´t use Kerberos. I am not using the ClientConfig() object, i use it directly like this:

import smbclient

[...]

smbclient.register_session(server, username=username, password=password)

I will try giving ntlm as the auth_protocol in the register_session method, maybe it will help. Unfortunately i will have to wait a week to give some feedback :/

Cheers

pbuchholz123 commented 2 months ago

Hey,

it did not fix the problem. Sadly. Any other ideas?

Cheers

jborean93 commented 2 months ago

In your stacktrace I can see that the entrypoint into this module is the below with just the path being set.

smbclientpath.exists(path)

This ultimately leads into get_smb_tree being called with the username and password set to the default of None. The pooling behaviour will perform some DFS lookups if necessary but ultimately it will call register_session with in your case the username and password set to the input username and password unless they are None in which case they are pulled from the default ClientConfig() values. The register_session function will check if there is an existing connection/session present and if not will create a new one for you. As there is no username/password set in this case it's going to try and authenticate with the builtin cache and for NTLM that does not exist causing the failure.

One of the extra checks that the connection cache does is check if the connection transport is connected. My guess is that after 7 days, the server is going to reset the TCP socket causing the smbclient connection thread to detect the connection is closed and mark it so. The next time that connection is used in register_session it will be seen as unavailable and a new connection will be made. In your case this happens automatically but as there is no credential provided for the session it fails.

What you can do is set the username/password as the default value to use for all enw connections and just remove the smbclient.register_session(server, username=username, password=password) call. For example

import smbclient

# Registers the global user/pass to use for all connections
smbclient.ClientConfig(username=username, password=password)

while True:
    ...
    smbclientpath.exists(path)

In the above case when the connection is closed after 7 days, the new connection will automatically use the username/password provided on the client config rather than none at all.

pbuchholz123 commented 1 month ago

Hey,

sorry for the late response. Giving credentials in the ClientConfig rather than in the register_session worked perfetcly.

No more errors.

But one more question. How do i accomplish that having two servers i have to connect to. Both servers have different credentials..

Let´s say: Server1 with User1 and Password1 and Server2 with User2 and Password2

With the register_session i was able to register multiple sessions for that smbclient. How do i do that with the ClientConfig?

Cheers

jborean93 commented 1 month ago

You cannot, the only safe way of doing that is to provide explicit credentials in the actual SMB function, in your case ‘smbclient.path.exists(path, username=username, password=password)`. Anything else is either a global setting or prone to this problem where a session is dropped over time.