"Sichere" Nutzung von Homedroid von extern

Nutzung von XML RPC, Remote Script, JSON RPC, XMLAPI

Moderator: Co-Administratoren

Antworten
AlteHippe
Beiträge: 27
Registriert: 23.03.2016, 23:29

"Sichere" Nutzung von Homedroid von extern

Beitrag von AlteHippe » 24.03.2016, 12:46

Hallo,

da ja die XML Api keine Authentifizierung bietet, und ich die Nutzung der Portweiterleitung FritzBox -> CCU2 und der XML Api nicht der ganzen Welt anbieten möchte, habe ich einen kleinen Workaround erstellt. Vielleicht nicht neu, habe aber nichts derartiges gefunden.

Idee: Nutzung eines "Proxies", der die Authentifizierung übernimmt.
1. in Homedroid unter Konfiguration -> Experimental Settings den Punkt "Enable HTTP Auth" aktivieren und Benutzername und Passwort eintragen
2. in Homedroid unter Konfiguration -> Netzwerkeinstellungen den Punkt "Fernzugriff aktivieren" aktivieren, externe CCU-Adresse + Port eingeben (HTTPS ist nicht möglich)
3. Weiterleitung in der FritzBox einrichten auf einen Host im privaten Netzwerk, NICHT auf die CCU2 - in meinem Fall: ein kleiner Raspberry Pi
4. Auf dem Host für die Weiterleitung: Starten der Proxy Software (Quellcode s.u. - Verwendung und Modifizierung nach Lust und Laune... :))

Code: Alles auswählen

java HomematicProxy 8088 homematic-ccu2 80 Proxyuser g3h3im!

Starte auf Port 8088, CCU unter homematic-ccu2:80
Benutzer: Proxyuser/Passwort: g3h3im!
Erwarte Verbindung...
...
Nun sollte Homedroid über den Proxy kommunizieren. Dieser prüft die in Homedroid angegebenen Logindaten.

Ist so sicher wie HTTP und Basic Authorisierung nun mal ist... aber besser als nichts :)

Viele Grüße von der alten Hippe.

Code: Alles auswählen


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
 */
public class HomematicProxy {

  /** default value */
  static int PROXY_PORT = 8088;
  /** default value */
  static int CCU_PORT = 80;
  /** default value */
  static String CCU_HOSTNAME = "homematic-ccu2";
  /** default value */
  static String AUTHSTRING = "";


  /**
   * Creates a new instance of Proxy
   * @param proxyPort
   * @param ccuPort
   * @param ccuHostName
   * @param user
   * @param password
   */
  public HomematicProxy(int proxyPort, String ccuHostName, int ccuPort, String user, String password) {
    PROXY_PORT = proxyPort;
    CCU_PORT = ccuPort;
    CCU_HOSTNAME = ccuHostName;
    AUTHSTRING = javax.xml.bind.DatatypeConverter.printBase64Binary((user + ":" + password).getBytes());

    System.out.println("Starte auf Port " + proxyPort + ", CCU unter " + ccuHostName + ":" + ccuPort);
    System.out.println("Benutzer: " + user + "/Passwort: " + password);

    int counter = 0;
    while (true) {
      try {
        counter++;
        ServerSocket ss = new ServerSocket(PROXY_PORT);
        System.out.println("Erwarte Verbindung...");
        Socket server = ss.accept();
        System.out.println("Verbunden...!");
        server.setSoTimeout(30 * 60 * 1000);
        new Handler(server, server.getInputStream(), server.getOutputStream(), counter);
        ss.close();
      } catch (BindException be) {
        System.err.println("Port " + PROXY_PORT + " bereits in Verwendung: " + be.getMessage());
        System.exit(1);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private class Handler extends Thread {

    BufferedInputStream clientIn;
    BufferedOutputStream clientOut;
    Socket clientSocket;
    String name;
    int connectionCounter = 0;
    int reuseCounter = 0;


    public Handler(Socket socket, InputStream in, OutputStream out, int counter) {
      this.clientIn = new BufferedInputStream(in);
      this.clientOut = new BufferedOutputStream(out);
      this.connectionCounter = counter;
      this.clientSocket = socket;
      this.start();
    }


    public void run() {
      this.name = "Verbindung " + connectionCounter + "/" + reuseCounter;
      System.out.println(name + " START");
      try {
        BufferedReader clientInReader = new BufferedReader(new InputStreamReader(clientIn));

        while (true) {
          reuseCounter++;
          this.name = "Verbindung " + connectionCounter + "/" + reuseCounter;
          String requestString = "";
          String s = null;
          boolean authorized = false;

          while ((s = clientInReader.readLine()) != null) {
            if (s.startsWith("Authorization: ")) {
              String auth = s.replace("Authorization: ", "");
              if (auth.equals("Basic " + AUTHSTRING)) {
                authorized = true;
                System.out.println(name + " Authorisierung ok");
              }
            }
            requestString += s;
            requestString += "\r\n";
            if (s.equals("")) {
              break;
            }
          }

          if (authorized) {
            Socket serverSocket = new Socket(CCU_HOSTNAME, CCU_PORT);
            BufferedWriter serverOutWriter = new BufferedWriter(new OutputStreamWriter(
              serverSocket.getOutputStream()));
            BufferedInputStream serverIn = new BufferedInputStream(serverSocket.getInputStream());

            serverOutWriter.write(requestString);
            System.out.println(name + " Request headers: " + requestString.replace("\r\n", ";"));
            serverOutWriter.flush();

            ByteArrayOutputStream responseHeaderBytes = new ByteArrayOutputStream();
            int contentLength = 0;
            while ((s = readLine(serverIn)) != null) {
              if (s.startsWith("Content-Length: ")) {
                contentLength = Integer.parseInt(s.replace("Content-Length: ", ""));
              }
              responseHeaderBytes.write((s + "\r\n").getBytes());

              if (s.equals("")) {
                break;
              }
            }

            responseHeaderBytes.flush();

            System.out.println(name + " Response headers: "
              + new String(responseHeaderBytes.toByteArray()).replace("\r\n", "; "));
            clientOut.write(responseHeaderBytes.toByteArray());
            clientOut.flush();
            boolean waitForDisconnect = contentLength == 0;
            int byteCount = 0;
            try {
              byte[] buf = new byte[4096];
              int bytesIn = 0;

              while (((byteCount < contentLength) || waitForDisconnect) && ((bytesIn = serverIn.read(buf)) >= 0)) {
                clientOut.write(buf, 0, bytesIn);
                clientOut.flush();
                byteCount += bytesIn;
                if (buf[bytesIn - 5] == 48 && buf[bytesIn - 4] == 13 && buf[bytesIn - 3] == 10
                  && buf[bytesIn - 2] == 13 && buf[bytesIn - 1] == 10) {
                  // System.out.println(name + " Found terminating chunk.");
                  break;
                }
              }
              clientOut.flush();
            } catch (Exception e) {
              e.printStackTrace();
            }
            System.out.println(name + " " + byteCount + " Bytes geschrieben - fertig.");
            clientOut.flush();
            serverOutWriter.close();
            serverIn.close();
            serverSocket.close();
          } else {
            clientOut
              .write("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\nContent-Length: 0\r\n\r\n"
                .getBytes());
            clientOut.flush();
            System.out.println(name + " 401 NOT AUTHORIZED - Ende");
          }
        }
      } catch (SocketTimeoutException e) {
        System.out.println(name + " Verbindungsende wg. Timeout");
      } catch (Exception e) {
        System.out.println(name + " Verbindungsende: " + e.getMessage());
      }
      try {
        clientSocket.close();
      } catch (IOException e) {
        System.err.println(name + " " + e.getMessage());
      }
    }
  }


  String readLine(InputStream in) {
    StringBuffer buf = new StringBuffer("");
    int c;

    try {
      in.mark(1);
      if (in.read() == -1) {
        return null;
      } else {
        in.reset();
      }
      while ((c = in.read()) >= 0) {
        if ((c == 0) || (c == 10) || (c == 13)) {
          break;
        } else {
          buf.append((char) c);
        }
      }
      if (c == 13) {
        in.mark(1);
        if (in.read() != 10) {
          in.reset();
        }
      }
    } catch (Exception e) {
    }
    return buf.toString();
  }


  /**
   * @param args
   */
  public static void main(String[] args) {
    if (null != args && args.length == 5) {
      try {
        new HomematicProxy(Integer.parseInt(args[0]), args[1], Integer.parseInt(args[2]), args[3], args[4]);
      } catch (Exception e) {
        System.err.println(e.getMessage());
        e.printStackTrace();
      }
    } else {
      System.out
        .println("5 Parameter benoetigt: <Port fuer Proxy> <Hostname CCU> <Port CCU> <Benutzer fuer Proxy> <Passwort>");
      System.out.println("Beispiel: 8088 homematic-ccu2 80 ProxyUser g3he1m");
    }
  }
}


gzi
Beiträge: 450
Registriert: 12.01.2015, 23:37
System: CCU
Hat sich bedankt: 15 Mal
Danksagung erhalten: 15 Mal

Re: "Sichere" Nutzung von Homedroid von extern

Beitrag von gzi » 07.02.2017, 19:52

AlteHippe hat geschrieben:Hallo,

da ja die XML Api keine Authentifizierung bietet, und ich die Nutzung der Portweiterleitung FritzBox -> CCU2 und der XML Api nicht der ganzen Welt anbieten möchte,
Ich habe nun eine sichere Lösung dafür entwickelt. Also XML API mit HTTPS und Authentication.
Hier weiterlesen und bei Interesse einfach nachfragen.

gzi
Lichtsteuerung, Heizungssteuerung, Überwachung (Feuer, Wasser, Einbruch, Stromausfall, Heizungsausfall, Wetter, Kamera), Alarmierung (optisch, akustisch, mail, SMS, voice call) - CCU, diverse HM- und HMIP Aktoren und Sensoren, Rauchmeldeanlage, UPS, GSM-Alarmwähler, Zugriff aus dem Internet via HTTPS und htdigest authentication, kein Datenkraken-Interface (Google, Amazon, China-Cloud, BND, NSA...) - HomeMatic Sicherheits-Kompendium - Checkliste für Auswahl von IP Kameras - Vergleich aktueller HomeMatic Zentralen - und alle Antworten für das gesamte Universum und den Rest

Antworten

Zurück zu „Softwareentwicklung von externen Applikationen“