Spracherkennung als Sicherheitsrisiko

Was vor fünf Jahren noch undenkbar war, findet sich heutzutage in fast jedem Mobiltelefon: Spracherkennung. Die Einstiegshürde ist inzwischen dank Fortschritten bei der Erkennungsgenauigkeit sehr niedrig. Im Gegensatz zu Spracherkennungssoftware der ersten Generation ist auch das Training mit Mustertexten nicht notwendig.
Allgemein scheint der Trend in die Richtung zu gehen, Spracherkennung auf möglichst vielen Plattformen anzubieten. So hat Apple beispielsweise kürzlich Siri in macOS Sierra integriert und Microsoft bietet Cortana für Windows 10 an. Zusätzlich verkaufen sowohl Google als auch Amazon auf dem Gebiet der Home Automation Lautsprecher mit integriertem Mikrofon, die auf Spracheingabe reagieren.
Viele dieser Systeme “hören” permanent mit und reagieren auf bestimmte Schlüsselwörter: z.B. “Ok, Google”, “Hey Alexa”, “Hey Siri” und “Xbox”. Dabei findet in der Regel keine Unterscheidung zwischen verschiedenen Stimmen statt, weswegen auch andere Personen als der Besitzer des Geräts Befehle ausführen können, sofern sie sich in der Nähe befinden.
Bei Systemen wie Amazon Echo können Besucher beispielsweise kostenpflichtige Bestellungen aufgeben. Die meisten Mobiltelefone erlauben es, Kontakte anzurufen oder ihnen Nachrichten zu senden.
Andererseits kann auch der rechtmäßige Besitzer dazu veranlasst werden, ungewollt Befehle auszuführen, wie dieses YouTube-Video am Beispiel der Xbox One zeigt.
Da die meisten Mobiltelefone gar nicht die notwendige Rechenleistung aufbringen können, um die Spracherkennung selbst durchzuführen, senden sie eine Aufnahme der gesprochenen Worte an ihren Hersteller. Was dieser im Detail damit macht, wird zwar in den Datenschutzrichtlinien dargelegt – jedoch werden bestimmte externe Partner (NSA, Bundesnachrichtendienst, Verfassungsschutz, u.ä.) dort nicht erwähnt.
Ich für meinen Teil verzichte auf meinen persönlichen Teleschirm und habe daher auf allen meinen Geräten Siri, Cortana und co. deaktiviert. Nicht nur zur Wahrung meiner Privatsphäre, sondern auch, weil ich mir ziemlich komisch vorkommen würde, in der Öffentlichkeit mit meinem Handy zu sprechen – aber vielleicht bin ich auch einfach zu alt für solch neumodischen Kram.

Speicherverbrauch analysieren mit Heaptrack

Um den Speicherverbrauch von Icinga 2 besser verstehen zu können, habe ich mir einen Docker-Container für heaptrack (gbeutner/heaptrack) gebaut. Heaptrack ist ähnlich wie Massif ein Tool, das dabei hilft, herauszufinden, wie Anwendungen Speicher verwenden:
heaptrack
In diesem Fall sieht man sehr schön, dass Strings zahlenmäßig anderen Objekt-Typen in Icinga 2 deutlich überlegen sind. Die Anzahl der geleakten Objekte ist in diesem Fall falsch, da ich den Icinga-Prozess mit Strg-C abgebrochen habe.
Der Speicherverbrauch ist eine der Stellen, an denen ich aktuell fleißig am optimieren bin, um sowohl den insgesamt benötigten Speicher allgemein zu verringern, aber auch um die Anzahl der temporär angelegten Objekte so klein wie möglich zu halten – was sich positiv auf die benötigte CPU-Zeit auswirkt. Zwischen der Version 2.4.10 und dem aktuellen Git-Master (“2.6.0”) habe ich den Config-Validator so z.B. bereits um fast 50% schneller gemacht (71 Sekunden mit 2.4.10 und nun aktuell 37 Sekunden)

Updates für den NETWAYS-Mittagsshop

Der NETWAYS-Mittagsshop ist eine von uns intern genutzte Anwendung, um z.B. Pizzabestellungen gesammelt ausführen zu können. Seit Kurzem unterstützt er nun einige neue Features:
1. Bestellstatistiken
Der Shop bietet nun eine Statistikfunktion, mit der z.B. die Bestellanzahl ausgewertet werden kann.
Bestellstatistik
2. Bearbeiten von Bestellungen
Benutzer können nun nachträglich ihre eigenen Bestellungen bearbeiten, ohne diese vorher löschen zu müssen. Außerdem können Händler für andere Benutzer Bestellungen einstellen bzw. auch ändern, wem eine Bestellung gehört.
3. Der Jabberbot ist nun im Git-Repository.
Der Sourcecode des Jabberbots befindet sich im Verzeichnis “jabber”. Der Bot unterstützt Befehle, um Bestellungen aufzugeben und den aktuellen Bestellstatus abzufragen.
4. Rabatte/Liefergebühren werden nun richtig berechnet
An sich ist das natürlich kein Feature, aber der Shop rundet Rabatte und Liefergebühren nun korrekt, auch wenn z.B. der Rabattsatz geändert wird, nachdem bereits Bestellungen aufgegeben wurden.
Für die Rabattfunktion kann nun zusätzlich auch ein Benutzer angegeben werden, dem die gewährten Rabatte per interner Lastschrift berechnet werden.
5. Die Menüs der Läden sind nun bearbeitbar.
Ursprünglich mussten Speisekarten mühsam mit SQL-Kommandos manuell in die Datenbank eingefügt werden. Dabei hat der durchschnittliche Laden ca. 500 Menüeinträge (verschiedene Pizzas mit Varianten: klein, groß, u.ä.). Inzwischen gibt es nun eine textbasierte Beschreibungssprache, die sogar Templates unterstützt. Somit können z.B. Preise per Git-Commit einfacher angepasst werden.

Avoiding Common Pitfalls with Apply Rules

When building apply rules for your Icinga 2 configuration there are a few common pitfalls you should avoid:
1. Using apply when you’re really just creating a single object
Rule-based configs are great at simplifying your config. However, there are times when you really just want to create a single object. One common anti-pattern I’ve come across is this:

apply Service "ntp" {
  ...
  assign where host.name == "ntp1.example.org"
}

Now, obviously this will work as intended, however there are two significant problems: Writing a filter rule for a single host is unnecessarily complicated and additionaly there is a significant performance penalty because this rule has to be evaluated for all of your hosts.
A much simpler way to achieve this is to just use a simple object declaration:

object Service "ntp" {
  host_name = "ntp1.example.org"
  ...
}

2. Using too many assign where rules
Apply rules are intended to be used to make your config more general by putting your hosts into certain classes (“all ntp servers”, “all database servers”, etc.) and then assigning services and notifications to each member of a certain class. However, for some reason people sometimes do this instead:

apply Service "web" {
  ...
  assign where host.name == "web1.example.org"
  assign where host.name == "web2.example.org"
  assign where host.name == "web3.example.org"
  ...
  assign where host.name == "web73.example.org"
}

The obvious problem here is that this is a maintenance nightmare – and as we’ve already learned “assign where” rules aren’t exactly free in terms of performance.
Unlike in our first example the solution isn’t to unroll this filter by creating an “object” definition for each of the hosts. Instead you should use some of the great filtering capabilities that come with Icinga 2. Here’s a short list of just some of the filters that are available:
1. CIDR matching (using the cidr_match function)
2. Regular expressions (using the regex function)
3. Wildcard matches (using the match function)
4. and last but not least: custom variables
In this particular example I’m going to use wildcard matching and custom variables:

object Host "web1.example.org" { }
object Host "web2.example.org" { }
object Host "web3.example.org" { vars.no_web_check = false }
apply Service "web" {
  ...
  assign where match("web*.example.org", host.name)
  ignore where host.vars.no_web_check
}

This assumes that all of your “web” hosts should have a “web” service by default. Using “ignore where” we can make sure that certain hosts don’t get the service.
3. Reusing “assign where” filters
This is pretty much the opposite problem compared to our previous example. Instead of using the same filter expression dozens of times in the same apply rule this is about unnecessarily repeating the filter in multiple apply rules:

apply Service "mysql" {
  ...
  // All db hosts except those in the dev subnet
  assign where match("db*.example.org", host.name) && !cidr_match("172.16.23.0/24", host.address)
}
apply Service "postgresql" {
  ...
  // All db hosts except those in the dev subnet
  assign where match("db*.example.org", host.name) && !cidr_match("172.16.23.0/24", host.address)
}
apply Service "mssql" {
  ...
  // All db hosts except those in the dev subnet
  assign where match("db*.example.org", host.name) && !cidr_match("172.16.23.0/24", host.address)
}

Code reuse is a best common practice when it comes to writing software. This also applies to Icinga 2 and makes your config much more maintainable and pleasant to work with.
Here’s how you can re-use your filter expression in multiple apply rules:

globals.is_prod_database_host = function(host) {
  // All db hosts except those in the dev subnet
  return match("db*.example.org", host.name) && !cidr_match("172.16.23.0/24", host.address)
}
apply Service "mysql" {
  ...
  assign where is_prod_database_host(host)
}
apply Service "postgresql" {
  ...
  assign where is_prod_database_host(host)
}
apply Service "mssql" {
  ...
  assign where is_prod_database_host(host)
}

By using descriptive function names you also gain the advantage of making your code… er, config more readable.

Mittagsbestellungen bei NETWAYS

Mittagsbestellungen in einem größeren Büro zu organisieren, ist gar nicht so einfach. Die Kollegen müssen gefragt werden, was sie denn gerne bestellen wollen und irgendjemand muss das Geld jeweils passend einsammeln (“Ich hab’ aber nur einen 50 Euro-Schein!”).
Sobald das Essen dann da ist, müssen die Kollegen benachrichtigt werden, die inzwischen vielleicht in einem Meeting sitzen und gar nicht mehr daran denken, dass sie vor einer halben Stunde eine Pizza bestellt haben.
Um diesen Prozess zu vereinfachen, haben wir uns einige Automatismen ausgedacht, die uns sowohl Zeit sparen als auch den Bestellvorgang komfortabler gestalten.
Zunächst einmal habe ich hierfür eine Webseite entwickelt, über die Bestellungen entgegengenommen werden können:
order-form
Unsere Mitarbeiter bekommen dazu täglich um 10:15 Uhr eine E-Mail, in der die heutigen Angebote stehen (z.B. Pizza oder Döner). Auf der Bestellseite gibt es auch für den jeweiligen Laden die aktuelle Speisekarte, in der einfach die gewünschten Gerichte inkl. derer Optionen (groß, klein, mit extra Käse, usw.) ausgewählt werden können. Alternativ kann auch über einen Jabber-Bot bestellt werden.
Händler können über die Webseite auch Trinkgeld für den jeweiligen Laden einsammeln, das dann gerecht auf alle Bestellungen aufgeteilt wird. Auch sind Rabattaktionen möglich (z.B. 30% auf alles).
Der Bestellschluss ist um 11:15 Uhr. Danach können sich unsere Händler die Liste der Bestellungen über die Webseite als PDF herunterladen und entsprechend bestellen. Sobald das Essen im Büro angekommen ist, ändern die Händler den Bestellstatus über die Webseite, wodurch auch gleichzeitig Jabber-Benachrichtigungen an alle Kollegen verschickt werden, die am jeweiligen Tag mitbestellt haben.
In Zukunft wird dieser Schritt dann über einen Taster erledigt, der neben der Eingangstür angebracht ist. Dabei gibt es für die Kollegen, von denen die meisten Bestellungen ausgeführt werden, jeweils einen Taster:
fhem-buttons
Bleibt eigentlich nur noch die Frage, wie die Händler an das Geld kommen, um die Bestellungen bezahlen zu können. Anstatt täglich Kleinstbeträge für Döner (3€) und ähnliches einzusammeln, erhält jeder Mitarbeiter ein Konto, über das die Bestellungen abgerechnet werden:
bank
Dies ermöglicht uns auch, Bargeld an einer zentralen Stelle einzusammeln. Hierfür haben wir eine Geldkasette, in der wir jeweils eine ausreichende Summe Bargeld vorhalten, um für die nächsten 1-2 Wochen Bestellungen ausführen zu können.
Einzahlungen sind allerdings auch über SEPA-Überweisungen möglich. Hierzu fragt ein Cronjob stündlich ein externes Bankkonto per HBCI ab und schreibt es dem jeweiligen Mitarbeiter auf seinem Essenskonto gut.
Überweisungen zwischen Mitarbeitern sind auch möglich (inkl. SMS-TAN zur Sicherheit) und wir räumen jedem Kollegen einen zinsfreien Dispokredit von 20€ ein.
Der Sourcecode für die beiden Anwendungen ist auf GitHub in den beiden Repositories https://github.com/gunnarbeutner/shop-app und https://github.com/gunnarbeutner/bank-app. Aktuell fehlt leider jegliche Dokumentation (ups!) und der Code ist an einigen Stellen nicht wirklich schön.

Understanding commands in Icinga 2

Icinga 2 command definitions can seem daunting at first. This blog post provides a quick introduction to some of the concepts you need to be familiar with when writing your own command definitions.
In their most basic form command definitions need a command line:

object CheckCommand "my_http" {
  import "plugin-check-command"
  command = [ PluginDir + "/check_http" ]
}

The “plugin-check-command” template tells Icinga how to execute commands, i.e. by executing an external plugin. There are a few other “*-check-command” templates but for virtually all of your own check commands you’ll need to use “plugin-check-command”.
The check_http plugin needs at least one more argument to work:

$ /opt/local/libexec/nagios/check_http -I 127.0.0.1
HTTP OK: HTTP/1.1 200 OK - 342 bytes in 0.001 second response time |time=0.001344s;;;0.000000 size=342B;;;0

We can add this argument to our check command like this:

object CheckCommand "my_http" {
  import "plugin-check-command"
  command = [ PluginDir + "/check_http" ]
  arguments = {
    "-I" = {
      value = "$my_http_address$"
      description = "IP address or name."
      required = true
    }
  }
  vars.my_http_address = "$address$"
}

The ‘required’ option tells Icinga to verify that the user specified a value for this argument.
We’re prefixing our custom attributes (my_http_address) with the name of the CheckCommand. This allows us to override specific custom attributes for HTTP checks on a per-host or per-service basis. If all
commands had the same custom attribute names (e.g. ‘timeout’) this wouldn’t be possible:

object Host "test" {
  ...
  // This affects all services on this host which use the my_http command
  vars.my_http_address = "127.0.0.1"
}

In our next step we’re going to add a few optional arguments. The check_http plugin lets us specify the ‘Host’ header and the URL that should be used. Adding optional arguments is rather simple:

    "-H" = {
      value = "$my_http_vhost$"
      description = "Host name argument for servers using host headers"
    }
    "-u" = {
      value = "$my_http_url$"
      description = "URL to GET or POST (default: /)"
    }

When Icinga encounters a command argument which uses an unresolvable macro (for example, because the user didn’t set a value for vars.my_http_vhost in their command, service or host) the entire argument is omitted.
Icinga can also add arguments only when certain conditions are met. In the next example I’m adding a new option ‘–sni’ which is only added when the custom attribute my_http_sni is set to true:

    "--sni" = {
      description = "Enable SSL/TLS hostname extension support (SNI)"
      set_if = "$my_http_sni$"
    }

Note that the ‘–sni’ option does not take an argument. Therefore we don’t need the ‘value’ attribute for this argument.
When the ‘my_http_sni’ custom attribute isn’t set at all Icinga defaults to not adding the argument.
There are a few more advanced topics for command arguments which aren’t covered in this blog post:

  • Ordering arguments
  • Using arrays for custom variables (with repeat_key/skip_key)
  • Using functions for set_if/value
  • Specifying an alternative ‘key’

I might write another blog post at a later point in time which deals with those features. In the meantime these things are explained in the documentation.

Neue IDE: CLion

Alle paar Monate wage ich einen neuen Versuch, eine bessere Entwicklungsumgebung als vim/make zu finden. Diesmal habe ich mir CLion angeschaut:
clion
Einige interessante Features, die CLion vor allem für die Entwicklung mit Icinga 2 interessant machen sind z.B.:

  • Integrierte Unterstützung für GDB als Debugger
  • CLion verwendet direkt CMake als Buildsystem (was wir zufälligerweise auch für Icinga 2 verwenden)
  • Intelligente Auto-Completion

Die nächsten Wochen werden dann wohl zeigen, ob CLion wirklich taugt oder ich bereits nach kürzester Zeit wieder auf vim zurückfalle.

JSON-Streams über HTTP

Viele Anwendungen sind darauf angewiesen, in Echtzeit Benachrichtigungen zu bestimmten Ereignissen zu erhalten:

  • Der Benutzer soll sofort über neue E-Mails benachrichtigt werden.
  • Notifications von einem Monitoring-System sollen durch eine Anwendung in der Taskleiste angezeigt werden.
  • Nachrichten, die per Instant Messaging versendet werden, sollten zeitnah beim Empfänger ankommen.

Zur Umsetzung solcher Anwendungen bieten sich durch die Verwendung von bekannten Standardprotokollen wie HTTP signifikante Vorteile:

Vorhandene Tools und Infrastruktur

Für HTTP gibt es in nahezu jeder Programmiersprache nativ integrierte Libraries. Hierdurch sinkt für Entwickler, die einen neuen Client für das Protokoll schreiben wollen der initiale Aufwand. Zwar unterscheiden sich die Anwendungen trotzdem noch an einigen Stellen (Wie sehen einzelne Messages aus? Welche URLs werden verwendet?), dafür werden andere Themen wie Authentifizierung bereits durch den Standard abgehandelt.
Um im Fehlerfall bzw. während der Entwicklung Probleme zu analysieren gibt es bereits etliche Tools. Diese reichen von einfachen konsolen-basierten HTTP-Clients wie curl und wget über Protokoll-Analyse-Tools wie Wireshark hin zu speziell für HTTP entwickelten Debug-Hilfsmitteln wie Fiddler.
Zusätzlich unterstützt HTTP mittels HTTP-Proxies “Routing”, da es in jeder Anfrage alle notwendigen Adressinformationen bereithält.

Streaming über HTTP

Nun ist HTTP zunächst ein Protokoll, das darauf basiert, dass für eine Anfrage jeweils genau eine Antwort übermittelt wird. Wie können wir nun also dem HTTP-Client unaufgefordert Events schicken?
Die einfache Grundlage hierfür ist eine Technologie, die als “Long Polling” bekannt ist: Der HTTP-Client sendet hierbei zunächst einen Request und bekommt vom Server auch eine Antwort. Diese Antwort hat allerdings kein Ende: Der Server sendet über die noch bestehende Verbindung JSON-kodierte Ereignisse sobald sie vorliegen.
Der Client muss dazu die Möglichkeit haben, zwischen den einzelnen JSON-kodierten Ereignissen unterscheiden zu können. Am Beispiel von zwei JSON-Ereignissen möchte ich hier auf die unterschiedlichen Ansätze eingehen.
Beispiel:

  • { “id”: 1, “message”: “Hallo Welt” }
  • { “id”: 2, “message”: “Dies ist ein Test.” }

Inkrementeller JSON-Parser

Ein JSON-Parser, der den Datenstrom inkrementell parsen kann, hat die Möglichkeit, zu erkennen, an welcher Stelle eine JSON-Message aufhört und die nächste beginnt. Die Messages werden hierbei also einfach aneinander gehängt. Der Body der HTTP-Antwort würde dabei etwa so aussehen:

{ "id": 1, "message": "Hallo Welt" }{ "id": 2, "message": "Dies ist ein Test." }

Vorteil hierbei ist, dass die Implementation des Servers sehr einfach ist. Allerdings können nur wenige JSON-Parser inkrementell parsen, was die Entwicklung des Clients erschwert.

Eine JSON-Message pro Zeile

Hierbei werden einzelne JSON-Messages per Newline (“\n”) getrennt. Beim Encoden der Messages muss darauf geachtet werden, dass diese selbst keine Newlines beinhalten.

{ "id": 1, "message": "Hallo Welt" }
{ "id": 2, "message": "Dies ist ein Test." }

Auf Clientseite ist dies im Regelfall recht einfach umzusetzen. Die Entwicklung des Servers wird minimal dadurch erschwert, dass mit Newlines innerhalb der JSON-Messages richtig umgegangen werden muss.

Längenangabe vor jeder JSON-Message

Bei dieser Möglichkeit sendet der Server vor jeder eigentlichen Message die Länge der JSON-Daten gefolgt von einem Newline:

37
{ "id": 1, "message": "Hallo Welt" }
45
{ "id": 2, "message": "Dies ist ein Test." }

(Wer nachzählt kommt möglicherweise darauf, dass die Längenangaben um ein Byte zu groß sind. Hierbei ist zu beachten, dass ich für dieses Beispiel aus Gründen der Lesbarkeit nach den JSON-Messages ein Newline eingefügt habe, das dann auch Bestandteil der Message ist und mitgezählt werden muss.)
Im Regelfall sollte es sowohl für Client als auch Server recht einfach sein, dies umzusetzen.

Neue Distributionen für das Icinga 2-Paket-Repository

Vor Kurzem wurden Debian 8.0 (“jessie”) und Ubuntu 15.04 (“Vivid”) veröffentlich. Für diese beiden Plattformen gibt es nun auch auf packages.icinga.org Pakete für Icinga 2 und Icinga Web 2.
Dabei wird für Debian auch die armhf-Architektur unterstützt, die beispielsweise der Raspberry Pi 2 verwendet (für Raspbian gibt es ein separates Repository, das allerdings nichts mit diesem Debian-Repo zu tun hat):
root@dashboard:~# uname -a
Linux dashboard.beutner.name 3.18.0-trunk-rpi2 #1 SMP PREEMPT Debian 3.18.5-1~exp1.co1 (2015-02-02) armv7l GNU/Linux
root@dashboard:~# icinga2 --version
icinga2 - The Icinga 2 network monitoring daemon (version: r2.3.4-1)
Copyright (c) 2012-2015 Icinga Development Team (https://www.icinga.org)
License GPLv2+: GNU GPL version 2 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Application information:
Installation root: /usr
Sysconf directory: /etc
Run directory: /run
Local state directory: /var
Package data directory: /usr/share/icinga2
State path: /var/lib/icinga2/icinga2.state
Objects path: /var/cache/icinga2/icinga2.debug
Vars path: /var/cache/icinga2/icinga2.vars
PID path: /run/icinga2/icinga2.pid
Application type: icinga/IcingaApplication
System information:
Operating system: Linux
Operating system version: 3.18.0-trunk-rpi2
Architecture: armv7l
Distribution: Debian GNU/Linux 8.0 (jessie)
root@dashboard:~#

Unterschiedliche Datentypen mit boost::variant

In C++ gibt es Fälle, in denen eine Funktion Werte zurückliefern soll, die nicht alle denselben Datentyp besitzen. Ein Beispiel hierfür ist eine Funktion, die einen JSON-String parsen soll und den deserialisierten Wert zurückgeben soll:

<Datentyp?> JsonDeserialize(const std::string& json);

Wenn wir die Funktion in C schreiben würden, könnten wir uns mit einem einfachen Trick behelfen: wir verwenden ein “Tagged Union” – also ein Union, zu dem wir uns zusätzlich merken, welches Feld darin aktuell gültig ist:

typedef struct {
  int tag; /* Das aktuell gültige Feld in dem Union */
  union {
    double value_double;
    char *value_string;
    ...
  };
} my_variant;

Mit C++ würde dies allerdings nicht funktionieren, da in einem Union nur POD-Typen erlaubt sind – also Datentypen, die keine Konstruktoren, Destruktoren oder andere Methoden enthalten. Der Hintergrund dabei ist, dass C++ nicht entscheiden kann, welchen Konstruktor und Destruktor es für das Union aufrufen soll, da es nicht weiss, welches Feld gültig ist.
Die Boost-Library bietet für dieses Problem eine elegante Lösung an: das boost::variant-Template kann verwendet werden, um eigene Typen zu definieren, die sich wie unser my_variant-Struct verhalten, aber zusätzlich auch nicht-triviale Typen (mit Konstruktor, Destruktor, usw.) enthalten kann:

typedef boost::variant<double, std::string, ...> my_variant;

Der my_variant-Typ verfügt dabei für jeden angegebenen Template-Parameter über einen passenden Konstruktor, der eine einfache Zuweisung erlaubt:

my_variant val = 7;

In diesem Fall wird der interne Typ des Variants auf “double” gesetzt und der Wert 7 gespeichert.
Um zu prüfen, welchen Typ ein Variant-Wert hat, kann die Methode “which” verwendet werden. Sie liefert einen null-basierten Index in die Template-Parameter zurück. Mit boost::get kann der Wert eines Variants extrahiert werden:

double dval = boost::get<double>(val);

Alternativ (und von der Boost-Dokumentation vorgeschlagen) kann auch das Visitor-Pattern verwendet werden, um auf die eigentlichen Werte zuzugreifen. Mehr Informationen gibt es in der Boost-Dokumentation.