Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
Hi,
I´ve tried to get snapshot with socket http request
Code: function SnapshotCamaraIP2()
local IP = "192.168.1.100"
local UrlCamara = 'http://user:pass@'..IP..'/ISAPI/Streaming/channels/101/picture'
local response
require('socket.http')
socket.http.TIMEOUT = 5
response,err = socket.http.request(UrlCamara)
log(response,err)
return response
end
And response is:
Code: * arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
* arg: 2
* number: 401
If I put this url in explorer it works.
Thanks
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Are you sure that this uses Basic auth but not digest?
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(13.03.2023, 12:51)admin Wrote: Are you sure that this uses Basic auth but not digest?
I´ll try it with digest first. I wroute in the post "Email with attachement" but you tell me to try with basic auth. But this post is very old and I create a new more specific.
Trying with digest auth this is the result:
Code: * arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
<!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
* arg: 2
* number: 401
* arg: 3
* table:
["server"]
* string: webserver
["content-type"]
* string: text/html
["connection"]
* string: close
["date"]
* string: Mon, 13 Mar 2023 10:37:01 GMT
["x-frame-options"]
* string: SAMEORIGIN
["www-authenticate"]
* string: Digest qop="auth", realm="IP Camera(F0849)", nonce="5a6d566c5a475a6d59324d365a474534596a67794d6d493d", stale="FALSE"
["content-length"]
* string: 178
And the code is:
Code: local url = 'http://user:Pass@'..IP..'/ISAPI/Streaming/channels/101/picture'
function SnapshotCamaraIP (url)
--Meter url de la camara http://admin:pass@"IP"/snapshot.jpeg
local skthttp = require('socket.http')
local skturl = require('socket.url')
local ltn12 = require('ltn12')
local md5sum = require('encdec').md5
local hash = function(...)
return md5sum(table.concat({...}, ':'))
end
local parse_header = function(header)
local result = {}
for key, value in (header .. ','):gmatch('(%w+)=(.-),') do
if value:sub(1, 1) == '"' then -- strip quotes
result[ key:lower() ] = value:sub(2, -2)
else
result[ key:lower() ] = value
end
end
return result
end
local make_digest_header = function(headers)
local digest = {}
for _, header in ipairs(headers) do
if not header.unquote then
header[ 2 ] = '"' .. header[ 2 ] .. '"'
end
digest[ #digest + 1 ] = header[ 1 ] .. '=' .. header[ 2 ]
end
return 'Digest ' .. table.concat(digest, ', ')
end
local _request = function(req)
if not req.url then
return nil, 'missing url'
end
local url = skturl.parse(req.url)
local user, password = url.user, url.password
--log(user,password)
if not user or not password then
return nil, 'missing credentials in url'
end
url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
req.url = skturl.build(url)
local source
if req.source then
local chunks = {}
local capture = function(chunk)
if chunk then
chunks[ #chunks + 1 ] = chunk
end
return chunk
end
local chunk_id = 0
source = function()
chunk_id = chunk_id + 1
return chunks[ chunk_id ]
end
req.source = ltn12.source.chain(req.source, capture)
end
local body, code, hdrs = skthttp.request(req)
--log(body, code, hdrs)
if code == 401 and hdrs['www-authenticate'] then
local ht = parse_header(hdrs['www-authenticate'])
log(ht)
if not ht.realm or not ht.nonce then
log("REALM")
return nil, 'missing realm/nonce from response'
end
if ht.qop ~= 'auth' then
log("QOP")
return nil, 'unsupported qop ' .. tostring(ht.qop)
end
if ht.algorithm and ht.algorithm:lower() ~= 'md5' then
log("MD5")
return nil, 'unsupported algo ' .. tostring(ht.algorithm)
end
local nc = '00000001'
local cnonce = string.format('%08x', os.time())
local uri = skturl.build({ path = url.path, query = url.query })
local method = req.method or 'GET'
local response = hash(
hash(user, ht.realm, password),
ht.nonce,
nc,
cnonce,
'auth',
hash(method, uri)
)
req.headers = req.headers or {}
local auth = {
{ 'username', user },
{ 'realm', ht.realm },
{ 'nonce', ht.nonce },
{ 'uri', uri },
{ 'cnonce', cnonce },
{ 'nc', nc, unquote = true },
{ 'qop', 'auth' },
{ 'algorithm', 'MD5' },
{ 'response', response },
}
if ht.opaque then
table.insert(auth, { 'opaque', ht.opaque })
end
req.headers.authorization = make_digest_header(auth)
if not req.headers.cookie and hdrs['set-cookie'] then
-- not really correct but enough for httpbin
local cookie = (hdrs['set-cookie'] .. ';'):match('(.-=.-)[;,]')
if cookie then
req.headers.cookie = '$Version: 0; ' .. cookie .. ';'
end
end
if req.source then
req.source = source
end
body, code, hdrs = skthttp.request(req)
end
return body, code, hdrs
end
local request = function(url)
local t = type(url)
if t == 'table' then
return _request(table.clone(url))
elseif t == 'string' then
local req = {}
local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req) })
return table.concat(req), code, headers
end
end
image, err, hdrs = request(url)
log(image, err, hdrs)
return image,err
end
Thanks
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
There might be some minor difference in the digest auth between the camera and the script. But it's impossible to tell what the difference is. Use basic auth instead.
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(14.03.2023, 08:46)admin Wrote: There might be some minor difference in the digest auth between the camera and the script. But it's impossible to tell what the difference is. Use basic auth instead.
I don´t understand. I put in the first post that basic auth doesnt´t work. I put the code.
What shoud I try?
Thanks
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Some cameras allow passing credentials as a GET parameter. See if this works for you:
Code: http = require('socket.http')
base64enc = require('encdec').base64enc
ip = '192.168.192.168'
auth = 'user:pass'
url = 'http://' .. ip .. '/ISAPI/Streaming/channels/101/picture?auth=' .. base64enc(auth)
res, err = http.request(url)
log(res, err)
Posts: 335
Threads: 75
Joined: Jun 2017
Reputation:
6
Hi,
for me this not working, just checking it.
log:
* arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
* arg: 2
* number: 401
Alex
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(14.03.2023, 16:35)admin Wrote: Some cameras allow passing credentials as a GET parameter. See if this works for you:
Code: http = require('socket.http')
base64enc = require('encdec').base64enc
ip = '192.168.192.168'
auth = 'user:pass'
url = 'http://' .. ip .. '/ISAPI/Streaming/channels/101/picture?auth=' .. base64enc(auth)
res, err = http.request(url)
log(res, err)
Same error:
Code: * arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
* arg: 2
* number: 401
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
See if this works with curl:
- Download curl for Windows: https://curl.se/windows/
- Open terminal, go to bin directory
- Run curl command, replace IP/USER/PASS as needed
- See if you get a correct camera snapshot as snapshot.jpeg in the bin directory
- If this works then provide the whole curl output that you get in the terminal window
Code: .\curl.exe -v 'http://IP/ISAPI/Streaming/channels/101/picture' --digest -u USER:PASS -o snapshot.jpeg
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(15.03.2023, 08:21)admin Wrote: See if this works with curl:
- Download curl for Windows: https://curl.se/windows/
- Open terminal, go to bin directory
- Run curl command, replace IP/USER/PASS as needed
- See if you get a correct camera snapshot as snapshot.jpeg in the bin directory
- If this works then provide the whole curl output that you get in the terminal window
Code: .\curl.exe -v 'http://IP/ISAPI/Streaming/channels/101/picture' --digest -u USER:PASS -o snapshot.jpeg
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Please provide it as text, not as a screenshot. Also send your camera password via PM so I can compare the results with the Lua library.
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(15.03.2023, 09:28)admin Wrote: Please provide it as text, not as a screenshot. Also send your camera password via PM so I can compare the results with the Lua library.
Code: PS C:\Users\david.grandes\Downloads\curl-7.88.1_2-win64-mingw\bin> .\curl.exe -v 'http://192.168.2.154/ISAPI/Streaming/channels/101/picture' --digest -u admin:<PASS> -o snapshot.jpeg
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 192.168.2.154:80...
* Connected to 192.168.2.154 (192.168.2.154) port 80 (#0)
* Server auth using Digest with user 'admin'
> GET /ISAPI/Streaming/channels/101/picture HTTP/1.1
> Host: 192.168.2.154
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Wed, 15 Mar 2023 10:29:53 GMT
< Server: webserver
< X-Frame-Options: SAMEORIGIN
< Content-Length: 178
< Content-Type: text/html
< Connection: close
< WWW-Authenticate: Digest qop="auth", realm="IP Camera(F0849)", nonce="5a575930597a67314d6a706c4e475178596a51784e673d3d", stale="FALSE"
<
0 178 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
* Closing connection 0
* Issue another request to this URL: 'http://192.168.2.154/ISAPI/Streaming/channels/101/picture'
* Hostname 192.168.2.154 was found in DNS cache
* Trying 192.168.2.154:80...
* Connected to 192.168.2.154 (192.168.2.154) port 80 (#1)
* Server auth using Digest with user 'admin'
> GET /ISAPI/Streaming/channels/101/picture HTTP/1.1
> Host: 192.168.2.154
> Authorization: Digest username="admin",realm="IP Camera(F0849)",nonce="5a575930597a67314d6a706c4e475178596a51784e673d3d",uri="/ISAPI/Streaming/channels/101/picture",cnonce="100632b1eb41b178e302a905c7861379",nc=00000001,response="945dfaa7af413e634b2a7070a45605f4",qop="auth"
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: image/jpeg; charset="UTF-8"
< Connection: close
< Content-Length:152478
<
{ [12240 bytes data]
100 148k 100 148k 0 0 201k 0 --:--:-- --:--:-- --:--:-- 201k
* Closing connection 1
PS C:\Users\david.grandes\Downloads\curl-7.88.1_2-win64-mingw\bin>
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Also send your camera password via PM so I can compare the results with the Lua library.
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(15.03.2023, 09:35)admin Wrote: Also send your camera password via PM so I can compare the results with the Lua library.
Done
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Thanks, here's a couple of things you can try:
1. Remove space between items in the Authorization header.
Replace:
Code: return 'Digest ' .. table.concat(digest, ', ')
With:
Code: return 'Digest ' .. table.concat(digest, ',')
2. Modify the items and their order in the header.
Replace:
Code: local auth = {
{ 'username', user },
{ 'realm', ht.realm },
{ 'nonce', ht.nonce },
{ 'uri', uri },
{ 'cnonce', cnonce },
{ 'nc', nc, unquote = true },
{ 'qop', 'auth' },
{ 'algorithm', 'MD5' },
{ 'response', response },
}
With:
Code: local auth = {
{ 'username', user },
{ 'realm', ht.realm },
{ 'nonce', ht.nonce },
{ 'uri', uri },
{ 'cnonce', cnonce },
{ 'nc', nc, unquote = true },
-- { 'algorithm', 'MD5' },
{ 'response', response },
{ 'qop', 'auth' },
}
3. Make the cnonce longer.
Replace:
Code: local cnonce = string.format('%08x', os.time())
With:
Code: local cnonce = md5sum(string.format('%08x', os.time()))
When setting cnonce manually I get the same header as curl does by applying change 1 and 2.
Posts: 237
Threads: 31
Joined: May 2018
Reputation:
2
(15.03.2023, 10:55)admin Wrote: Thanks, here's a couple of things you can try:
1. Remove space between items in the Authorization header.
Replace:
Code: return 'Digest ' .. table.concat(digest, ', ')
With:
Code: return 'Digest ' .. table.concat(digest, ',')
2. Modify the items and their order in the header.
Replace:
Code: local auth = {
{ 'username', user },
{ 'realm', ht.realm },
{ 'nonce', ht.nonce },
{ 'uri', uri },
{ 'cnonce', cnonce },
{ 'nc', nc, unquote = true },
{ 'qop', 'auth' },
{ 'algorithm', 'MD5' },
{ 'response', response },
}
With:
Code: local auth = {
{ 'username', user },
{ 'realm', ht.realm },
{ 'nonce', ht.nonce },
{ 'uri', uri },
{ 'cnonce', cnonce },
{ 'nc', nc, unquote = true },
-- { 'algorithm', 'MD5' },
{ 'response', response },
{ 'qop', 'auth' },
}
3. Make the cnonce longer.
Replace:
Code: local cnonce = string.format('%08x', os.time())
With:
Code: local cnonce = md5sum(string.format('%08x', os.time()))
When setting cnonce manually I get the same header as curl does by applying change 1 and 2.
It doesn´t work.
Code: * arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
<!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
I´ve found in the camera manual (Hikvision HWI-B140H ( https://www.hi-watch.eu/en-us/product/15...let-camera))
The autentification mode is:
Code: 9.17 Security
You can improve system security by setting security parameters.
9.17.1 Authentication
You can improve network access security by setting RTSP and WEB authentication.
Go to Configuration → System → Security → Authentication to choose authentication protocol
and method according to your needs.
RTSP Authentication
Digest and digest/basic are supported, which means authentication information is needed when
RTSP request is sent to the device. If you select digest/basic, it means the device supports
digest or basic authentication. If you select digest, the device only supports digest
authentication.
RTSP Digest Algorithm
MD5, SHA256 and MD5/SHA256 encrypted algorithm in RTSP authentication. If you enable the
digest algorithm except for MD5, the third-party platform might not be able to log in to the
device or enable live view because of compatibility. The encrypted algorithm with high strength
Network Camera User Manual
79
is recommended.
WEB Authentication
Digest and digest/basic are supported, which means authentication information is needed when
WEB request is sent to the device. If you select digest/basic, it means the device supports digest
or basic authentication. If you select digest, the device only supports digest authentication.
WEB Digest Algorithm
MD5, SHA256 and MD5/SHA256 encrypted algorithm in WEB authentication. If you enable the
digest algorithm except for MD5, the third-party platform might not be able to log in to the
device or enable live view because of compatibility. The encrypted algorithm with high strength
is recommended.
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
Can you provide remote access to the LM that can access the camera via ZeroTier? I'll send you my network id via PM.
Posts: 110
Threads: 10
Joined: Mar 2019
Reputation:
3
Very interesting topic. Subscribing.
Posts: 7762
Threads: 42
Joined: Jun 2015
Reputation:
447
The conclusion is that the digest auth script on LM is working correctly. It is recommended to create separate user that only has viewing access on the camera. It seems that the camera might block access for some reason (might be a different script with wrong credentials). But this has nothing to do with LM. Camera firmware upgrade might solve this issue.
|