Unify QuickConfig-Tool klonen

Ich habe ja vor ein paar Monaten mein Unify-FirmwareTool und mein Unify-QuickConfig-Tool veröffentlicht. Nun bin ich (auch nicht zum ersten Mal) gefragt worden:

Wäre es möglich den Quellcode zu veröffentlichen, sodass man das Tool innerhalb des eigenen Netzwerks selbst betreiben kann?

Den kompletten Quellcode nicht unbedingt, denn das Tool ist nur das Abfallprodukt eines dienstlich entwickelten Autoprovisioning-Servers. Und der kann verschiedene Hersteller (nicht nur unify) und der kann auch verschiedene Aufgaben (der erkennt das Gerät anhand z.B. der Mac-Adresse oder anhand des Kundennetzes (bei einer Suche nach neuen Geräten), rollt automatisch die passende Firmware aus, konfiguriert die Tasten automatisch uvam. Und das alles da rauszulösen ist mehr Arbeit, als es neu zu programmieren.

Und anstatt das immer einzeln zu beantworten, hier gerne öffentlich eine Anleitung, wie man es "neu programmieren" würde:

 

Schritt 1: Das Webinterface

Mein Webinterface besteht aus einer Handvoll Input-Feldern: Ist es eine einzelne IP oder eine Range (und wenn ja, welche) und was soll konfiguriert werden. Das ist nicht schwierig.

Im nächsten Schritt wird dann einerseits ein JavaScript an den Browser ausgeliefert - siehe nächster Punkt. Und anderseits werden die Items gebaut aus dem, was man eingegeben hat.

Wenn man eingegeben hat:

screensaver-enabled = false
display-skin = 1  
function-key-def[1] = 59
key-label-unicode[1] = Harry
stimulus-led-control-uri[1] = 123

also: Kein Bildschirmschoner, Farbschema anthrazit-orange (OpenStage 60) und die erste Funktionstaste konfigurieren wir als Typ 59 (Zielwahltaste mit BLF), Beschriftung Harry, Rufnummer 123

dann baue ich daraus folgende XML-Elemente:

<item name=”display-skin”>1</item>
<item name=”screensaver-enabled”>false</item>
<item name=”function-key-def” index=”1”>59</item>
<item name=”key-label-unicode” index=”1”>Harry</item>
<item name=”stimulus-led-control-uri” index=”1”>123</item>
 

und die brauchen wir später dann.

Schritt 2: Die Telefon-Suche

Die Telefonsuche läuft über ein JavaScript ab, das per HTML-Formularaufruf die IP des Telefons aufruft (und zwar auf einer ohne Zugangsdaten zugänglichen URL, die für die Kontaktaufnahme mit dem Server zuständig ist) und im Formular dann eben "meinen" Server übergibt.

Das Script ist kein Geheimnis (sind JavaScripte nie), der wichtige Teil ist folgender:

function ValidateIPaddress(ipaddress) {
    // source: www.w3resource.com/javascript/form/ip-address-validation.php
    var ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
    if(ipaddress.match(ipformat)) {
        return (true)
    }
    return (false)
}
function Sleep(milliseconds) {
  // source: www.heise.de/tipps-tricks/JavaScript-Sleep-und-setTimeout-4060840.html
  return new Promise(resolve => setTimeout(resolve, milliseconds));
}

async function StartScan() {
  var startip=document.getElementById("startip").value;
  var stopip=document.getElementById("stopip").value;
  if (document.getElementById("mode").value=="single") {
    startip = document.getElementById("singleip").value;
    stopip = startip;
  }

if (!ValidateIPaddress(startip)) {
  alert("Bitte geben Sie eine korrekte Start-IP an.");
return false;
}
if (!ValidateIPaddress(stopip)) {
  alert("Bitte geben Sie eine korrekte Stop-IP an.");
return false;
}
var startnetwork=startip.split(".");
var stopnetwork=stopip.split(".");
if (startnetwork[0]!=stopnetwork[0] || startnetwork[1]!=stopnetwork[1] || startnetwork[2]!=stopnetwork[2]) {
  alert("Start- und Stop-IP sollten im gleichen /24er-Netz liegen.");
  return false;
}
for (i = parseInt(startnetwork[3]); i<= parseInt(stopnetwork[3]); i++) {
  ip = startnetwork[0]+"."+startnetwork[1]+"."+startnetwork[2]+"."+i;
  document.getElementById("status").innerHTML="Kontaktiere "+ip;
  document.getElementById("temp").innerHTML += '<form id="dynForm'+i+'" action="http://'+ip+':8085/contact_dls.html/ContactDLS" method="post" target="openstage_1fefda"><input type=text name=ContactMe value="true"><input type=text name=dls_ip_addr value="HIER-MUSS-DANN-DEIN-DLS-SERVER-REIN"><input type=text name=dls_ip_port value=18443></form>';
  document.getElementById("dynForm"+i).submit();
  await Sleep(2000);
}
document.getElementById("status").innerHTML="Durchlauf beendet.";
}</script>
<script type=text/JavaScript>
</script>

<input type=hidden name="startip" id="startip" value="$$$$$$"><input type=hidden name="stopip" id="stopip" value="$$$$$"><input type=hidden name="singleip" id="$$$$$" value=""><input type=hidden name="mode" id="mode" value="$$$$$$">
<div id=status>Starting soon...</div>
<div id=temp style="display:none;"></div>
<iframe id="openstage_1fefda" name="openstage_1fefda" style="display:none;"></iframe>
<script type=text/JavaScript>
StartScan();
</script>

Weiter oben im JavaScript muss dann die IP Deines DLS-Servers rein (zu dem kommen wir gleich). Und weiter unten im Script habe ich ein paar Hidden-Felder, die dann mit den Daten aus dem vorherigen Formular gefült werden (da, wo die $$$$$ stehen). Der Mode ist entweder "singleip" oder "range".

Schritt 3: Der DLS-Server

Jetzt brauchst Du noch den eigentlichen DLS-Server. Wenn Dir wirklich die einfache Funktionalität meines QuickConfig-Tools reicht, dann muss der nicht viel können:

  1. Er muss auf Port 18443 per https lauschen
  2. Er muss die Anfrage des Telefons verstehen (kommt als XML-Post-Request)
  3. Er muss einen "Content-Type: text/xml" zurückgeben
  4. Gefolgt von XML-Konfiguration ans Gerät

Punkt1 und 3 sollten klar sein.

Punkt 2 ist insofern relativ einfach, als dass Du aus dem Request des Telefons für mein Tool nicht mal das XML parsen und verstehen musst. Da steht drin, welches Gerät - Modell, Firmware, MAC - es ist, warum  es sich meldet (routinemäßig oder auf Anfrage hin, in unserem Fall letzteres) und ggf. schickt es auch seine Bestandskonfiguration mit. Für einen vernünftigen DLS-Server (der natürlich nicht ungefragt jedem Gerät ständig irgendwelche Configs schicken will, womöglich jedem Gerät bei jedem Reboot einen Firmware-Download-Auftrag, damit es in einer Loop gefangen ist) müsste man diese Infos auswerten.

In meinem Fall nicht, denn der Anwendungsfall ist ja immer, dass man seine Konfiguration ausrollt und danach mittels "dls-addr" wieder die eigentliche Telefonanlage als DLS-Server konfiguriert (d.h. die spielt dann den "vernünftigen Server"). Alles andere kann zu komischen Effekten führen!

Von daher brauchen wir aus der Anfrage des Telefons das XML gar nicht, und außerhalb nur das "nonce", einen Zufallswert, den wir zurückgeben müssen (damit das Telefon weiß, dass die Antwort zur Anfrage gehört). Und das holen wir uns in PHP so (Du kannst natürlich eine Programmiersprache Deiner Wahl nehmen):

$raw = file_get_contents('php://input');
$Nonce = '';
foreach (explode("\n", $raw) as $row) {
  if (stristr($row, "ReasonForContact") && !stristr($row, "solicited")) {
      // Evtl. Abbruch, siehe weiter unten
  }
  if (stristr($row, 'nonce=')) {
    $Teile = explode('"', $row);
    $Teil = trim($Teile[1]);
    if ($Teil) $Nonce = $Teil;
  }
}

Und jetzt kommt noch Punkt 4, das XML, das wir ans Gerät schicken. Das sieht so aus:

<DLSMessage>
  <Message nonce=”$Nonce”>
        <Action>WriteItems</Action>
        <item name=”display-skin”>1</item>
        <item name=”screensaver-enabled”>false</item>
        <item name=”function-key-def” index=”1”>59</item>
        <item name=”key-label-unicode” index=”1”>Harry</item>
        <item name=”stimulus-led-control-uri” index=”1”>123</item>
  </Message>
</DLSMessage>

Der Aufbau ist immer gleich, oben kommt das Nonce rein, und dann eben die Items, die wir in Schritt 1 unserer Anleitung gebaut haben. Fertig.

Neben "WriteItems" gibt es noch ein paar weitere interessante Actions, nämlich:

  • CleanUp - sagt dem Telefon einfach nur, dass die Anfrage erledigt ist (also man keine weiteren Aufgaben hat und man die Kommunikation sauber beendet). Da man bei einem WriteItems aber immer (siehe oben) zum Schluss auch wieder den richtigen DLS-Server übertragen sollte (und das Telefon also nach dem write-items seine nächsten Verbindungen zu dem aufbaut), braucht man das eigentlich nie.
  • FileDeployment - damit kann man Logos, Klingeltöne etc. ausrollen. Die entsprechenden Feldnamen kann man in meinen Vorlagen (oder aus dem Handbuch) rausnehmen
  • SoftwareDeployment - im Prinzip das gleiche, nur für Firmware. Feldnamen siehe Handbuch.
  • Restart (damit kann man einen Neustart und/oder Factory Reset durchführen), Feldnamen findet man auch in der Anleitung

Aber Achtung: Wenn man eine andere Action überträgt, kann man keinen anderen DLS-Server hinterlegen (das geht nur als WriteItem). Das heißt, das Telefon wird sich danach (nach dem Download, dem Firmware-Update oder dem Neustart) wieder bei unserem selbstgebauten DLS-Server melden. Und der wird dann das Kommando wiederholen und das Gerät ist scheinbar in einer Endlos-Schleife.

Wenn Du diese Kommandos nutzen willst, dann habe ich in meinem PHP-Dreizeiler noch ein if eingebaut ("Evtl. Abbruch, siehe weiter unten"): In dem XML-Teil (den wir gar nicht geparst haben, für den Zweck genügt auch die Volltextsuche) sagt das Telefon im Feld "ReasonForContact", warum es sich überhaupt bei uns meldet. Und nur, wenn da "solicited" steht (Zitat Anleitung: The phone has received a contact-me message from the provisioning service and is calling back for further actions) dann meldet es sich auf unser Scan-JavaScript hin. Wenn wir also das Feld "ReasonForContact" finden, aber nicht das Schlüsselwort "solicited", dann meldet sich das Telefon aus einem anderen Grund (z.B. start-up = nach einem Reboot) und wir schicken ihm keinen neuen Reboot-Auftrag zu. 

Die oben zitierte "Anleitung" ist der "OpenStage / OpenScape Desk Phone IP Provisioning Interface Developer's Guide" und unter der Artikelnummer A31003-S2000-R102-16-7620 bei Unify zu finden.

Disclaimer

Bereits meine Tools haben ein großes Potential, bei falscher Bedienung ordentlich Chaos anzulegen. Mit einem selbstgebauten DLS-Server wird es noch viel schlimmer, von falscher Firmware über Sicherheitslücken (wenn Du die Telefone auch konfigurierst, muss Dein DLS-Server ja Zugangsdaten irgendwo raussuchen können) fällt mir ganz viel ein, was man falsch machen kann.

Andererseits, ich habe hier nur eine kurze Zusammenfassung dessen formuliert, was in o.g. Developers Guide öffentlich von unify beschrieben wird (ich habe ja auch nichts anderes gemacht als das Ding gelesen und anhand dieser Anleitung meinen eigenen DLS gebaut). Und ein bisschen Eigenleistung brauchst Du für meine Schritt-für-Schritt-Anleitung ja trotzdem noch.

Dennoch: Sei vorsichtig, sonst hast Du nachher ordentlich Chaos bei Deinen Anwendern.

Feedback