Bild von IP Kamera holen

Alle Fragen zur Netzwerkkommunikation
Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Bild von IP Kamera holen

Beitrag von Timm Thaler »

Ich hab jetzt was in der englischen Wikipedia zur Berechnung des response gefunden: https://en.wikipedia.org/wiki/Digest_access_authentication#Overview

Muss ich das selber programmieren oder macht das der fphttpclient für mich?

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: Bild von IP Kamera holen

Beitrag von sstvmaster »

schau mal hier wurde was mit synapse gemacht: http://forum.lazarus.freepascal.org/ind ... 747.0.html
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Bild von IP Kamera holen

Beitrag von kupferstecher »

Ich hab mal mit Synapse eine URL-Anfrage mit User und Passwort (erfolgreich) durchgeführt. Hat aber nichts mit einer Kamera zu tun gehabt.

Hier mal die entscheidenden Stellen rauskopiert (hoffentlich komplett):

Code: Alles auswählen

uses HTTPSend;
//// Initialisierung
HTTP:= THTTPSend.Create;
 
HTTP.UserName:= 'Timm';
HTTP.Password:= '123456';
 
HTTP.Sock.SocksTimeout:= 5000; //ms
 
////Vorbereiten
HTTP.Clear;
WriteToMemStream(HTTP.Document,'URL-Anfrage');
 
////Senden
success:= HTTP.HTTPMethod('POST','Adresse');
 
////Antwort auslesen
if success and (HTTP.Document.Size > 0) then begin
  ReadFromMemStream(HTTP.Document,RecvStr);
end;
 
//Fertig
HTTP.Abort;
HTTP.Destroy;

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Bild von IP Kamera holen

Beitrag von Warf »

kupferstecher hat geschrieben:Ich hab mal mit Synapse eine URL-Anfrage mit User und Passwort (erfolgreich) durchgeführt. Hat aber nichts mit einer Kamera zu tun gehabt.


Das macht auch wieder Basic Authentification. Das kann auch der FPHTTPClient

Timm Thaler hat geschrieben:Ich hab jetzt was in der englischen Wikipedia zur Berechnung des response gefunden: https://en.wikipedia.org/wiki/Digest_access_authentication#Overview

Muss ich das selber programmieren oder macht das der fphttpclient für mich?


Musst du wohl oder übel selbst machen. Aber wie gesagt, Indy kann das bereits, ich würd einfach Indy verwenden. Ansonsten siehe:
sstvmaster hat geschrieben:schau mal hier wurde was mit synapse gemacht: http://forum.lazarus.freepascal.org/ind ... 747.0.html


Da wurde das per hand in synapse implementiert

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Bild von IP Kamera holen

Beitrag von Timm Thaler »

Ich denk mal, ich werde mir das selbst zusammenzimmern, ehe ich jetzt wieder auf ein externes Projekt wie Indy setze.

Ich nehme an, den Server dazu zu überreden Base64 Auth zu verwenden, wenn er Digest Auth will ist nicht möglich.

Ist ja schön, dass die Chinesen sich hier so um die Sicherheit Gedanken machen. Etwas grotesk ist halt, dass sie andererseits die Kamerabilder per Default um den halben Globus schicken und das Passwort dafür 123456 ist. Ich hab das selbe Passwort an meinem Koffer.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Bild von IP Kamera holen

Beitrag von Timm Thaler »

Ich hab jetzt wireshark noch auf dem Raspi installiert und die Kamera mit curl abgefragt. curl macht dabei im Prinzip das Gleiche wie Chromium unter Windows: Anfrage, 401 Error, erneute Anfrage mit den Login-Daten per Digest-Auth. Nur der cnonce-String sieht sehr anders aus, muss mal sehen, wie der ermittelt wird. < Ah, der Client-Nonce wird einmal im Klartext und einmal mit MD5 verschlüsselt im responce versendet.

Mein Gottchen, MD5 ist doch eh kaputt, eigentlich könnte man sich den Mist auch sparen, oder?

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Bild von IP Kamera holen

Beitrag von Warf »

Timm Thaler hat geschrieben:Ich hab jetzt wireshark noch auf dem Raspi installiert und die Kamera mit curl abgefragt. curl macht dabei im Prinzip das Gleiche wie Chromium unter Windows: Anfrage, 401 Error, erneute Anfrage mit den Login-Daten per Digest-Auth. Nur der cnonce-String sieht sehr anders aus, muss mal sehen, wie der ermittelt wird. < Ah, der Client-Nonce wird einmal im Klartext und einmal mit MD5 verschlüsselt im responce versendet.

Mein Gottchen, MD5 ist doch eh kaputt, eigentlich könnte man sich den Mist auch sparen, oder?


Könnte man, wenn man HTTPs verwendet kann man auch Basic auth verwenden. Aber MD5 ist in so fern unsicher das man gezielt kollisionen erzeugen kann, ich glaube aber echtes "entschlüsseln" ist damit auch nicht wirklich möglich. Für die meisten fälle wohl sicher genug

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Bild von IP Kamera holen

Beitrag von Timm Thaler »

Verdammte Axt, ich glaub es läuft.

Code: Alles auswählen

uses
  Classes, SysUtils, StrUtils, DateUtils, Crt,
  md5, fphttpclient, fpimage, fpwritejpeg;
 
const
  host = 'http://192.168.2.10';
  uri = '/mjpeg/snap.cgi?chn=0';
  url = host + uri;
  user = 'admin';
  pwd = '123456';
var
  fname : string = 'test.jpg';
  k, j : integer;
 
function GetImageData(const data : TMemoryStream) : integer;
const
  ncount : integer = 0;
  realm : string = '';
  qop : string = '';
  nonce : string = '';
  cnonce : string = '';
  oldnonce : string = '';
  oldcnonce : string = '';
var
  hcli : TFPHTTPClient;
  param : TStringList;
  i : integer;
  txt : string;
  nc, h1, h2, h3 : string;
begin
  GetImageData := 0;
  try
    hcli := TFPHTTPClient.Create(nil);
    try
      hcli.AddHeader('User-Agent','Mozilla/5.0 (compatible; fpweb)');
      if nonce <> '' then begin  // prüfen ob bereits Parameter für Auth von Kamera erhalten
        if nonce = oldnonce then begin  // Anmeldung noch gültig
          Inc(ncount);
          cnonce := oldcnonce;
        end else begin  // neue Anmeldung
          ncount := 1;
          cnonce := md5Print(md5String(IntToStr(DateTimeToUnix(Now()))));
          WriteLn(ncount, ' ', cnonce);
          oldnonce := nonce;
          oldcnonce := cnonce;
        end;
        nc := RightStr('00000000' + IntToStr(ncount), 8);
        h1 := md5Print(md5String(user + ':' + realm + ':' + pwd));
        h2 := md5Print(md5String('GET' + ':' + uri));
        h3 := md5Print(md5String(h1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + h2));
        txt := 'Digest username=' + AnsiQuotedStr(user,#34) + ', realm=' + AnsiQuotedStr(realm, #34) + ', nonce=' + AnsiQuotedStr(nonce,#34) + ', uri=' + AnsiQuotedStr(uri, #34) + ', qop=' + qop + ', nc=' + nc + ', cnonce=' + AnsiQuotedStr(cnonce, #34) + ', response=' + AnsiQuotedStr(h3, #34);
        hcli.AddHeader('Authorization', txt);
      end;
      data.Clear;
      hcli.HTTPMethod('GET', url, data, [200, 400, 401, 403, 404]);
 
      if hcli.ResponseStatusCode = 401 then begin  // wenn Auth gefordert
        txt := Trim(hcli.ResponseHeaders.Values['www-authenticate'])// Auth ermitteln
        if Lowercase(LeftStr(txt, 6)) = 'digest' then begin
          param := TStringList.Create();
          param.Clear;
          param.StrictDelimiter := true;
          param.Delimiter := ',';
          param.DelimitedText := Trim(Copy(txt, 7, Length(txt)));
          for i := 0 to param.Count - 1 do begin
            param[i] := StringReplace(Trim(param[i]), '"', '', [rfReplaceAll]);
          end;
          realm := param.Values['realm']// Parameter der Auth übernehmen
          nonce := param.Values['nonce'];
          qop := param.Values['qop'];
          param.Free;
          oldnonce := '';
          ncount := 0;
        end;
      end;
      data.Position := 0// Stream auf Anfang
      GetImageData := hcli.ResponseStatusCode;
    except
      WriteLn('Fehler beim Senden');
    end;
  finally
    hcli.Free;
  end;
end;
 
 
 
procedure ReadCamera();
var
  idata : TMemoryStream;
  ifile : TFileStream;
  stat : integer;
begin
  idata := TMemoryStream.Create();
  stat := GetImageData(idata);
  if stat = 401 then begin  // wenn Auth angefordert
    stat := GetImageData(idata)// nochmal mit Auth abrufen
  end;
  if stat = 200 then begin  // wenn erfolgreich
    try
      ifile := TFileStream.Create(fname, fmCreate);
      ifile.CopyFrom(idata, idata.Size);
      WriteLn('Bild gespeichert');
    finally
      ifile.Free;
    end;
  end;
  idata.Free;
end;


Funktioniert soweit erstmal. Hat gerade ein paar hundert Bilder geholt. Ist zwischendrin mit einer Exception "Server nicht erreichbar" abgeschmiert, aber um das Exception-Handling muss ich mich noch kümmern.

Der Client merkt sich die Anmeldedaten und versucht erstmal mit diesen Daten - oder Nichts beim Starten - ein Bild zu holen. Schlägt das fehl, versucht er es mit den übermittelten Parametern erneut.

Das ist noch etwas unausgegoren, eigentlich müsste ich noch prüfen, ob qop = 'auth' und so Kram. Ich bin da für Verbesserungsvorschläge offen. Bitte schaut mal drüber und teilt mir auch grobe Schnitzer mit, besonders anfällig bin ich für vergessene oder falsch platzierte Frees.

Wenn das läuft, stelle ich es gern ins Wiki. Prinzipiell sollte es für alle Kameras gehen, die Onvif unterstützen, denn dort ist die Einzelbildabfrage mit Digest Auth per http einer der Standards.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Bild von IP Kamera holen

Beitrag von Warf »

Timm Thaler hat geschrieben:Verdammte Axt, ich glaub es läuft.

Funktioniert soweit erstmal. Hat gerade ein paar hundert Bilder geholt. Ist zwischendrin mit einer Exception "Server nicht erreichbar" abgeschmiert, aber um das Exception-Handling muss ich mich noch kümmern.

Der Client merkt sich die Anmeldedaten und versucht erstmal mit diesen Daten - oder Nichts beim Starten - ein Bild zu holen. Schlägt das fehl, versucht er es mit den übermittelten Parametern erneut.

Das ist noch etwas unausgegoren, eigentlich müsste ich noch prüfen, ob qop = 'auth' und so Kram. Ich bin da für Verbesserungsvorschläge offen. Bitte schaut mal drüber und teilt mir auch grobe Schnitzer mit, besonders anfällig bin ich für vergessene oder falsch platzierte Frees.

Wenn das läuft, stelle ich es gern ins Wiki. Prinzipiell sollte es für alle Kameras gehen, die Onvif unterstützen, denn dort ist die Einzelbildabfrage mit Digest Auth per http einer der Standards.


Statt es ins wiki zu stellen wäre es besser einfach das ganze in die HTTP client klasse zu basteln und die dann über den Bugtracker oder so zu submitten. Zu den Frees, benutz immer try-finally. Wenn eine Exception auftritt wird die Methode verlassen und die Exception hoch gegeben. Aller speicher der nicht durch ein Finally bereinigt wird ist verloren. Z.B. deine Zeile: param.DelimitedText := Trim(Copy(txt, 7, Length(txt)));
Wenn du nicht genug speicher frei hast (oder dein Speicher zu fragmentiert ist) dann bekommst du eine Exception, die Methode wird verlassen und Param wird nie freigegeben.

Wenn du nach jedem .Create ein Try - Finally machst, vergisst du das Free auch weniger

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Bild von IP Kamera holen

Beitrag von Timm Thaler »

Warf hat geschrieben:Wenn du nach jedem .Create ein Try - Finally machst, vergisst du das Free auch weniger


Das ist wohl war, aber das kann eine ordentliche try-finally-Orgie werden. Was mir auch fehlt ist ein try-exception-finally, also die Exception abhandeln und dann abschließende Bereinigung.

Ich hab das jetzt auch nochmal auseinandergenommen und die Authentifizierung in eigene Prozeduren gepackt.

Antworten