Home AMX User Forum AMX General Discussion

HTTP commands of Dahua NVR

Hi guys,

I wanted to ask on here as im not sure whether this will be ok.
We are hoping to implement control of a Dahua NVR which has firmware on it to allow HTTP control of its onscreen display.
The document reckons the command for nav up would be;

http://admin:[email protected]:80/cgi-bin/keyBoardCtrl.cgi?setAction=Up

I have done other devices using HTTP Get type commands by connected to the device on say Port 80 TCP , on the button press,
then linking the command as per below on a button switch case;

CASE 1: SEND_STRING dvDevice_IP,"'GET //cgi-bin/keyBoardCtrl.cgi?setAction=Up HTTP/1.1',$0D,$0A,$0D,$0A"

Then disconnect the connection on release..

I have never had to supply credential before.

How do I go about providing credentials in the connect? at the moment my connects are basic;

DEFINE_CALL 'XBMC CONNECT'
{
IP_CLIENT_OPEN(dvXBMC_IP.port,XBMC_IP_IpAddress,XBMC_IP_Port,IP_TCP)
}

DEFINE_CALL 'XBMC DISCONNECT'
{
IP_CLIENT_CLOSE(dvXBMC_IP.port)
}

Any direction really appreciated..

Comments

  • You need to encode the user and password string rather than sending it in plain text. There is a good function for base 64 encoding on the forums if you have a look.

    The resulting string then needs to be sent as an Authorization header

    SEND_STRING dvDVR,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Up HTTP/1.1',13,10"
    SEND_STRING dvDVR,"'Authorization: Basic ',fnBase64('admin:admin'),13,10,13,10"

    Hopefully that should work.

    Simon
  • ericmedleyericmedley Posts: 4,177
    Keep in mind that the communication has to be managed by you. For example, when you send a rs232 string - all you do is "send string blah blah blah. the processor is handling all the protocols to actually do it under the hood. With IP communications you are required to do an additional step.
    The two steps are 1) establish the connection socket and 2) once the connection is established - send your message.

    So, you need to have a DATA_EVENT[dvXBMC_IP] on the IP port. In that data_event>online: is where you now know the IP Port is connected and it is now time to send your message.
  • Guys, thanks and also thanks to Chip Moody for his Base64 code. I had perfect control of the Dahua NVR onscreen display in the office using a NI3101SIG.
    I then took this code and the NVR to site and installed it onto the customers NI3100 and it wouldnt work......
    I can get the onscreen display to change by typing the commands into a browser and then manually inputting username and password into the prompt but I just cannot get it to work from Netlinx.
    Lots of head scratching and back onto it tomorrow I guess... I am wondering if 3.60 Duet firmware may be the issue as our 3101Sig is on 4.xx.. very strange..
  • John NagyJohn Nagy Posts: 1,733
    Telnet into the NI and PING the IP of the screen to see if you have comms at all. You may be on different subnets. Firmware is UNLIKELY the cause of this one.
  • Thanks for the tip John, I have pinged 192.168.1.238 (The NVR) from 192.168.1.200 (NI-3100 Master) and it looks OK.. (Aparently 192.168.1.238 is alive)
    I have checked the router and no VLANs are setup, very odd indeed.. The only difference between the controllers is our office unit a NI-3101-SIG on 192.168.0 range with Duet firmware of 4.1.373 and the customers is a NI-3100 on a 192.168.1 range with firmware of 3.60.453.

    			PROGRAM_NAME='DAHUA_NVR'
    
    			DEFINE_DEVICE
    
    			dvNVR_IP        =     0:10:1
    
    			dvTP_CCTV_1_1         =    10001:21:1
    			dvTP_CCTV_1_2         =    10002:21:1
    			dvTP_CCTV_1_3         =    10003:21:1
    			dvTP_CCTV_1_4         =    10004:21:1
    			dvTP_CCTV_1_5         =    10005:21:1
    			dvTP_CCTV_1_6         =    10006:21:1
    			dvTP_CCTV_1_7         =    10007:21:1
    			dvTP_CCTV_1_8         =    10008:21:1
    			dvTP_CCTV_1_9         =    10009:21:1
    			dvTP_CCTV_1_10        =    10010:21:1
    
    			DEFINE_CONSTANT
    
    			NVR_IP_IpAddress     =     '192.168.1.238'
    			NVR_IP_Port         =     80
    
    			DEFINE_VARIABLE
    
    			DEV dvTP_NVR[]=
    			{
    			dvTP_CCTV_1_1, dvTP_CCTV_1_2, dvTP_CCTV_1_3, dvTP_CCTV_1_4,
    			dvTP_CCTV_1_5, dvTP_CCTV_1_6, dvTP_CCTV_1_7, dvTP_CCTV_1_8,
    			dvTP_CCTV_1_9, dvTP_CCTV_1_10
    			}
    
    			iNVR_IP_Connected     =     0
    
    			DEFINE_EVENT
    
    			DATA_EVENT[dvNVR_IP]
    			{
    			    ONLINE:
    			    {
    			       iNVR_IP_Connected = 1
    			    }
    			    OFFLINE:
    			    {
    			      iNVR_IP_Connected = 0
    			    }
    			}
    
    
    			BUTTON_EVENT[dvTP_NVR,BUTTON.INPUT.CHANNEL]
    			{
    			PUSH:
    			   {
    			   fnNVRIPConnect()
    			   SWITCH(BUTTON.INPUT.CHANNEL)
    			   {
    			   CASE 1:    { //Up
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Up HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 2:    { //Down
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Down HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 3:    { //Left
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Left HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 4:    { //Right
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Right HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 5:    { //Enter
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Enter HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 6:    { //Escape
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/keyBoardCtrl.cgi?setAction=Escape HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 11:    { //Camera 1
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=0 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 12:    { //Camera 2
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=1 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 13:    { //Camera 3
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=2 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 14:    { //Camera 4
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=3 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 15:    { //Camera 5
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=4 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 16:    { //Camera 6
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=5 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 17:    { //Camera 7
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=6 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 18:    { //Camera 8
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=7 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 19:    { //Camera 9
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=8 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 20:    { //Camera 10
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=9 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 21:    { //Camera 11
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=10 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 22:    { //Camera 12
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=11 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 23:    { //Camera 13
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=12 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 24:    { //Camera 14
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=13 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 25:    { //Camera 15
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=14 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 26:    { //Camera 16
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split1&group=15 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 31:    { //Split4
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split4&group=0 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 32:    { //Split9
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split9&group=0 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   CASE 33:    { //Split16
    			           SEND_STRING dvNVR_IP,"'GET /cgi-bin/split.cgi?action=setMode&channel=0&mode=Split16&group=0 HTTP/1.1',13,10"
    			           SEND_STRING dvNVR_IP,"'Authorization: Basic ',fnBase64('remote:remote2017'),13,10,13,10"
    			           }
    			   }
    			   }
    			   RELEASE:
    			   {
    			   fnNVRIPDisconnect()
    			   }
    			}
    
    			DEFINE_FUNCTION fnNVRIPConnect()
    			{
    			IP_CLIENT_OPEN(dvNVR_IP.port,NVR_IP_IpAddress,NVR_IP_Port,IP_TCP)
    			}
    
    
    			DEFINE_FUNCTION fnNVRIPDisconnect()
    			{
    			IP_CLIENT_CLOSE(dvNVR_IP.port)
    			}
    
    			DEFINE_FUNCTION CHAR[255] fnBase64 (CHAR Source[])
    			{
    			 LOCAL_VAR CHAR    Uncode[255]      // Password protected sites require that the
    			 LOCAL_VAR CHAR    Coded[255]       // username & password get encoded together in
    			 LOCAL_VAR CHAR    Munch[3]         // Base64 format.  Don't know what Base64 is?
    			 LOCAL_VAR CHAR    Chew[4]          // Neither did I until I looked at
    			 LOCAL_VAR INTEGER Step             // http://freesoft.org/CIE/RFC/2065/56.htm
    			                                    // and http://freesoft.org/CIE/RFC/1521/7.htm
    			 Coded = ''
    			 Chew = '    '
    			 Uncode = Source
    			 WHILE (LENGTH_STRING(Uncode) >= 3)
    			 {
    			   Munch = GET_BUFFER_STRING(Uncode,3)
    			   Chew[1] = Munch[1]
    			   Chew[1] = Chew[1] / 4    
    			   Chew[2] = ((Munch[1] * 16) BAND $30)
    			   Chew[2] = Chew[2] + ((Munch[2] / 16) BAND $F)    
    			   Chew[3] = ((Munch[2]*4) BAND $3C)
    			   Chew[3] = Chew[3] + ((Munch[3] / 64) BAND $3)    
    			   Chew[4] = Munch[3] BAND $3F
    			   Coded = "Coded,Chew"
    			 }
    			 SELECT
    			 {
    			   ACTIVE (LENGTH_STRING(Uncode) = 2):
    			   {
    			     Munch = "GET_BUFFER_STRING(Uncode,2)"
    			     Chew[1] = Munch[1]
    			     Chew[1] = Chew[1] / 4      
    			     Chew[2] = ((Munch[1] * 16) BAND $30)
    			     Chew[2] = Chew[2] + ((Munch[2] / 16) BAND $F)        
    			     Chew[3] = ((Munch[2]*4) BAND $3C)      
    			     Chew[4] = 64
    			     Coded = "Coded,Chew"        
    			   }
    			   ACTIVE (LENGTH_STRING(Uncode) = 1):
    			   {
    			     Munch = GET_BUFFER_STRING(Uncode,1)
    			     Chew[1] = Munch[1]
    			     Chew[1] = Chew[1] / 4      
    			     Chew[2] = ((Munch[1] * 16) BAND $30)      
    			     Chew[3] = 64
    			     Chew[4] = 64
    			     Coded = "Coded,Chew"  
    			   }
    			 }
    			 FOR (Step = 1;Step <= LENGTH_STRING(Coded);Step++)
    			 {
    			   SELECT
    			   {
    			     ACTIVE (Coded[Step] <= 25): Coded[Step] = Coded[Step] + 65
    			     ACTIVE (Coded[Step] <= 51): Coded[Step] = Coded[Step] + 71
    			     ACTIVE (Coded[Step] <= 61): Coded[Step] = Coded[Step] - 4
    			     ACTIVE (Coded[Step] = 62):  Coded[Step] = '+'
    			     ACTIVE (Coded[Step] = 63):  Coded[Step] = '/'
    			     ACTIVE (coded[Step] = 64):  Coded[Step] = '='
    			   }
    			 }
    			 RETURN Coded
    			}
    
    



  • viningvining Posts: 4,368
    John Nagy wrote: »
    Telnet into the NI and PING the IP of the screen to see if you have comms at all. You may be on different subnets. Firmware is UNLIKELY the cause of this one.

    Firmware on the NVR might be a possibility but I agree not he master. I've come across several devices over the years that would not work because of the firmware variant used. Heck even when AMX came out with that audio system whose name escapes me they changed the communication baud rate between versions with out documenting that fact so the NVR could have changed many things from port or going from HTTP/1.0 to 1.1 or vice verse between versions.
  • I just visited site and amended the code to get some feedback.. I'm getting this back...

    Line 5 (14:17:29):: HTTP/1.1 401 Unauthorized$0D$0AWWW-Authenticate: Digest realm="Login to 1L04659PAMZVS6J",qop="auth",nonce="1659866321",opaque="9ceb3a49

    Does this mean digest type authentication? I defintately had it working perfectly in our office - this is driving me nuts =/
  • Just incase anyone else wants to control Dahua NVR's (To save them hassle), the NVR's that I have received ship with Basic Authentication and after being connected to the internet for 10 minutes, auto update their web servers to insist on Digest Authentication. They haven't confirmed this but I have seen it happen on three units now. If I send basic auth commands to it out of the box, it works for 10 minutes and voila... error messages.
    Have parsed digest info and sent back the correct credential and am controlling perfectly again...
    DEFINE_FUNCTION fnParseNVRBuffer()
    {
    WHILE (FIND_STRING (CnvrBuffer, 'WWW-Authenticate: Digest realm="', 1))
        {
        REMOVE_STRING (CnvrBuffer, 'WWW-Authenticate: Digest realm="', 1)
        cRealm=LEFT_STRING(CnvrBuffer, FIND_STRING(CnvrBuffer, '",', 1) -1)
        cRealmActual = "'"',cRealm,'"'"
        REMOVE_STRING (CnvrBuffer, 'qop="', 1)
        cQop=LEFT_STRING(CnvrBuffer, FIND_STRING(CnvrBuffer, '",', 1) -1)
        REMOVE_STRING (CnvrBuffer, 'nonce="', 1)
        cNonce = LEFT_STRING(CnvrBuffer, FIND_STRING(CnvrBuffer, '",', 1) -1)
        REMOVE_STRING (CnvrBuffer, 'opaque="', 1)
        cOpaque = LEFT_STRING(CnvrBuffer, FIND_STRING(CnvrBuffer, '"', 1) -1)
        A1 = "NVR_Username,':',cRealm,':',NVR_Password"
        A2 = "'GET:',cURI"
        fnGenRandom()
        MD5(A1,HA1)
        MD5(A2,HA2)
        TEMP = "HA1,':',cNonce,':',cQop,':',HA2"
        MD5("HA1,':',cNonce,':00000001:',cCNonce,':',cQop,':',HA2",Response)
        CLEAR_BUFFER CnvrBuffer
        fnSendResponse()
        } 
    }
    
  • andyh747andyh747 Posts: 1

    I know this is an old thread but it's the only place I could find the http commands for navigation on the NVR. However I've been unable to get the above navigation commands to work on the Dahua NVR I have. None of the navigation commands work but the commands for switching cameras work. If I send a "Right" command I get an Error Bad Request! response which implies the NVR doesn't understand the command.

    Can anyone help?

  • Hi,

    As in the previous post - I know it's an old thread, but I have also a Dahua NVR to control. The way to acquire information necessary for digest authorization posted by @mkleynhans is very helpful, I have, however, one problem. After NVR replies with all the stuff like cRealm, qop, nonce etc., it closes the connection and I can't respond to it with the appropriate credentials. If I open the connection again and send the string with the credentials from the previous session, I receive a response as follows:

    Line 401 2021-04-02 (08:57:18):: HTTP/1.1 401 Unauthorized$0D$0AConnection: close$0D$0AContent-type: text/plain;charset=utf-8$0D$0AContent-Length: 27$0D$0A$0D$0AError$0D$0AInvalid Authority!$0D$0A

    Can anyone give a piece of advice how to hadle it?

    Best regards,

  • If you can secure the control API, we will write a module for it. Their support channels keep sending me to Sales for the document and since I am not a customer, they have not been willing to share.

  • sling100sling100 Posts: 123

    @MichalMoraszczyk said:
    Hi,

    As in the previous post - I know it's an old thread, but I have also a Dahua NVR to control. The way to acquire information necessary for digest authorization posted by @mkleynhans is very helpful, I have, however, one problem. After NVR replies with all the stuff like cRealm, qop, nonce etc., it closes the connection and I can't respond to it with the appropriate credentials. If I open the connection again and send the string with the credentials from the previous session, I receive a response as follows:

    Line 401 2021-04-02 (08:57:18):: HTTP/1.1 401 Unauthorized$0D$0AConnection: close$0D$0AContent-type: text/plain;charset=utf-8$0D$0AContent-Length: 27$0D$0A$0D$0AError$0D$0AInvalid Authority!$0D$0A

    Can anyone give a piece of advice how to hadle it?

    Best regards,

    @MichalMoraszczyk you need to use MD5 encryption to send the correct digest credentials it has sent you back to the unit. I had to do digest auth recently for a Sony unit that wouldn't work in any other way.

    Have a look at this

    [https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation]

    It's actually pretty straightforward - and there is a MD5 include kicking around already if you do a search.

    Simon

  • Lots of http goodness to be found here

    https://github.com/DavidVine/amx-util-library

    @DavidVine shout out!

  • sling100sling100 Posts: 123

    @HARMAN_icraigie said:
    Lots of http goodness to be found here

    https://github.com/DavidVine/amx-util-library

    @DavidVine shout out!

    Well done Ian that's the one! For all MD5 requirements - very useful if you ever need to write a PJLink module.

  • @sling100 - thank you for your response. I'm using md5 encryption (btw - taken from github repository mentioned by Ian). @HARMAN_icraigie - thank you for the link to the repository.

    My current situation is following: I send the request to a NVR (without credentials) and I receive a response '401 Unauthorized', which triggers a following piece of code (under 'string' part of data_event):

    string:
    {
    send_string 0,sCameraRecorderBuffer;
    if(find_string(sCameraRecorderBuffer,'401 Unauthorized',1))
    {

        if(find_string(sCameraRecorderBuffer, 'WWW-Authenticate: Digest realm="', 1))
        {
        remove_string(sCameraRecorderBuffer, 'WWW-Authenticate: Digest realm="', 1);
        cRealm = left_string(sCameraRecorderBuffer, find_string(sCameraRecorderBuffer, '",', 1) -1);
        cRealmActual = "'"',cRealm,'"'";
        remove_string (sCameraRecorderBuffer, 'qop="', 1);
        cQop = left_string(sCameraRecorderBuffer, find_string(sCameraRecorderBuffer, '",', 1) -1);
        remove_string (sCameraRecorderBuffer, 'nonce="', 1);
        cNonce = left_string(sCameraRecorderBuffer, find_string(sCameraRecorderBuffer, '",', 1) -1);
        remove_string (sCameraRecorderBuffer, 'opaque="', 1);
        cOpaque = left_string(sCameraRecorderBuffer, find_string(sCameraRecorderBuffer, '"', 1) -1);
        sResponse = fnCameraRecorderResponse();
        CLEAR_BUFFER sCameraRecorderBuffer;
        fnStringToCameraRecorder();
        } 
    }
    }
    

    Function fnCameraRecorderResponse() which is used to build a response using the credentials received in the response from NVR looks as follows:

    define_function char[256] fnCameraRecorderResponse()
    {
    stack_var char sTempResponse[256];

    A1 = "sCameraRecorderUserName,':',cRealm,':',sCameraRecorderUserPass";
    A2 = "'GET:',cURI";
    HA1 = md5(A1);
    HA2 = md5(A2);
    cCNonce = fnGenerateCNonce();
    //TEMP = "HA1,':',cNonce,':',cQop,':',HA2"
    sTempResponse = md5("HA1,':',cNonce,':00000001:',cCNonce,':',cQop,':',HA2");
    
    return sTempResponse;
    

    }

    Whereas function fnGenerateCNonce() which calculates CNonce parameter looks as follows:

    define_function char[20] fnGenerateCNonce()
    {
    local_var char cCnonce[20];
    local_var char sTempHex[5];
    stack_var integer nLoopCounter;

    cCnonce = '';
    for(nLoopCounter = 1; nLoopCounter < 5; nLoopCounter++)
    {
    sTempHex = itohex(random_number(256));
    cCnonce = "cCnonce,sTempHex";
    }
    
    return cCnonce;
    

    }

    Finally, function fnStringToCameraRecorder() which is responsible for sending the request with credentials was built in a following way:

    define_function fnStringToCameraRecorder()
    {
    if(!nCameraRecorderIsOnline)
    {
    ip_client_open(dvCameraRecorder.port,sCameraRecorderIPAddress,nCameraRecorderIPPort,IP_TCP);
    }

    wait_until(nCameraRecorderIsOnline)
    {
        send_string dvCameraRecorder,"'GET ',cURI,' HTTP/1.1',$0D,$0A,'Host: ',sCameraRecorderIPAddress,$0D,$0A";
        send_string 0,"'GET ',cURI,' HTTP/1.1',$0D,$0A,'Host: ',sCameraRecorderIPAddress,$0D,$0A";
        send_string dvCameraRecorder,"'Authorization: Digest username:="',sCameraRecorderUserName,'", realm="',cRealm,'", nonce="',cNonce,'", uri="',cURI,'", qop=auth, nc=00000001, cnonce="',cCnonce,'"
        , response="',sResponse,'", opaque="',cOpaque,'"',$0D,$0A,$0D,$0A";
        send_string 0,"'Authorization: Digest username:="',sCameraRecorderUserName,'", realm="',cRealm,'", nonce="',cNonce,'", uri="',cURI,'", qop=auth, nc=00000001, cnonce="',cCnonce,'"
        , response="',sResponse,'", opaque="',cOpaque,'"',$0D,$0A,$0D,$0A";
    }
    }
    

    }

    The problem was that after triggering this function in the Diagnostics window I saw onerror event and a message that the string couldn't be sent (I don't remember how it looked precisely, but it was something like this). It looked like AMX master hadn't managed to send the string before the connection with NVR closed.

    I rebuilt the latter function to send the request with credentials, so that it waits until the connection is closed, then reopens it and sends the string as above. The response from NVR is in this case as below:

    Line 401 2021-04-02 (08:57:18):: HTTP/1.1 401 Unauthorized$0D$0AConnection: close$0D$0AContent-type: text/plain;charset=utf-8$0D$0AContent-Length: 27$0D$0A$0D$0AError$0D$0AInvalid Authority!$0D$0A

    It looks like the credentials from the previous session are not valid anymore.

    Perhaps someone has had a similar issue (or can see a bug in one of the functions).

    Thanks in advance for any advice.

    Best regards.

  • HTTP is not a maintained connection. You need to build and cache the packet, open the connection then send the cached packet once the connection is online.

    Sometimes you can sneak in the next message if the connection is still open from the previous message so the packet can be sent directly instead of being cached if the connection is still online.

  • sling100sling100 Posts: 123

    send_string dvCameraRecorder,"'Authorization: Digest username:="',sCameraRecorderUserName,'"

    Your issue is the colon after the username - it should be username= not username:=

    The rest of it looks correct though.

    Simon

  • I've had a go with Dahua kit and I constantly get Unauthorised as the response.

    Couple of things that you may want to look at though, other tha the thing that Simon spotted.

    Your final Hash value(sTempHash) needs to be ASCII, as it comes out here you have hex values in a string(I think) - this needs converting to ASCII - I did this, it still didnt work.
    I did a manual track of every step, and converted each separate section to MD5 and they all check out.

    The Realm I was supplied looked something like this

    "Login to ab518fe342718fe43a19eb" and I'm not sure if this needs to be shortened to just the hex string.

    Bit frustrating. I'm working on a couple of Dahua Intercoms - the SIP works fine but I can't open the gate because the bloody authentication doesn't work.

    HTTP/1.1 401 Unauthorized$0D$0AConnection: close$0D$0ASet-Cookie:secure; HttpOnly$0D$0AContent-type: text/plain;charset=utf-8$0D$0AContent-Length: 27$0D$0A$0D$0AError$0D$0AInvalid Authority!$0D$0A

    Duncan

  • I think I have this working. WIll double check it on Monday at site and I'll post the code

    Have a great weekend all

  • Duncan EllisDuncan Ellis Posts: 154
    edited April 2021

    @MichalMoraszczyk @HARMAN_Chris

    I've got this to work on a Dahua Intercom. Couple of things to bar in mind. It seems to have a max limit to the size of data that you can send to it in one go, otherwise you end up with a 400 error. The second is that you have to be careful that you convert all of your results to text before MD5ing the results - otherwise it doesnt work. Also,it expects all ASCII Hex as being lower case.
    I use the code in this thread plus the MD5 donated from the library listed above to shorten the development time.

    functions as follows:

    ///AUTHETICATION USING MD5 HASH

    DEFINE_FUNCTION CHAR[256] fnMD5DeviceChallengeResponse()
    {
    LOCAL_VAR CHAR sTempResponse[256]
    local_var char Stemptemp[500]
    local_var char stempascii[500]
    local_var char stemp[500]

    sMD5HashA1 = "sIntercomUserName,':',sMD5HashRealm,':',sIntercomUserPass"
    sMD5HashA2 = "'GET:',sGateURI"
    sMD5HashHA1 =lower_string(fnConvertMD5ResultToASCII( md5(sMD5HashA1)))
    sMD5HashHA2 =lower_string(fnConvertMD5ResultToASCII(md5(sMD5HashA2)))
    sMD5HashCNonce = fnGenerateMD5HashCNonce()

    sTempResponse =lower_string(fnConvertMD5ResultToASCII (md5("sMD5HashHA1,':',sMD5HashNonce,':00000001:',sMD5HashCNonce,':',sMD5HashQop,':',sMD5HashHA2")))

    RETURN sTempResponse
    }

    DEFINE_FUNCTION char[20] fnGenerateMD5HashCNonce()
    {
    LOCAL_VAR CHAR sCnonce[20]
    LOCAL_VAR CHAR sTempHex[5]
    STACK_VAR INTEGER nLoopCounter

    sCnonce = ''
    FOR(nLoopCounter = 1; nLoopCounter < 5; nLoopCounter++)
    {
    sTempHex = FORMAT('%02x',(RANDOM_NUMBER(256)))
    sCnonce = "sCnonce,sTempHex"
    }

    RETURN sCnonce
    }

    DEFINE_FUNCTION CHAR[100] fnConvertMD5ResultToASCII(CHAR sData[])
    {
    LOCAL_VAR INTEGER nCount
    LOCAL_VAR CHAR sConvStr[100]

    SET_LENGTH_STRING(sConvStr,0)

    FOR (nCount = 1 ; nCount<=LENGTH_STRING(sData) ; nCount++)
    {
    sConvStr = "sConvStr,FORMAT('%02x',(sData[nCount]))"
    }
    RETURN(sConvStr)

    I sent the intercom a standard header to which it responded:

    HTTP/1.1 401 Unauthorized$0D$0AWWW-Authenticate: Digest realm="Login to d3a7279ebfcb50c5c150db97af437b9f",
    qop="auth", nonce="1899237514", opaque="787cda0d411d2f1281242c16ef4613f2928c01b4"$0D$0AConnection:
    close$0D$0ASet-Cookie:secure; HttpOnly$0D$0A

    Here in the string handler in the data event, I built up the data for the MD5 Calculation

    DATA_EVENT[dvIntercom1]
    {
    ONLINE:
    {
    send_string 0,"'intercom 1 online'"
    IF (!nMD5headerReady)
    {
    SEND_STRING dvIntercom1,sFinalCmd
    SEND_STRING 0,sFinalCmd
    }
    ELSE
    {
    SEND_STRING dvIntercom1,"'GET ',sGateURI,' HTTP/1.1',$0D,$0A,'Host: ',sSIPIntercomaddress[1],$0D,$0A"
    SEND_STRING dvIntercom1,"'Authorization: Digest username="',sIntercomUserName,'",
    realm="',sMD5HashRealm,'", nonce="',sMD5HashNonce,
    '", uri="',sGateURI,'", qop="auth", nc=00000001, cnonce="',sMD5HashCNonce,
    '", response="',sTempHashData,'", opaque="',sMD5HashOpaque,'"',$0D,$0A,$0D,$0A"

    OFF[nMD5headerReady]
    }      
    }
    

    OFFLINE:
    {
    send_string 0,"'intercom 1 offline'"
    IF (nMD5headerReady)
    {
    IP_CLIENT_OPEN(dvIntercom1.Port,sSIPIntercomaddress[1],80,IP_TCP)
    }
    }
    STRING:
    {
    LOCAL_VAR CHAR sData[5000]
    LOCAL_VAR INTEGER nCount
    LOCAL_VAR CHAR sConcat[1000]

    sIntercomData1 = "sIntercomData1,data.text"
    
    
    SEND_STRING 0,"'intercom DATA ',data.text"
    
    IF (FIND_STRING(sIntercomData1,"$0D,$0A,$0D,$0A",1))
    {
    sConcat = REMOVE_STRING(sIntercomData1,"$0D,$0A,$0D,$0A",1)
    SELECT
        {
        ACTIVE(FIND_STRING(UPPER_STRING(sConcat),'401 UNAUTHORIZED',1))://NEEDS MD5
        {
    
        IF(FIND_STRING(sConcat, 'WWW-Authenticate: Digest realm="', 1))
            {
            REMOVE_STRING(sConcat, 'WWW-Authenticate: Digest realm="', 1)
            sMD5HashRealm = LEFT_STRING(sConcat, FIND_STRING(sConcat, '",', 1) -1)
            //sMD5HashRealmActual = "'"',sMD5HashRealm,'"'"
            REMOVE_STRING (sConcat, 'qop="', 1)
            sMD5HashQop = LEFT_STRING(sConcat, FIND_STRING(sConcat, '",', 1) -1)
            REMOVE_STRING (sConcat, 'nonce="', 1)
            sMD5HashNonce = LEFT_STRING(sConcat, FIND_STRING(sConcat, '",', 1) -1)
            remove_string (sConcat, 'opaque="', 1)
            sMD5HashOpaque = LEFT_STRING(sConcat, FIND_STRING(sConcat, '"', 1) -1)
            sTempHashData = fnMD5DeviceChallengeResponse()
            ON[nMD5headerReady]
            } 
    
        IP_CLIENT_CLOSE(dvIntercom1.Port)
    
        }
        ACTIVE(FIND_STRING(UPPER_STRING(sData),'202 ACCEPTED',1)):
        {
        IP_CLIENT_CLOSE(dvIntercom1.PORT)
    
        }
        ACTIVE(FIND_STRING(UPPER_STRING(sData),'200 OK',1)):
        {
        IP_CLIENT_CLOSE(dvIntercom1.PORT)
        IF (nIntercomCurrentCommand == INTERCOM_OPEN_GATE)
            {
            SEND_COMMAND dvIntercomPanels,"'^TXT-',ITOA(INTERCOM_CALL_TITLE1_WINDOW),',0,Gate Opening'"
            }
        }
        }
    }
    }
    

    }

    Hope this helps. It certainly works for me. To work through this, I made the intercom open the gate from a browser and used wireshark to get the browser responses and checked my calculations using an online MD5 calculator to check mine against theirs.

    You probably also need to notice that I used a flag to signal that the MD5 conversion was ready so that when the initial command fails, because the intercom wants Digest and not Basic, the new connection is automatically opened and the String sent to the device.

    It still needs cleaning up, but it does work.

  • @Duncan Ellis thank you for your post. I have to dig into it and hopefully next week I can try to fix the issue on site. I'll let you know my result.

    @sling100 thank you for your tip. I already corrected it in my code. As soon as I can be on site, I'll see how it works.

    Best regards,

  • @Duncan Ellis one more thing - Is the "sFinalCmd" variable in your code a string you send to receive the credentials for digest authentication? I couldn't see how it is built.

  • Duncan EllisDuncan Ellis Posts: 154

    @MichalMoraszczyk Sorry. The sFinalCmd is a standard http header with just a GET command to the URL in the above command. It forces the Device to respond with the digest info.

    the HTTP body is empty, by the way

    SWITCH(nIntercomType)
    {
    CASE SIP_INTERCOM_TYPE_DAHUA:
    {
    SELECT
    {
    ACTIVE(nCmd = INTERCOM_OPEN_GATE):
    {
    sURI = "'/cgi-bin/accessControl.cgi?action=openDoor&channel=1&UserID=101&Type=Remote'"
    }
    }

    SWITCH(nCommandType)
        {
        CASE CMD_GET:
        {
        sHTTPheader= "'GET ',sURI,' HTTP/1.1',$0D,$0A,
                  'HOST: ',sSIPIntercomaddress[nIntercomIndex],':80',$0D,$0A,
                  'ACCEPT-LANGUAGE: EN',$0D,$0A,
                  'AUTHORIZATION: BASIC  ',fnEncodeBase10toBase64str('admin:qwerty!!'),$0D,$0A,
                  'CONTENT-TYPE: TEXT/PLAIN',$0D,$0A"
    
        }
        } 
    sFinalCmd = "sHTTPheader,$0d,$0a,$0D,$0A,sHTTPBody"
    
    IP_CLIENT_OPEN(dvIntercomDevices[nIntercomIndex].Port,sSIPIntercomaddress[nIntercomIndex],80,IP_TCP)
    }
    

    Hope this helps

  • @Duncan Ellis Thank you for your involvement. I replaced my functions with yours but it hasn't solved the problem. I couldn't examine my calculations against the ones captured from the browser, however, so I still don't have a clue what can be wrong. As soon as I can move forward and investigate the problem deeper, I will let you know the result.

    Best regards,

  • Duncan EllisDuncan Ellis Posts: 154

    @MichalMoraszczyk it will be something really simple.
    The best thing to do is send the initial request using a browser and capture the command response using Wireshark. Take the results from that and run it through an MD5 calculator online. Step your results through your code one step at a time. I had made some really simple errors because I was trying to look at the whole thing.

    The codeI supplied here worked using the Dahua Intercom and never fails so, the calcs are definitely correct.

  • @Duncan Ellis - I finally found my mistake. In the function calculating response, in the statement below

    sTempResponse =lower_string(fnConvertMD5ResultToASCII (md5("sMD5HashHA1,':',sMD5HashNonce,':00000001:',sMD5HashCNonce,':',sMD5HashQop,':',sMD5HashHA2")))

    I put the variable "sMD5HashCNonce" twice, instead of the variable "sMD5HashNonce" after the first colon. Now it works :)

    Thank all of you for your support.

  • Duncan EllisDuncan Ellis Posts: 154

    @MichalMoraszczyk no problem at all, glad you got it working!!!

Sign In or Register to comment.