1 ## @defgroup openvpn OpenVPN (allgemein)
2 ## @brief Vorbereitung, Konfiguration und Prüfung von VPN-Verbindunge (z.B. für Nutzertunnel oder UGW).
3 # Beginn der opnvpn-Doku-Gruppe
7 VPN_DIR_TEST=/etc/openvpn/opennet_vpntest
8 OPENVPN_CONFIG_BASEDIR=/var/etc/openvpn
11 ## @fn enable_openvpn_service()
12 ## @brief Erzeuge eine funktionierende openvpn-Konfiguration (Datei + UCI).
13 ## @param service_name Name eines Dienstes
14 ## @details Die Konfigurationsdatei wird erzeugt und eine openvpn-uci-Konfiguration wird angelegt.
15 ## Falls zu diesem openvpn-Dienst kein Zertifikat oder kein Schlüssel gefunden wird, dann passiert nichts.
17 trap
"error_trap enable_openvpn_service '$*'" $GUARD_TRAPS
18 local service_name=
"$1"
20 msg_info "Refuse to enable openvpn server ('$service_name'): missing key or certificate"
21 trap
"" $GUARD_TRAPS &&
return 1
23 local uci_prefix=
"openvpn.$service_name"
25 # zukuenftige config-Datei referenzieren
27 # zuvor ankuendigen, dass zukuenftig diese uci-Konfiguration an dem Dienst haengt
28 service_add_uci_dependency
"$service_name" "$uci_prefix"
29 # lege die uci-Konfiguration an und aktiviere sie
30 uci
set "${uci_prefix}=openvpn"
31 uci
set "${uci_prefix}.enabled=1"
32 uci
set "${uci_prefix}.config=$config_file"
38 ## @brief Schreibe eine openvpn-Konfigurationsdatei.
39 ## @param service_name Name eines Dienstes
41 trap
"error_trap update_vpn_config '$*'" $GUARD_TRAPS
42 local service_name=
"$1"
44 service_add_file_dependency
"$service_name" "$config_file"
45 # Konfigurationsdatei neu schreiben
46 mkdir -
p "$(dirname "$config_file
")"
52 ## @brief Löschung einer openvpn-Verbindung
53 ## @param service_name Name eines Dienstes
54 ## @details Die UCI-Konfiguration, sowie alle anderen mit der Verbindung verbundenen Elemente werden entfernt.
55 ## Die openvpn-Verbindung bleibt bestehen, bis zum nächsten Aufruf von 'apply_changes openvpn'.
57 trap
"error_trap disable_openvpn_service '$*'" $GUARD_TRAPS
58 local service_name=
"$1"
59 # Abbruch, falls es keine openvpn-Instanz gibt
60 [ -z
"$(uci_get "openvpn.$service_name
")" ] &&
return 0
61 # openvpn wird automatisch neugestartet
62 cleanup_service_dependencies
"$service_name"
63 # nach einem reboot sind eventuell die dependencies verlorengegangen - also loeschen wir manuell
68 ## @fn is_openvpn_service_active()
69 ## @brief Prüfung ob eine openvpn-Verbindung besteht.
70 ## @param service_name Name eines Dienstes
71 ## @details Die Prüfung wird anhand der PID-Datei und der Gültigkeit der enthaltenen PID vorgenommen.
73 local service_name=
"$1"
76 # existiert ein VPN-Eintrag?
77 [ -z
"$(uci_get "openvpn.$service_name
")" ] && trap
"" $GUARD_TRAPS &&
return 1
78 # gibt es einen Verweis auf eine passende PID-Datei?
79 check_pid_file
"$(get_service_value "$service_name
" "pid_file
")" "openvpn" &&
return 0
80 trap
"" $GUARD_TRAPS &&
return 1
84 ## @fn _change_openvpn_config_setting()
85 ## @brief Ändere eine Einstellung in einer openvpn-Konfigurationsdatei.
86 ## @param config_file Name der Konfigurationsdatei.
87 ## @param config_key Name der openvpn-Einstellung.
88 ## @param config_value Neuer Inhalt der Einstellung - die Einstellung wird gelöscht, falls dieser Parameter fehlt oder leer ist.
89 ## @attention OpenVPN-Optionen ohne Parameter (z.B. --mtu-test) können nicht mittels dieser Funktion gesetzt werden.
91 local config_file=
"$1"
93 local config_value=
"${3:-}"
94 sed -i
"/^$config_key[\t ]/d" "$config_file"
95 [ -n
"$config_value" ] && echo
"$config_key $config_value" >>
"$config_file"
100 ## @fn get_openvpn_config()
101 ## @brief liefere openvpn-Konfiguration eines Dienstes zurück
102 ## @param service_name Name eines Dienstes
104 trap
"error_trap get_openvpn_config '$*'" $GUARD_TRAPS
105 local service_name=
"$1"
109 [
"$protocol" =
"tcp" ] && protocol=tcp-client
112 # schreibe die Konfigurationsdatei
113 echo
"# automatically generated by $0"
114 echo
"remote $remote $port"
115 echo
"proto $protocol"
116 echo
"writepid $pid_file"
118 # sicherstellen, dass die Konfigurationsdatei mit einem Zeilenumbruch endet (fuer
"echo >> ...")
123 ## @fn verify_vpn_connection()
124 ## @brief Prüfe einen VPN-Verbindungsaufbau
125 ## @param service_name Name eines Dienstes
126 ## @param key [optional] Schluesseldatei: z.B. $VPN_DIR/on_aps.key
127 ## @param cert [optional] Zertifikatsdatei: z.B. $VPN_DIR/on_aps.crt
128 ## @param ca-cert [optional] CA-Zertifikatsdatei: z.B. $VPN_DIR/opennet-ca.crt
129 ## @returns Exitcode=0 falls die Verbindung aufgebaut werden konnte
131 trap
"error_trap verify_vpn_connection '$*'" $GUARD_TRAPS
132 local service_name=
"$1"
133 local key_file=${2:-}
134 local cert_file=${3:-}
136 local config_file=$(mktemp -t
"VERIFY-${service_name}-XXXXXXX")
137 local log_file=
"$(get_service_log_filename "$service_name
" "openvpn
" "verify
")"
143 msg_debug "start vpn test of $service_name"
146 # erstelle die config-Datei
148 # filtere Einstellungen heraus, die wir ueberschreiben wollen
149 # nie die echte PID-Datei ueberschreiben (falls ein Prozess laeuft)
152 # some openvpn options:
153 # ifconfig-noexec: we do not want to configure a device (and mess up routing tables)
154 # route-noexec: keinerlei Routen hinzufuegen
155 echo
"ifconfig-noexec"
158 # some timing options:
159 # inactive: close connection after 15s without traffic
160 # ping-exit: close connection after 15s without a ping from the other side (which is probably disabled)
161 echo
"inactive 15 1000000"
165 # verb: verbose level 3 is required for the TLS messages
166 # nice: testing is not too important
167 # resolv-retry: fuer ipv4/ipv6-Tests sollten wir mehrere Versuche zulassen
170 echo
"resolv-retry 3"
172 # prevent a real connection (otherwise we may break our current vpn tunnel):
173 # tls-exit: stop immediately after tls handshake failure
174 # ns-cert-type: enforce a connection against a server certificate (instead of peer-to-peer)
176 echo
"ns-cert-type server"
180 # kein Netzwerkinterface erzeugen
182 # keine PID-Datei anlegen
184 # keine Netzwerkkonfiguration via up/down
187 # TLS-Pruefung immer fehlschlagen lassen
192 # nur fuer tcp-Verbindungen (ipv4/ipv6)
193 # connect-retry: Sekunden Wartezeit zwischen Versuchen
194 # connect-timeout: Dauer eines Versuchs
195 # connect-retry-max: Anzahl moeglicher Wiederholungen
196 if grep -q
"^proto[ \t]\+tcp" "$config_file"; then
197 echo
"connect-retry 1"
198 echo
"connect-timeout 15"
199 echo
"connect-retry-max 1"
202 # Schluessel und Zertifikate bei Bedarf austauschen
203 [ -n
"$key_file" ] && \
204 _change_openvpn_config_setting
"$config_file" "key" "$key_file"
205 [ -n
"$cert_file" ] && \
206 _change_openvpn_config_setting
"$config_file" "cert" "$cert_file"
207 [ -n
"$ca_file" ] && \
208 _change_openvpn_config_setting
"$config_file" "ca" "$ca_file"
210 # Aufbau der VPN-Verbindung bis zum Timeout oder bis zum Verbindungsabbruch via "tls-exit" (/bin/false)
211 openvpn --config
"$config_file" ||
true
212 # read the additional options from the config file (for debug purposes)
213 file_opts=$(grep -v
"^$" "$config_file" | grep -v
"^#" | sed
's/^/--/' | tr
'\n' ' ')
215 grep -q
"Initial packet" "$log_file" &&
return 0
216 msg_debug "openvpn test failed: openvpn $file_opts"
217 trap
"" $GUARD_TRAPS &&
return 1
222 ## @brief Prüfe ob das Zertifikat eines openvpn-basierten Diensts existiert.
223 ## @returns exitcode=0 falls das Zertifikat existiert
224 ## @details Falls der Ort der Zertifikatsdatei nicht zweifelsfrei ermittelt
225 ## werden kann, dann liefert die Funktion "wahr" zurück.
227 local service_name=
"$1"
231 # im Zweifelsfall (kein Template gefunden) liefern wir
"wahr"
232 [ -z
"$config_template" ] &&
return 0
233 # Verweis auf lokale config-Datei (keine uci-basierte Konfiguration)
234 if [ -e
"$config_template" ]; then
238 # im Zweifelsfall: liefere "wahr"
241 # das Zertifikat scheint irgendwie anders konfiguriert zu sein - im Zeifelsfall: OK
242 [ -z
"$cert_file" -o -z
"$key_file" ] &&
return 0
243 # existiert die Datei?
244 [ -e
"$cert_file" -a -e
"$key_file" ] &&
return 0
245 trap
"" $GUARD_TRAPS &&
return 1
249 ## @fn submit_csr_via_http()
250 ## @param upload_url URL des Upload-Formulars
251 ## @param csr_file Dateiname einer Zertifikatsanfrage
252 ## @brief Einreichung einer Zertifikatsanfrage via http (bei http://ca.on)
253 ## @details Eine Prüfung des Ergebniswerts ist aufgrund des auf menschliche Nutzer ausgerichteten Interface nicht so leicht moeglich.
254 ## @todo Umstellung vom Formular auf die zu entwickelnde API
255 ## @returns Das Ergebnis ist die html-Ausgabe des Upload-Formulars.
257 trap
"error_trap submit_csr_via_http '$*'" $GUARD_TRAPS
258 # upload_url: z.B. http://ca.on/csr/csr_upload.php
259 local upload_url=
"$1"
261 local helper=
"${3:-}"
262 local helper_email=
"${4:-}"
263 curl -q --silent --capath /etc/ssl/certs --form
"file=@$csr_file" --form
"opt_name=$helper" --form
"opt_mail=$helper_email" "$upload_url" &&
return 0
264 # ein technischer Verbindungsfehler trat auf
265 trap
"" $GUARD_TRAPS &&
return 1
269 ## @fn has_openvpn_credentials_by_template()
270 ## @brief Prüft, ob der Nutzer bereits einen Schlüssel und ein Zertifikat angelegt hat.
271 ## @param template_file Name einer openvpn-Konfigurationsdatei (oder einer Vorlage). Aus dieser Datei werden "cert"- und "key"-Werte entnommen.
272 ## @returns Liefert "wahr", falls Schlüssel und Zertifikat vorhanden sind oder falls in irgendeiner Form Unklarheit besteht.
274 trap
"error_trap has_openvpn_credentials_by_template '$*'" $GUARD_TRAPS
275 local template_file=
"$1"
278 # im Zweifel: liefere "wahr"
279 [ -z
"$key_file" -o -z
"$cert_file" ] &&
return 0
280 # beide Dateien existieren
281 [ -e
"$key_file" -a -e
"$cert_file" ] &&
return 0
282 trap
"" $GUARD_TRAPS &&
return 1
286 ## @fn log_openvpn_events_and_disconnect_if_requested()
287 ## @brief Allgemeines Ereignisbehandlung fuer openvpn-Verbindungen: Logging und eventuell Dienst-Bereinigung (nur für "down").
288 ## @details Alle Informationen (bis auf das Log-Ziel) werden aus den Umgebungsvariablen gezogen, die openvpn in
289 ## seinen Ereignisskripten setzt.
291 local log_target=
"$1"
292 # die config-Datei enthaelt den Dienst-Namen
293 local service_name=$(basename
"${config%.conf}")
295 case "$script_type" in
300 # der openwrt-Build von openvpn setzt wohl leider nicht die
"time_duration"-Umgebungsvariable
301 [ -z
"${time_duration:-}" ] && time_duration=$(($(date +%
s) - $daemon_start_time))
302 # Verbindungsverlust durch fehlende openvpn-Pings?
303 if [
"${signal:-}" =
"ping-restart" ]; then
305 "Lost connection with ${remote_1}:${remote_port_1} after ${time_duration}s"
307 set_service_value
"$service_name" "status" "n"
309 [ -n
"$pid_file" ] && rm -f
"$pid_file" ||
true
312 "Closing connection with ${remote_1}:${remote_port_1} after ${time_duration}s"
322 ## @fn prepare_openvpn_service()
323 ## @param Name eines Diensts
324 ## @param template_file Name einer openvpn-Konfigurationsvorlage
325 ## @brief Erzeuge oder aktualisiere einen OpenVPN-Dienst
327 trap
"error_trap prepare_openvpn_service '$*'" $GUARD_TRAPS
328 local service_name=
"$1"
329 local template_file=
"$2"
330 local pid_file=
"/var/run/${service_name}.pid"
331 local config_file=
"$OPENVPN_CONFIG_BASEDIR/${service_name}.conf"
332 set_service_value
"$service_name" "template_file" "$template_file"
333 set_service_value
"$service_name" "config_file" "$config_file"
334 set_service_value
"$service_name" "pid_file" "$pid_file"
338 ## @fn openvpn_get_mtu()
339 ## @brief Ermittle die MTU auf dem Weg zum Anbieter des Diensts.
340 ## @details The output can be easily parsed via 'cut'. Even the full status output of openvpn is safe for parsing since potential tabulator characters are removed.
341 ## @returns One line consisting of five fields separated by tab characters is returned (tried_to_remote real_to_remote tried_from_remote real_from_remote full_status_output). Failed tests are indicated by an empty result.
343 trap
"error_trap openvpn_get_mtu '$*'" $GUARD_TRAPS
344 local service_name=
"$1"
345 local config_file=$(mktemp -t
"MTU-${service_name}-XXXXXXX")
346 local pid_file=
"$(mktemp)"
347 local log_file=
"$(get_service_log_filename "$service_name
" "openvpn
" "mtu
")"
355 # kein Netzwerk konfigurieren
356 echo
"ifconfig-noexec"
360 # kein Netzwerkinterface, keine pid-Datei
372 # Test-Schluessel verwenden, falls kein echter Schluessel vorhanden ist
373 # TOOD: Aktuell funktioniert der MTU-Test nicht mit dem Testzertifikat (es wurde von der falschen CA ausgestellt)
374 while read
key default;
do
375 filename=
"$(_get_file_dict_value "key
" "$config_file
")"
378 ca $VPN_DIR_TEST/opennet-ca.crt
379 key $VPN_DIR_TEST/on_aps.key
380 cert $VPN_DIR_TEST/on_aps.crt
383 openvpn --mtu-test --config
"$config_file" 2>&1 &
384 # warte auf den Startvorgang
386 local pid=
"$(cat "$pid_file
")"
389 local mtu_out_filtered
390 while [
"$wait_loops" -gt 0 ];
do
391 # keine Fehlermeldungen (-s) falls die Log-Datei noch nicht existiert
392 mtu_out=$(grep -
s "MTU test completed" "$log_file" ||
true)
394 # Thu Jul 3 22:23:01 2014 NOTE: Empirical MTU test completed [Tried,Actual] local->remote=[1573,1573] remote->local=[1573,1573]
395 if [ -n
"$mtu_out" ]; then
396 # Ausgabe der vier Zahlen getrennt durch Tabulatoren
397 mtu_out_filtered=
"$(echo "$mtu_out
" | tr '[' ',' | tr ']' ',')"
398 # Leider koennen wir nicht alle Felder auf einmal ausgeben (tab-getrennt),
399 # da das busybox-cut nicht den --output-delimiter unterstützt.
400 echo
"$mtu_out_filtered" | cut -
d , -f 5 | tr
'\n' '\t'
401 echo
"$mtu_out_filtered" | cut -
d , -f 6 | tr
'\n' '\t'
402 echo
"$mtu_out_filtered" | cut -
d , -f 8 | tr
'\n' '\t'
403 echo
"$mtu_out_filtered" | cut -
d , -f 9 | tr
'\n' '\t'
404 # wir ersetzen alle eventuell vorhandenen Tabulatoren in der Statusausgabe - zur Vereinfachung des Parsers
405 echo -n
"$mtu_out" | tr
'\t' ' '
408 if [ -z
"$pid" -o ! -d
"/proc/$pid" ]; then
409 msg_info "failed to verify MTU resctrictions for '$host'"
415 # sicherheitshalber brechen wir den Prozess ab und loeschen alle Dateien
416 kill
"$pid" >/dev/
null 2>&1 ||
true
417 rm -f
"$config_file" "$pid_file"
418 # ist der Zaehler abgelaufen?
419 [
"$wait_loops" -eq 0 ] &&
msg_info "timeout for openvpn_get_mtu '$host' - aborting."
424 ## @fn cleanup_stale_openvpn_services()
425 ## @brief Beräumung liegengebliebener openvpn-Konfigurationen, sowie Deaktivierung funktionsunfähiger Verbindungen.
426 ## @details Verwaiste openvpn-Konfigurationen können aus zwei Grunden auftreten:
427 ## 1) nach einem reboot wurde nicht du zuletzt aktive openvpn-Verbindung ausgewählt - somit bleibt der vorher aktive uci-Konfigurationseintrag erhalten
428 ## 2) ein VPN-Verbindungsaufbau scheitert und hinterlässt einen uci-Eintrag, eine PID-Datei, jedoch keinen laufenden Prozess
430 trap
"error_trap cleanup_stale_openvpn_services '$*'" $GUARD_TRAPS
435 find_all_uci_sections openvpn openvpn |
while read uci_prefix;
do
436 config_file=$(uci_get
"${uci_prefix}.config")
437 # Keine config-Datei? Keine von uns verwaltete Konfiguration ...
438 [ -z
"$config_file" ] &&
continue
439 service_name=
"${uci_prefix#openvpn.}"
440 # Es scheint sich um eine von uns verwaltete Verbindung zu handeln.
441 # Das
"pid_file"-Attribut ist nicht persistent - nach einem Neustart kann es also leer sein.
443 # Falls die config-Datei oder die pid-Datei fehlt, dann ist es ein reboot-Fragment. Wir löschen die Überreste.
444 if [ ! -e
"$config_file" -o -z
"$pid_file" -o ! -e
"$pid_file" ]; then
445 msg_info "Removing a reboot-fragment of a previously used openvpn connection: $service_name"
447 elif check_pid_file
"$pid_file" "openvpn"; then
448 # Prozess läuft - alles gut
451 # Falls die PID-Datei existiert, jedoch veraltet ist (kein dazugehöriger Prozess läuft), dann
452 # schlug der Verbindungsaufbau fehlt (siehe "tls-exit" und "single-session").
453 # Wir markieren die Verbindung als kaputt.
454 msg_info "Marking a possibly interrupted openvpn connection as broken: $service_name"
455 set_service_value
"$service_name" "status" "n"
459 apply_changes openvpn
462 # Ende der openvpn-Doku-Gruppe