diff --git a/nselib/smb.lua b/nselib/smb.lua
index 4dc21c770..553722891 100644
--- a/nselib/smb.lua
+++ b/nselib/smb.lua
@@ -132,9 +132,6 @@ command_names = {}
status_codes = {}
status_names = {}
-local mutexes = setmetatable({}, {__mode = "k"});
---local debug_mutex = nmap.mutex("SMB-DEBUG")
-
local TIMEOUT = 10000
---Wrapper around smbauth.add_account.
@@ -174,68 +171,6 @@ function get_overrides_anonymous(overrides)
end
end
-
----Returns the mutex that should be used by the current connection. This mutex attempts
--- to use the name, first, then falls back to the IP if no name was returned.
---
---@param smbstate The SMB object associated with the connection
---@return A mutex
-local function get_mutex(smbstate)
- local mutex_name = "SMB-"
- local mutex
-
--- if(nmap.debugging() > 0) then
--- return debug_mutex
--- end
-
- -- Decide whether to use the name or the ip address as the unique identifier
- if(smbstate['name'] ~= nil) then
- mutex_name = mutex_name .. smbstate['name']
- else
- mutex_name = mutex_name .. smbstate['ip']
- end
-
- if(mutexes[smbstate] == nil) then
- mutex = nmap.mutex(mutex_name)
- mutexes[smbstate] = mutex
- else
- mutex = mutexes[smbstate]
- end
-
- stdnse.print_debug(3, "SMB: Using mutex named '%s'", mutex_name)
-
- return mutex
-end
-
----Locks the mutex being used by this host. Doesn't return until it successfully
--- obtains a lock.
---
---@param smbstate The SMB object associated with the connection
---@param func A name to associate with this call (used purely for debugging
--- and logging)
-local function lock_mutex(smbstate, func)
- local mutex
-
- stdnse.print_debug(3, "SMB: Attempting to lock mutex [%s]", func)
- mutex = get_mutex(smbstate)
- mutex "lock"
- stdnse.print_debug(3, "SMB: Mutex lock obtained [%s]", func)
-end
-
----Unlocks the mutex being used by this host.
---
---@param smbstate The SMB object associated with the connection
---@param func A name to associate with this call (used purely for debugging
--- and logging)
-local function unlock_mutex(smbstate, func)
- local mutex
-
- stdnse.print_debug(3, "SMB: Attempting to release mutex [%s]", func)
- mutex = get_mutex(smbstate)
- mutex "done"
- stdnse.print_debug(3, "SMB: Mutex released [%s]", func)
-end
-
---Convert a status number from the SMB header into a status name, returning an error message (not nil) if
-- it wasn't found.
--
@@ -304,9 +239,7 @@ function disable_extended(smb)
smb['extended_security'] = false
end
---- Begins a SMB session, automatically determining the best way to connect. Also starts a mutex
--- with mutex_id. This prevents multiple threads from making queries at the same time (which breaks
--- SMB).
+--- Begins a SMB session, automatically determining the best way to connect.
--
-- @param host The host object
-- @return (status, smb) if the status is true, result is the newly crated smb object;
@@ -318,6 +251,8 @@ function start(host)
state['uid'] = 0
state['tid'] = 0
+ state['mid'] = 1
+ state['pid'] = math.random(32766) + 1
state['host'] = host
state['ip'] = host.ip
state['sequence'] = -1
@@ -341,17 +276,14 @@ function start(host)
return false, "SMB: Couldn't find a valid port to check"
end
- -- Initialize the accounts for logging on (note: this has to be outside the mutex, or things break)
+ -- Initialize the accounts for logging on
smbauth.init_account(host)
- lock_mutex(state, "start(1)")
-
if(port ~= 139) then
status, state['socket'] = start_raw(host, port)
state['port'] = port
if(status == false) then
- unlock_mutex(state, "start(1)")
return false, state['socket']
end
return true, state
@@ -360,15 +292,12 @@ function start(host)
status, state['socket'] = start_netbios(host, port)
state['port'] = port
if(status == false) then
- unlock_mutex(state, "start(2)")
return false, state['socket']
end
return true, state
end
- unlock_mutex(state, "start(3)")
-
return false, "SMB: Couldn't find a valid port to check"
end
@@ -447,8 +376,7 @@ function start_ex(host, negotiate_protocol, start_session, tree_connect, create_
return true, smbstate
end
---- Kills the SMB connection, closes the socket, and releases the mutex. Because of the mutex
--- being released, a script HAS to call stop before it exits, no matter why it's exiting!
+--- Kills the SMB connection and closes the socket.
--
-- In addition to killing the connection, this function will log off the user and disconnect
-- the connected tree, if possible.
@@ -466,8 +394,6 @@ function stop(smb)
logoff(smb)
end
- unlock_mutex(smb, "stop()")
-
stdnse.print_debug(2, "SMB: Closing socket")
if(smb['socket'] ~= nil) then
local status, err = smb['socket']:close()
@@ -721,9 +647,9 @@ local function smb_encode_header(smb, command, overrides)
(overrides['signature'] or 0), -- extra (signature)
(overrides['extra'] or 0), -- extra (unused)
(overrides['tid'] or smb['tid']), -- tid
- (overrides['pid'] or 12345), -- pid
+ (overrides['pid'] or smb['pid']), -- pid
(overrides['uid'] or smb['uid']), -- uid
- (overrides['uid'] or 0) -- mid
+ (overrides['mid'] or smb['mid']) -- mid
)
return header
@@ -1152,7 +1078,7 @@ function negotiate_protocol(smb, overrides)
end
-function start_session_basic(smb, log_errors, overrides)
+local function start_session_basic(smb, log_errors, overrides)
local i, err
local status, result
local header, parameters, data, domain
@@ -1161,6 +1087,7 @@ function start_session_basic(smb, log_errors, overrides)
local andx_command, andx_reserved, andx_offset, action
local os, lanmanager
local username, domain, password, password_hash, hash_type
+ local busy_count = 0
header = smb_encode_header(smb, command_codes['SMB_COM_SESSION_SETUP_ANDX'], overrides)
@@ -1188,7 +1115,7 @@ function start_session_basic(smb, log_errors, overrides)
0x0000, -- ANDX -- next offset
0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes
- 0x0000, -- Virtual circuit num
+ 0x0001, -- Virtual circuit num
smb['session_key'], -- The session key
#lanman, -- ANSI/Lanman password length
#ntlm, -- Unicode/NTLM password length
@@ -1269,17 +1196,30 @@ function start_session_basic(smb, log_errors, overrides)
return true
else
- -- This username failed, print a warning and keep going
- if(log_errors == nil or log_errors == true) then
- stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), get_status_name(status))
- end
+ -- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
+ if(status == 0xc00000d0) then
+ busy_count = busy_count + 1
- -- Go to the next account
- if(overrides == nil or overrides['username'] == nil) then
- smbauth.next_account(smb['host'])
- result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
+ if(busy_count > 9) then
+ return false, "SMB: ERROR: Server has too many active connections; giving up."
+ end
+
+ local backoff = math.random() * 10
+ stdnse.print_debug(1, "SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
+ stdnse.sleep(backoff)
else
- result = false
+ -- This username failed, print a warning and keep going
+ if(log_errors == nil or log_errors == true) then
+ stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), get_status_name(status))
+ end
+
+ -- Go to the next account
+ if(overrides == nil or overrides['username'] == nil) then
+ smbauth.next_account(smb['host'])
+ result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
+ else
+ result = false
+ end
end
end
end
@@ -1291,7 +1231,7 @@ function start_session_basic(smb, log_errors, overrides)
return false, username
end
-function start_session_extended(smb, log_errors, overrides)
+local function start_session_extended(smb, log_errors, overrides)
local i
local status, status_name, result, err
local header, parameters, data
@@ -1300,6 +1240,7 @@ function start_session_extended(smb, log_errors, overrides)
local andx_command, andx_reserved, andx_offset, action, security_blob_length
local os, lanmanager
local username, domain, password, password_hash, hash_type
+ local busy_count = 0
-- Set a default status_name, in case everything fails
status_name = "An unknown error has occurred"
@@ -1342,7 +1283,7 @@ function start_session_extended(smb, log_errors, overrides)
0x0000, -- ANDX -- next offset
0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes
- 0x0000, -- Virtual circuit num
+ 0x0001, -- Virtual circuit num
smb['session_key'], -- The session key
#security_blob, -- Security blob length
0x00000000, -- Reserved
@@ -1423,24 +1364,38 @@ function start_session_extended(smb, log_errors, overrides)
end -- Should we parse the parameters/data?
until status_name ~= "NT_STATUS_MORE_PROCESSING_REQUIRED"
- -- Display a message to the user, and try the next account
- if(log_errors == nil or log_errors == true) then
- stdnse.print_debug(1, "SMB: Extended login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), status_name)
+ -- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
+ if(status == 0xc00000d0) then
+ busy_count = busy_count + 1
+
+ if(busy_count > 9) then
+ return false, "SMB: ERROR: Server has too many active connections; giving up."
+ end
+
+ local backoff = math.random() * 10
+ stdnse.print_debug(1, "SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
+ stdnse.sleep(backoff)
+ else
+ -- Display a message to the user, and try the next account
+ if(log_errors == nil or log_errors == true) then
+ stdnse.print_debug(1, "SMB: Extended login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), status_name)
+ end
+
+ -- Go to the next account
+ if(overrides == nil or overrides['username'] == nil) then
+ smbauth.next_account(smb['host'])
+ result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
+ if(not(result)) then
+ return false, username
+ end
+ else
+ result = false
+ end
end
- -- Reset the user id and security_blob
+ -- Reset the user id
smb['uid'] = 0
- -- Go to the next account
- if(overrides == nil or overrides['username'] == nil) then
- smbauth.next_account(smb['host'])
- result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
- if(not(result)) then
- return false, username
- end
- else
- result = false
- end
end -- Loop over the accounts
if(log_errors == nil or log_errors == true) then
@@ -1666,50 +1621,67 @@ function create_file(smb, path, overrides)
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
local andx_command, andx_reserved, andx_offset
local oplock_level, fid, create_action, created, last_access, last_write, last_change, attributes, allocation_size, end_of_file, filetype, ipc_state, is_directory
+ local error_count = 0
- -- Make sure we have overrides
- overrides = overrides or {}
+ repeat
+ local mutex = nmap.mutex(smb['host'])
+ mutex "lock"
+
+ -- Make sure we have overrides
+ overrides = overrides or {}
+
+ header = smb_encode_header(smb, command_codes['SMB_COM_NT_CREATE_ANDX'], overrides)
+ parameters = bin.pack(" 10) then
+ return false, "SMB: ERROR: Server returned NT_STATUS_PIPE_NOT_AVAILABLE too many times; giving up."
+ end
+ stdnse.print_debug(1, "WARNING: Server refused connection with NT_STATUS_PIPE_NOT_AVAILABLE; trying again")
+ stdnse.sleep(.2)
+ end
+ until (status ~= 0xc00000ac)
- data = bin.pack("z", path)
-
- -- Send the create file
- stdnse.print_debug(2, "SMB: Sending SMB_COM_NT_CREATE_ANDX")
- local result, err = smb_send(smb, header, parameters, data, overrides)
- if(result == false) then
- return false, err
- end
-
- -- Read the result
- status, header, parameters, data = smb_read(smb, false)
- if(status ~= true) then
- return false, header
- end
-
- -- Check if we were allowed in
- local uid, tid
- pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("