From dd3cccc5e67548dcc2dd6c6254ed6c97859085d5 Mon Sep 17 00:00:00 2001 From: Nishant Chaprana Date: Wed, 14 Oct 2020 13:59:23 +0530 Subject: [PATCH] Imported Upstream version 1.38 Change-Id: I7cada2367329445e1d200055e21d96e7e9c05047 Signed-off-by: Nishant Chaprana --- .gitignore | 4 +- AUTHORS | 5 + ChangeLog | 7 + Makefile.am | 15 +- Makefile.plugins | 58 +- README | 13 +- TODO | 35 + client/agent.c | 14 +- client/commands.c | 128 ++- client/dbus_helpers.c | 33 +- client/dbus_helpers.h | 2 +- client/vpnconnections.c | 2 +- configure.ac | 25 +- doc/agent-api.txt | 6 +- doc/coding-style.txt | 17 +- doc/config-format.txt | 2 + doc/connman-service.config.5.in | 9 + doc/connman-vpn-provider.config.5.in | 2 +- doc/connman-vpn.conf.5.in | 36 +- doc/connman.conf.5.in | 2 +- doc/connmanctl.1.in | 2 +- doc/counter-api.txt | 4 +- doc/manager-api.txt | 2 +- doc/overview-api.txt | 2 +- doc/peer-api.txt | 4 +- doc/plugin-api.txt | 2 +- doc/session-api.txt | 2 +- doc/session-overview.txt | 2 +- doc/vpn-agent-api.txt | 80 +- doc/vpn-config-format.txt | 110 +- doc/vpn-connection-api.txt | 48 +- doc/vpn-overview.txt | 41 +- doc/wifi-p2p-overview.txt | 2 +- gdhcp/client.c | 6 +- gdhcp/common.c | 10 +- gdhcp/common.h | 3 +- gsupplicant/gsupplicant.h | 3 + gsupplicant/supplicant.c | 18 + gweb/gresolv.c | 5 +- gweb/gweb.c | 3 +- include/inet.h | 1 + include/provider.h | 4 +- include/service.h | 2 + include/task.h | 6 +- plugins/ethernet.c | 8 +- plugins/iwd.c | 885 ++++++++++++++-- plugins/ofono.c | 6 +- plugins/session_policy_local.c | 6 +- plugins/tist.c | 2 +- plugins/vpn.c | 61 +- plugins/wifi.c | 24 +- scripts/{openconnect-script.c => vpn-script.c} | 8 +- src/agent.c | 6 +- src/config.c | 27 + src/connection.c | 15 +- src/connman.h | 7 +- src/dnsproxy.c | 38 +- src/error.c | 9 + src/firewall-nftables.c | 26 +- src/inet.c | 81 +- src/ipconfig.c | 272 ++--- src/iptables.c | 8 +- src/network.c | 2 +- src/notifier.c | 2 +- src/provider.c | 32 +- src/resolver.c | 39 +- src/rtnl.c | 11 +- src/service.c | 369 ++++--- src/shared/mnlg.c | 331 ++++++ src/shared/mnlg.h | 27 + src/shared/netlink.c | 666 ------------ src/shared/netlink.h | 53 - src/shared/util.c | 39 + src/shared/util.h | 5 + src/stats.c | 8 +- src/storage.c | 22 - src/task.c | 17 +- src/tethering.c | 2 +- src/timeserver.c | 2 +- src/timezone.c | 5 +- src/wispr.c | 19 + test/backtrace | 8 +- test/connect-provider | 24 +- test/disable-tethering | 8 +- test/enable-tethering | 10 +- test/get-global-timeservers | 2 +- test/get-proxy-autoconfig | 16 +- test/get-services | 10 +- test/get-state | 2 +- test/list-services | 10 +- test/monitor-connman | 4 +- test/monitor-services | 16 +- test/monitor-vpn | 4 +- test/p2p-on-supplicant | 90 +- test/remove-provider | 4 +- test/service-move-before | 6 +- test/set-clock | 4 +- test/set-domains | 4 +- test/set-global-timeservers | 4 +- test/set-ipv4-method | 8 +- test/set-ipv6-method | 8 +- test/set-nameservers | 4 +- test/set-proxy | 16 +- test/set-timeservers | 4 +- test/set-timezone | 8 +- test/show-introspection | 4 +- test/simple-agent | 88 +- test/test-clock | 6 +- test/test-compat | 2 +- test/test-connman | 78 +- test/test-counter | 10 +- test/test-manager | 26 +- test/test-new-supplicant | 2 +- test/test-session | 100 +- test/vpn-connect | 4 +- test/vpn-disconnect | 4 +- test/vpn-get | 10 +- test/vpn-property | 18 +- tools/netlink-test.c | 123 --- tools/stats-tool.c | 8 +- vpn/connman-vpn.service.in | 2 +- vpn/main.c | 68 +- vpn/plugins/l2tp.c | 45 +- vpn/plugins/libwireguard.c | 998 ++++++++++++++++++ vpn/plugins/openconnect.c | 1318 +++++++++++++++++++++--- vpn/plugins/openvpn.c | 798 +++++++++++++- vpn/plugins/pptp.c | 39 +- vpn/plugins/vpn.c | 197 +++- vpn/plugins/vpn.h | 3 +- vpn/plugins/vpnc.c | 564 +++++++++- vpn/plugins/wireguard.c | 404 ++++++++ vpn/plugins/wireguard.h | 103 ++ vpn/vpn-agent.c | 146 ++- vpn/vpn-agent.h | 12 + vpn/vpn-config.c | 7 +- vpn/vpn-provider.c | 554 +++++++++- vpn/vpn-provider.h | 21 +- vpn/vpn-settings.c | 253 +++++ vpn/vpn.h | 18 +- 139 files changed, 8056 insertions(+), 2168 deletions(-) rename scripts/{openconnect-script.c => vpn-script.c} (95%) create mode 100644 src/shared/mnlg.c create mode 100644 src/shared/mnlg.h delete mode 100644 src/shared/netlink.c delete mode 100644 src/shared/netlink.h delete mode 100644 tools/netlink-test.c create mode 100644 vpn/plugins/libwireguard.c create mode 100644 vpn/plugins/wireguard.c create mode 100644 vpn/plugins/wireguard.h create mode 100644 vpn/vpn-settings.c diff --git a/.gitignore b/.gitignore index b43336c..cd7d881 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ autom4te.cache test-driver m4/ !m4/configmake.m4 +TAGS +cscope.* connman.pc include/connman @@ -40,7 +42,7 @@ src/connman-wait-online.service src/connmand-wait-online plugins/connman.policy scripts/connman -scripts/openconnect-script +scripts/vpn-script scripts/openvpn-script scripts/connman_resolvconf.conf client/connmanctl diff --git a/AUTHORS b/AUTHORS index b992c4e..e03a071 100644 --- a/AUTHORS +++ b/AUTHORS @@ -154,3 +154,8 @@ Volodymyr Ostap Artem Yamshanov Matthias Berndt Henrik Persson +Nicola Lunghi +Yasser +Matt Vogt +David Llewellyn-Jones +David Weidenkopf diff --git a/ChangeLog b/ChangeLog index 9418de1..dedc1fe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +ver 1.38: + Fix issue with online check on IP address update. + Fix issue with OpenVPN and encrypted private keys. + Fix issue with finishing of VPN connections. + Add support for updated stable iwd APIs. + Add support for WireGuard networks. + ver 1.37: Fix issue with handling invalid gateway addresses. Fix issue with handling updates of default gateway. diff --git a/Makefile.am b/Makefile.am index 745bef0..5971ca9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,7 +55,6 @@ stats_sources = src/nostats.c endif shared_sources = src/shared/util.h src/shared/util.c \ - src/shared/netlink.h src/shared/netlink.c \ src/shared/arp.h src/shared/arp.c if DATAFILES @@ -159,7 +158,7 @@ endif if NFTABLES src_connmand_SOURCES += src/firewall-nftables.c -src_connmand_LDADD += @NFTABLES_LIBS@ +src_connmand_LDADD += @NFTABLES_LIBS@ @LIBMNL_LIBS@ endif if VPN @@ -182,7 +181,7 @@ vpn_connman_vpnd_SOURCES = $(builtin_vpn_sources) $(backtrace_sources) \ vpn/vpn-ipconfig.c src/inet.c vpn/vpn-rtnl.c \ src/dbus.c src/storage.c src/ipaddress.c src/agent.c \ vpn/vpn-agent.c vpn/vpn-agent.h src/inotify.c \ - vpn/vpn-config.c + vpn/vpn-config.c vpn/vpn-settings.c vpn_connman_vpnd_LDADD = gdbus/libgdbus-internal.la $(builtin_vpn_libadd) \ @GLIB_LIBS@ @DBUS_LIBS@ @GNUTLS_LIBS@ \ @@ -287,8 +286,8 @@ src_connmand_CFLAGS += @XTABLES_CFLAGS@ endif if NFTABLES -AM_CFLAGS += @NFTABLES_CFLAGS@ -src_connmand_CFLAGS += @NFTABLES_CFLAGS@ +AM_CFLAGS += @NFTABLES_CFLAGS@ @LIBMNL_CFLAGS@ +src_connmand_CFLAGS += @NFTABLES_CFLAGS@ @LIBMNL_CFLAGS@ endif EXTRA_DIST += vpn/vpn-dbus.conf vpn/vpn-polkit.conf @@ -340,7 +339,7 @@ noinst_PROGRAMS += tools/supplicant-test \ tools/tap-test tools/wpad-test \ tools/stats-tool tools/private-network-test \ tools/session-test \ - tools/dnsproxy-test tools/netlink-test + tools/dnsproxy-test tools_supplicant_test_SOURCES = tools/supplicant-test.c \ tools/supplicant-dbus.h tools/supplicant-dbus.c \ @@ -413,10 +412,6 @@ endif tools_dnsproxy_test_SOURCES = tools/dnsproxy-test.c tools_dnsproxy_test_LDADD = @GLIB_LIBS@ -tools_netlink_test_SOURCES = src/shared/util.c src/shared/netlink.c \ - tools/netlink-test.c -tools_netlink_test_LDADD = @GLIB_LIBS@ - endif test_scripts = test/get-state test/list-services \ diff --git a/Makefile.plugins b/Makefile.plugins index dce8b28..ab2bbe0 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -62,17 +62,41 @@ if VPN builtin_modules += vpn builtin_sources += plugins/vpn.c +if WIREGUARD +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h +if WIREGUARD_BUILTIN +builtin_vpn_modules += wireguard +builtin_vpn_sources += src/shared/mnlg.h src/shared/mnlg.c \ + vpn/plugins/wireguard.h vpn/plugins/libwireguard.c \ + vpn/plugins/wireguard.c +builtin_vpn_cflags += @LIBMNL_CFLAGS@ -DWIREGUARD=\"@WIREGUARD@\" +builtin_vpn_libadd += @LIBMNL_LIBS@ +else +vpn_plugin_LTLIBRARIES += vpn/plugins/wireguard.la +vpn_plugin_objects += $(plugins_wireguard_la_OBJECTS) +vpn_plugins_wireguard_la_SOURCES = src/shared/mnlg.h src/shared/mnlg.c \ + vpn/plugins/wireguard.h \ + vpn/plugins/libwireguard.c \ + vpn/plugins/wireguard.c +vpn_plugins_wireguard_la_CFLAGS = $(plugin_cflags) @LIBMNL_CFLAGS@ \ + -DWIREGUARD=\"@WIREGUARD@\" \ + -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ + -DSCRIPTDIR=\""$(build_scriptdir)"\" +vpn_plugins_wireguard_la_LDFLAGS = $(plugin_ldflags) +vpn_plugins_wireguard_la_LIBADD = @LIBMNL_LIBS@ +endif +endif + if OPENCONNECT +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h if OPENCONNECT_BUILTIN builtin_vpn_modules += openconnect builtin_vpn_sources += vpn/plugins/openconnect.c -builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h builtin_vpn_cflags += -DOPENCONNECT=\"@OPENCONNECT@\" else vpn_plugin_LTLIBRARIES += vpn/plugins/openconnect.la vpn_plugin_objects += $(plugins_openconnect_la_OBJECTS) -vpn_plugins_openconnect_la_SOURCES = vpn/plugins/vpn.h vpn/plugins/vpn.c \ - vpn/plugins/openconnect.c +vpn_plugins_openconnect_la_SOURCES = vpn/plugins/openconnect.c vpn_plugins_openconnect_la_CFLAGS = $(plugin_cflags) \ -DOPENCONNECT=\"@OPENCONNECT@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ @@ -82,16 +106,15 @@ endif endif if OPENVPN +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h if OPENVPN_BUILTIN builtin_vpn_modules += openvpn builtin_vpn_sources += vpn/plugins/openvpn.c -builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h builtin_vpn_cflags += -DOPENVPN=\"@OPENVPN@\" else vpn_plugin_LTLIBRARIES += vpn/plugins/openvpn.la vpn_plugin_objects += $(plugins_openvpn_la_OBJECTS) -vpn_plugins_openvpn_la_SOURCES = vpn/plugins/vpn.h vpn/plugins/vpn.c \ - vpn/plugins/openvpn.c +vpn_plugins_openvpn_la_SOURCES = vpn/plugins/openvpn.c vpn_plugins_openvpn_la_CFLAGS = $(plugin_cflags) -DOPENVPN=\"@OPENVPN@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ -DSCRIPTDIR=\""$(build_scriptdir)"\" @@ -100,16 +123,15 @@ endif endif if VPNC +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h if VPNC_BUILTIN builtin_vpn_modules += vpnc builtin_vpn_sources += vpn/plugins/vpnc.c -builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h builtin_vpn_cflags += -DVPNC=\"@VPNC@\" else vpn_plugin_LTLIBRARIES += vpn/plugins/vpnc.la vpn_plugin_objects += $(plugins_vpnc_la_OBJECTS) -vpn_plugins_vpnc_la_SOURCES = vpn/plugins/vpn.h vpn/plugins/vpn.c \ - vpn/plugins/vpnc.c +vpn_plugins_vpnc_la_SOURCES = vpn/plugins/vpnc.c vpn_plugins_vpnc_la_CFLAGS = $(plugin_cflags) -DVPNC=\"@VPNC@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ -DSCRIPTDIR=\""$(build_scriptdir)"\" @@ -118,16 +140,15 @@ endif endif if L2TP +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h if L2TP_BUILTIN builtin_vpn_modules += l2tp builtin_vpn_sources += vpn/plugins/l2tp.c -builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h builtin_vpn_cflags += -DL2TP=\"@L2TP@\" else vpn_plugin_LTLIBRARIES += vpn/plugins/l2tp.la vpn_plugin_objects += $(plugins_l2tp_la_OBJECTS) -vpn_plugins_l2tp_la_SOURCES = vpn/plugins/vpn.h vpn/plugins/vpn.c \ - vpn/plugins/l2tp.c +vpn_plugins_l2tp_la_SOURCES = vpn/plugins/l2tp.c vpn_plugins_l2tp_la_CFLAGS = $(plugin_cflags) -DL2TP=\"@L2TP@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ -DSCRIPTDIR=\""$(build_scriptdir)"\" @@ -136,16 +157,15 @@ endif endif if PPTP +builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h if PPTP_BUILTIN builtin_vpn_modules += pptp builtin_vpn_sources += vpn/plugins/pptp.c -builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h builtin_vpn_cflags += -DPPPD=\"@PPPD@\" -DPPTP=\"@PPTP@\" else vpn_plugin_LTLIBRARIES += vpn/plugins/pptp.la vpn_plugin_objects += $(plugins_pptp_la_OBJECTS) -vpn_plugins_pptp_la_SOURCES = vpn/plugins/vpn.h vpn/plugins/vpn.c \ - vpn/plugins/pptp.c +vpn_plugins_pptp_la_SOURCES = vpn/plugins/pptp.c vpn_plugins_pptp_la_CFLAGS = $(plugin_cflags) -DPPPD=\"@PPPD@\" \ -DPPTP=\"@PPTP@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ @@ -199,14 +219,14 @@ plugins_iospm_la_LDFLAGS = $(plugin_ldflags) endif if OPENCONNECT -script_PROGRAMS += scripts/openconnect-script +script_PROGRAMS += scripts/vpn-script -scripts_openconnect_script_LDADD = @DBUS_LIBS@ +scripts_vpn_script_LDADD = @DBUS_LIBS@ else if VPNC -script_PROGRAMS += scripts/openconnect-script +script_PROGRAMS += scripts/vpn-script -scripts_openconnect_script_LDADD = @DBUS_LIBS@ +scripts_vpn_script_LDADD = @DBUS_LIBS@ endif endif diff --git a/README b/README index f16b9ec..e911bc2 100644 --- a/README +++ b/README @@ -278,7 +278,7 @@ If timing conditions are relevant then it is recommended command to get log traces as follows: connmand -d 2>&1 | ts '[%H:%M:%.S]' | tee connman.log -The 'ts' program is normaly avialable in the moreutils package. +The 'ts' program is normally available in the moreutils package. Kernel configuration @@ -368,6 +368,15 @@ routes will not be set by ConnMan if the uplink is a cellular network. While the same setup works well for a WiFi or ethernet uplink. +Up to (at least) version 2.4.5 of OpenVPN getting information about +private key decryption failures via management channel is missing. This +will result in attempting with the invalid key over and over as the +information about failed decryprion is not delivered to OpenVPN plugin. +The following patch to OpenVPN is required for the private key +decryption failures to be sent: +https://git.sailfishos.org/mer-core/openvpn/blob/ +4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey-passphrase- +handling.diff GnuTLS ====== @@ -418,7 +427,7 @@ the online check request (example): Connection: close Currently following information is returned from connman.net if -the connection is successfull (200 OK http response code is returned): +the connection is successful (200 OK http response code is returned): Server: nginx Date: Mon, 09 Jun 2014 09:25:42 GMT Content-Type: text/html diff --git a/TODO b/TODO index c1694e3..c10b8ce 100644 --- a/TODO +++ b/TODO @@ -194,6 +194,41 @@ VPN ids and passphrases. +- Change OpenConnect plugin to use libopenconnect + + Priority: Medium + Complexity: C4 + + Current implementation of OpenConnect uses screenscraping and interactive + mode for accepting self signed certificates and reacting to PKCS pass + phrase requests. This should be replaced with libopenconnect use. It may be + worthwhile to attempt to replace the whole authentication with the use of + openconnect_obtain_cookie() whatever authentication type is used. This + would lead to using only the cookie when connecting (--cookie-on-stdin) + and would cleanup the code at run_connect(). + + The usage of stdout can be removed as unnecessary. Cookie should be + retrieved with openconnect_obtain_cookie(). Remove this also from + connman_task_run(). + + Function is_valid_protocol() must use openconnect_get_supported_protocols. + Also the static const char *protocols[] would be unnecessary. + + Reading the stderr with byte-by-byte approach is to be removed, as well as + are the PKCS failures and requests in stderr IO channel processing. + + The use of interactive mode toggle is to be removed. Non-interactive mode + must be used, which leads to using --syslog with each authentication type + as task arg. + + If the peer certificate cannot be verified with normal means it is because + the peer certificate is self signed and the user setting + "AllowSelfSignedCert" has to be used for the verify certificate callback + reply. The callback for certificate validation must return zero if user has + allowed self signed certificates. In such case save the SHA1 fingerprint of + server certificate as it is done now, otherwise indicate error to + libopenconnect. + Tools ===== diff --git a/client/agent.c b/client/agent.c index d020889..1cad3e0 100644 --- a/client/agent.c +++ b/client/agent.c @@ -710,8 +710,8 @@ static const GDBusMethodTable agent_methods[] = { { }, }; -static int agent_register_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int agent_register_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { DBusConnection *connection = user_data; @@ -768,8 +768,8 @@ int __connmanctl_agent_register(DBusConnection *connection) return result; } -static int agent_unregister_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int agent_unregister_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error unregistering Agent: %s\n", error); @@ -819,8 +819,8 @@ static const GDBusMethodTable vpn_agent_methods[] = { { }, }; -static int vpn_agent_register_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int vpn_agent_register_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { DBusConnection *connection = user_data; @@ -872,7 +872,7 @@ int __connmanctl_vpn_agent_register(DBusConnection *connection) return result; } -static int vpn_agent_unregister_return(DBusMessageIter *iter, +static int vpn_agent_unregister_return(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { if (error) { diff --git a/client/commands.c b/client/commands.c index 097d293..94c375d 100644 --- a/client/commands.c +++ b/client/commands.c @@ -142,7 +142,7 @@ static int parse_args(char *arg, struct connman_option *options) return '?'; } -static int enable_return(DBusMessageIter *iter, const char *error, +static int enable_return(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { char *tech = user_data; @@ -154,10 +154,19 @@ static int enable_return(DBusMessageIter *iter, const char *error, else str = tech; - if (!error) + switch (errnum) { + case 0: fprintf(stdout, "Enabled %s\n", str); - else + break; + case -ENODEV: + fprintf(stderr, "%s is not available\n", str); + break; + case -EALREADY: + fprintf(stderr, "%s is already enabled\n", str); + break; + default: fprintf(stderr, "Error %s: %s\n", str, error); + } g_free(user_data); @@ -191,7 +200,7 @@ static int cmd_enable(char *args[], int num, struct connman_option *options) "Powered", DBUS_TYPE_BOOLEAN, &b); } -static int disable_return(DBusMessageIter *iter, const char *error, +static int disable_return(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { char *tech = user_data; @@ -203,10 +212,19 @@ static int disable_return(DBusMessageIter *iter, const char *error, else str = tech; - if (!error) - fprintf(stdout, "Disabled %s\n", str); - else + switch (errnum) { + case 0: + fprintf(stdout, "Disable %s\n", str); + break; + case -ENODEV: + fprintf(stderr, "%s is not available\n", str); + break; + case -EALREADY: + fprintf(stderr, "%s is already disabled\n", str); + break; + default: fprintf(stderr, "Error %s: %s\n", str, error); + } g_free(user_data); @@ -240,7 +258,7 @@ static int cmd_disable(char *args[], int num, struct connman_option *options) "Powered", DBUS_TYPE_BOOLEAN, &b); } -static int state_print(DBusMessageIter *iter, const char *error, +static int state_print(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { DBusMessageIter entry; @@ -267,7 +285,7 @@ static int cmd_state(char *args[], int num, struct connman_option *options) state_print, NULL, NULL, NULL); } -static int clock_print(DBusMessageIter *iter, const char *error, +static int clock_print(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { DBusMessageIter entry; @@ -294,7 +312,7 @@ static int cmd_clock(char *args[], int num, struct connman_option *options) clock_print, NULL, NULL, NULL); } -static int services_list(DBusMessageIter *iter, const char *error, +static int services_list(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { if (!error) { @@ -307,7 +325,7 @@ static int services_list(DBusMessageIter *iter, const char *error, return 0; } -static int peers_list(DBusMessageIter *iter, +static int peers_list(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { if (!error) { @@ -319,7 +337,7 @@ static int peers_list(DBusMessageIter *iter, return 0; } -static int tethering_clients_list(DBusMessageIter *iter, +static int tethering_clients_list(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { if (!error) { @@ -331,7 +349,7 @@ static int tethering_clients_list(DBusMessageIter *iter, return 0; } -static int object_properties(DBusMessageIter *iter, +static int object_properties(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { char *path = user_data; @@ -429,7 +447,7 @@ static int cmd_peers(char *args[], int num, struct connman_option *options) object_properties, path, NULL, NULL); } -static int technology_print(DBusMessageIter *iter, const char *error, +static int technology_print(DBusMessageIter *iter, int errnum, const char *error, void *user_data) { DBusMessageIter array; @@ -476,8 +494,8 @@ struct tether_enable { dbus_bool_t enable; }; -static int tether_set_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int tether_set_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { struct tether_enable *tether = user_data; char *str; @@ -553,8 +571,8 @@ static int tether_update(struct tether_properties *tether) return -EINPROGRESS; } -static int tether_set_ssid_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int tether_set_ssid_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { struct tether_properties *tether = user_data; @@ -569,8 +587,8 @@ static int tether_set_ssid_return(DBusMessageIter *iter, const char *error, return tether_update(tether); } -static int tether_set_passphrase_return(DBusMessageIter *iter, - const char *error, void *user_data) +static int tether_set_passphrase_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { struct tether_properties *tether = user_data; @@ -663,7 +681,7 @@ static int cmd_tethering_clients(char *args[], int num, struct connman_option *o tethering_clients_list, NULL, NULL, NULL); } -static int scan_return(DBusMessageIter *iter, const char *error, +static int scan_return(DBusMessageIter *iter, int ernnum, const char *error, void *user_data) { char *path = user_data; @@ -699,8 +717,8 @@ static int cmd_scan(char *args[], int num, struct connman_option *options) scan_return, path, NULL, NULL); } -static int connect_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int connect_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { char *path = user_data; @@ -740,8 +758,8 @@ static int cmd_connect(char *args[], int num, struct connman_option *options) iface, "Connect", connect_return, path, NULL, NULL); } -static int disconnect_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int disconnect_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { char *path = user_data; @@ -787,8 +805,8 @@ struct move_service { char *target; }; -static int move_before_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int move_before_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { struct move_service *services = user_data; char *service; @@ -845,8 +863,8 @@ static int cmd_service_move_before(char *args[], int num, services->target); } -static int move_after_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int move_after_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { struct move_service *services = user_data; char *service; @@ -903,8 +921,8 @@ static int cmd_service_move_after(char *args[], int num, services->target); } -static int config_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int config_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { char *service_name = user_data; @@ -1560,8 +1578,8 @@ static int cmd_agent(char *args[], int num, struct connman_option *options) return 0; } -static int vpnconnections_properties(DBusMessageIter *iter, const char *error, - void *user_data) +static int vpnconnections_properties(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { char *path = user_data; char *str; @@ -1590,8 +1608,8 @@ static int vpnconnections_properties(DBusMessageIter *iter, const char *error, return 0; } -static int vpnconnections_list(DBusMessageIter *iter, const char *error, - void *user_data) +static int vpnconnections_list(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (!error) __connmanctl_vpnconnections_list(iter); @@ -1782,8 +1800,8 @@ static void session_notify_remove(void) session_notify_path = NULL; } -static int session_connect_cb(DBusMessageIter *iter, const char *error, - void *user_data) +static int session_connect_cb(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error: %s\n", error); @@ -1801,8 +1819,8 @@ static int session_connect(void) session_connect_cb, NULL, NULL, NULL); } -static int session_disconnect_cb(DBusMessageIter *iter, const char *error, - void *user_data) +static int session_disconnect_cb(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) fprintf(stderr, "Error: %s\n", error); @@ -1817,8 +1835,8 @@ static int session_disconnect(void) session_disconnect_cb, NULL, NULL, NULL); } -static int session_create_cb(DBusMessageIter *iter, const char *error, - void *user_data) +static int session_create_cb(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { gboolean connect = GPOINTER_TO_INT(user_data); char *str; @@ -1990,8 +2008,8 @@ static int session_create(gboolean connect, char *args[], int num, return res; } -static int session_destroy_cb(DBusMessageIter *iter, const char *error, - void *user_data) +static int session_destroy_cb(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error destroying session: %s", error); @@ -2022,8 +2040,8 @@ static int session_destroy(void) session_destroy_append, session_path); } -static int session_config_return(DBusMessageIter *iter, const char *error, - void *user_data) +static int session_config_return(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { char *property_name = user_data; @@ -2462,8 +2480,8 @@ static char *lookup_session(const char *text, int state) return lookup_options(session_options, text, state); } -static int peer_service_cb(DBusMessageIter *iter, const char *error, - void *user_data) +static int peer_service_cb(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { bool registration = GPOINTER_TO_INT(user_data); @@ -2972,8 +2990,8 @@ static void update_services(DBusMessageIter *iter) } } -static int populate_service_hash(DBusMessageIter *iter, const char *error, - void *user_data) +static int populate_service_hash(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error getting services: %s", error); @@ -3030,8 +3048,8 @@ static void add_vpnconnections(DBusMessageIter *iter) } } -static int populate_vpnconnection_hash(DBusMessageIter *iter, const char *error, - void *user_data) +static int populate_vpnconnection_hash(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { DBusMessageIter array; @@ -3104,8 +3122,8 @@ static void update_peers(DBusMessageIter *iter) } } -static int populate_peer_hash(DBusMessageIter *iter, - const char *error, void *user_data) +static int populate_peer_hash(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error getting peers: %s", error); @@ -3169,8 +3187,8 @@ static void update_technologies(DBusMessageIter *iter) } } -static int populate_technology_hash(DBusMessageIter *iter, const char *error, - void *user_data) +static int populate_technology_hash(DBusMessageIter *iter, int errnum, + const char *error, void *user_data) { if (error) { fprintf(stderr, "Error getting technologies: %s\n", error); diff --git a/client/dbus_helpers.c b/client/dbus_helpers.c index 6ca407d..929ebe1 100644 --- a/client/dbus_helpers.c +++ b/client/dbus_helpers.c @@ -30,6 +30,34 @@ #define TIMEOUT 120000 +#ifndef DBUS_ERROR_UNKNOWN_METHOD +#define DBUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_OBJECT +#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +#endif + +static int string2errno(const char *error) +{ + if (!g_strcmp0(error, DBUS_ERROR_UNKNOWN_METHOD)) + return -ENOTSUP; + if (!g_strcmp0(error, DBUS_ERROR_UNKNOWN_PROPERTY)) + return -ENOENT; + if (!g_strcmp0(error, DBUS_ERROR_UNKNOWN_OBJECT)) + return -ENODEV; + if (!g_strcmp0(error, "net.connman.Error.AlreadyEnabled")) + return -EALREADY; + if (!g_strcmp0(error, "net.connman.Error.AlreadyDisabled")) + return -EALREADY; + + return -EINVAL; +} + void __connmanctl_dbus_print(DBusMessageIter *iter, const char *pre, const char *dict, const char *sep) { @@ -159,14 +187,15 @@ static void dbus_method_reply(DBusPendingCall *call, void *user_data) dbus_error_init(&err); dbus_set_error_from_message(&err, reply); - callback->cb(NULL, err.message, callback->user_data); + callback->cb(NULL, string2errno(err.name), err.message, + callback->user_data); dbus_error_free(&err); goto end; } dbus_message_iter_init(reply, &iter); - res = callback->cb(&iter, NULL, callback->user_data); + res = callback->cb(&iter, 0, NULL, callback->user_data); end: __connmanctl_redraw_rl(); diff --git a/client/dbus_helpers.h b/client/dbus_helpers.h index 395808a..d3326a4 100644 --- a/client/dbus_helpers.h +++ b/client/dbus_helpers.h @@ -38,7 +38,7 @@ void __connmanctl_dbus_print(DBusMessageIter *iter, const char *pre, const char *dict, const char *sep); typedef int (*connmanctl_dbus_method_return_func_t)(DBusMessageIter *iter, - const char *error, void *user_data); + int errnum, const char *error, void *user_data); typedef void (*connmanctl_dbus_append_func_t)(DBusMessageIter *iter, void *user_data); diff --git a/client/vpnconnections.c b/client/vpnconnections.c index d7bcbfe..7cef6bb 100644 --- a/client/vpnconnections.c +++ b/client/vpnconnections.c @@ -72,7 +72,7 @@ static void print_connection(char *path, DBusMessageIter *iter) else str = path; - fprintf(stdout, " %c %-20s %s", state, name, str); + fprintf(stdout, " %c %-20s vpn_%s", state, name, str); } void __connmanctl_vpnconnections_list(DBusMessageIter *iter) diff --git a/configure.ac b/configure.ac index ee49a22..5041e69 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT(connman, 1.37) +AC_INIT(connman, 1.38) AC_CONFIG_MACRO_DIR([m4]) @@ -287,10 +287,20 @@ if (test "${firewall_type}" = "iptables"); then fi AM_CONDITIONAL(XTABLES, test "${found_iptables}" != "no") +found_libmnl="no" +if (test "${firewall_type}" = "nftables" -o \ + "${enable_wireguard}" != "no"); then + PKG_CHECK_MODULES(LIBMNL, [libmnl >= 1.0.0], [found_libmnl="yes"], + AC_MSG_ERROR([libmnl >= 1.0.0 not found])) + AC_SUBST(LIBMNL_CFLAGS) + AC_SUBST(LIBMNL_LIBS) +fi +AM_CONDITIONAL(LIBMNL, test "${found_libmnl}" != "no") + found_nftables="no" if (test "${firewall_type}" = "nftables"); then - PKG_CHECK_MODULES(NFTABLES, [libnftnl >= 1.0.4 libmnl >= 1.0.0], [found_nftables="yes"], - AC_MSG_ERROR([libnftnl >= 1.0.4 or libmnl >= 1.0.0 not found])) + PKG_CHECK_MODULES(NFTABLES, [libnftnl >= 1.0.4], [found_nftables="yes"], + AC_MSG_ERROR([libnftnl >= 1.0.4])) AC_SUBST(NFTABLES_CFLAGS) AC_SUBST(NFTABLES_LIBS) fi @@ -333,6 +343,12 @@ AC_ARG_ENABLE(ethernet, AC_HELP_STRING([--disable-ethernet], [enable_ethernet=${enableval}]) AM_CONDITIONAL(ETHERNET, test "${enable_ethernet}" != "no") +AC_ARG_ENABLE(wireguard, AC_HELP_STRING([--disable-wireguard], + [disable Wireguard support]), + [enable_wireguard=${enableval}]) +AM_CONDITIONAL(WIREGUARD, test "${enable_wireguard}" != "no") +AM_CONDITIONAL(WIREGUARD_BUILTIN, test "${enable_wireguard}" = "builtin") + AC_ARG_ENABLE(gadget, AC_HELP_STRING([--disable-gadget], [disable USB Gadget support]), [enable_gadget=${enableval}]) @@ -444,7 +460,8 @@ AM_CONDITIONAL(VPN, test "${enable_openconnect}" != "no" -o \ "${enable_openvpn}" != "no" -o \ "${enable_vpnc}" != "no" -o \ "${enable_l2tp}" != "no" -o \ - "${enable_pptp}" != "no") + "${enable_pptp}" != "no" -o \ + "${enable_wireguard}" != "no") AC_MSG_CHECKING(which DNS backend to use) AC_ARG_WITH(dns-backend, AC_HELP_STRING([--with-dns-backend=TYPE], diff --git a/doc/agent-api.txt b/doc/agent-api.txt index e3c1dcd..b2becd2 100644 --- a/doc/agent-api.txt +++ b/doc/agent-api.txt @@ -36,7 +36,7 @@ Methods void Release() void RequestBrowser(object service, string url) This method gets called when it is required - to ask the user to open a website to procceed + to ask the user to open a website to proceed with login handling. This can happen if connected to a hotspot portal @@ -54,7 +54,7 @@ Methods void Release() keys are the field names and the values are the actual fields. Alternatively an error indicating that the request got canceled can be returned. - OperationAborted will be return on a successfull + OperationAborted will be return on a successful cancel request. Most common return field names are "Name" and of @@ -121,7 +121,7 @@ Fields string Name string PreviousPassphrase The previous passphrase successfully saved, i.e. - which led to a successfull connection. This field is + which led to a successful connection. This field is provided as an informational argument when connecting with it does not work anymore, for instance when it has been changed on the AP. Such argument appears when diff --git a/doc/coding-style.txt b/doc/coding-style.txt index 97410ce..c2fdc71 100644 --- a/doc/coding-style.txt +++ b/doc/coding-style.txt @@ -155,10 +155,19 @@ for (i = 0; i < 3; i++) { } -M8: Use g_try_malloc instead of g_malloc -======================================== -When g_malloc fails, the whole program would exit. Most of time, this is not -the expected behavior, and you may want to use g_try_malloc instead. +M8: Abort if small allocation fail +================================== +When g_malloc fails, the whole program would exit. Small allocations +are very unlikely to fail and if an allocations is not possible, it is +very likely the error code can't recover from this +situation. Furthermore, many of the error paths are not tested at +all. Instead use g_malloc() when the allocation fails and rely on an +external watchdog to restart the program. + +Furthermore, Glib's functions such as g_strdup use g_malloc which +obviously terminates the program anyway. + +For large allocation using g_try_malloc is still the right choice. Example: additional = g_try_malloc(len - 1); // correct diff --git a/doc/config-format.txt b/doc/config-format.txt index 584220f..cdde9cb 100644 --- a/doc/config-format.txt +++ b/doc/config-format.txt @@ -59,6 +59,8 @@ Allowed fields: interface is used. The byte values must have prefix 0 added, the bytes must be separated by ":" char and its length must be exactly 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 = 17 characters. +- DeviceName: The interface name where this setting should be applied, e.g. + eth0. The MAC address will take preference over DeviceName in matching. - Nameservers: Comma separated list of nameservers - SearchDomains: Comma separated list of DNS search domains - Timeservers: Comma separated list of timeservers diff --git a/doc/connman-service.config.5.in b/doc/connman-service.config.5.in index eb63f22..701f61f 100644 --- a/doc/connman-service.config.5.in +++ b/doc/connman-service.config.5.in @@ -59,6 +59,10 @@ IPv6 privacy settings as per RFC3041. MAC address of the interface to be used. If not specified, the first found interface is used. Must be in format ab:cd:ef:01:23:45. .TP +.BI DeviceName= ifname +Device name the interface to be used, e.g. eth0. MAC takes preference +over DeviceName. +.TP .BI Nameservers= servers Comma separated list of nameservers. .TP @@ -209,6 +213,11 @@ Name = my_home_wifi Passphrase = password IPv4 = 192.168.2.2/255.255.255.0/192.168.2.1 MAC = 06:05:04:03:02:01 + +[service_vlan] +Type = ethernet +DeviceName = enp4s0.1 +IPv4 = 192.168.1.42/255.255.255.0/192.168.1.1 .fi .SH "SEE ALSO" .BR connman (8) diff --git a/doc/connman-vpn-provider.config.5.in b/doc/connman-vpn-provider.config.5.in index 51d547b..cea99e6 100644 --- a/doc/connman-vpn-provider.config.5.in +++ b/doc/connman-vpn-provider.config.5.in @@ -12,7 +12,7 @@ connection_name.config \- ConnMan vpn connection provisioning file \fIConnMan\fP's vpn connections are configured with so called "\fBprovisioning files\fP" which reside under \fI@vpn_storagedir@/\fP. The files can be named anything, as long as they contain only printable -ascii characers, for example letters, numbers and underscores. The file +ascii characters, for example letters, numbers and underscores. The file must end with \fB.config\fP. Each VPN connection requires a provisioning file, but multiple connections can be specified in the same file. .SH "FILE FORMAT" diff --git a/doc/connman-vpn.conf.5.in b/doc/connman-vpn.conf.5.in index 20d30fc..22d3241 100644 --- a/doc/connman-vpn.conf.5.in +++ b/doc/connman-vpn.conf.5.in @@ -14,6 +14,18 @@ is a configuration file for ConnMan-VPN. The configuration file is optional but it can be used to set up various aspects of ConnMan-VPN's behavior. The location of the file may be changed through use of the \fB\-\-config= \fRargument for \fBconnman-vpn\fP(8). +.P +DAC privileges (user, group and supplementary groups) of a VPN binary +ran by \fBconnman-vpn\fP(8) can be controlled by this configuration. +Configuration in +.B connman-vpn.conf +is for all VPN types and can be overridden by defining separate configs into +.B @sysconfdir@/connman/vpn-plugin/ +using the plugin name + .conf suffix using the same syntax. For example, +for OpenVPN the path to config is +.B @sysconfdir@/connman/vpn-plugin/openvpn.conf +which will override any value in the main configuration. + .SH "FILE FORMAT" .P The configuration file consists of sections (groups) of key-value pairs. @@ -30,12 +42,32 @@ This section is the only mandatory section of the configuration file. Set input request timeout. Default is 300 seconds. The request for inputs like passphrase will timeout after certain amount of time. Use this setting to increase the value in case of different user interface designs. -.SH "EXAMPLE" -The following example configuration sets InputRequestTimeout to 10 minutes. +.SS [DACPrivileges] +This section controls the DAC privileges to use for a VPN binary used by a VPN +plugin. DAC privileges that can be set are user, group and supplementary groups. +.TP +.BI User= username/uid +User on the system to use for running VPN binary. Username or uid can be used. +.TP +.BI Group= groupname/gid +The main group to use for running VPN binary. Group name or gid can be used. +.TP +.BI SupplementaryGroups= groupnames/gids +Comma separated list of supplementary groups to set for the VPN binary. Groups +can be defined with their names or gid's. +.SH "EXAMPLES" +The following example configuration sets InputRequestTimeout to 10 minutes, +runs VPNs as user "vpn_user" of group "vpn" with additional supplementary +groups "inet" and "net_admin". .PP .nf [General] InputRequestTimeout = 600 + +[DACPrivileges] +User = vpn_user +Group = vpn +SupplementaryGroups = inet, net_admin .fi .SH "SEE ALSO" .BR connman (8), \ connman-vpn (8) diff --git a/doc/connman.conf.5.in b/doc/connman.conf.5.in index e42b22f..a90c229 100644 --- a/doc/connman.conf.5.in +++ b/doc/connman.conf.5.in @@ -87,7 +87,7 @@ technology even if not setup and saved to storage. List of technoolgies which are always connected regardless of PreferredTechnologies setting (AutoConnect = true). The default value is empty and this feature is disabled unless -explicitely enabled in the config file. +explicitly enabled in the config file. .TP .BI PreferredTechnologies= technology\fR[,...] List of preferred technologies from the most preferred diff --git a/doc/connmanctl.1.in b/doc/connmanctl.1.in index 47d9c30..d87472c 100644 --- a/doc/connmanctl.1.in +++ b/doc/connmanctl.1.in @@ -47,7 +47,7 @@ Shows the abbreviated help menu in the terminal. .PP .TP .B state -Shows the system properties. Includes ths online state of the +Shows the system properties. Includes the online state of the system, offline mode, and session mode. .PP .TP diff --git a/doc/counter-api.txt b/doc/counter-api.txt index 32411d5..c0d9b76 100644 --- a/doc/counter-api.txt +++ b/doc/counter-api.txt @@ -47,12 +47,12 @@ Methods void Release() RX.Errors - Total number of erronous packets + Total number of erroneous packets received. TX.Errors - Total number of erronous packets + Total number of erroneous packets sent. RX.Dropped diff --git a/doc/manager-api.txt b/doc/manager-api.txt index bfb07bd..6eaa0a3 100644 --- a/doc/manager-api.txt +++ b/doc/manager-api.txt @@ -149,7 +149,7 @@ Methods dict GetProperties() creation of a tun/tap interface, and IP configuration, NAT and IP forwarding on that interface. - An object path, a dictionnary and a file descriptor + An object path, a dictionary and a file descriptor with IP settings are returned. Possible Errors: [service].Error.InvalidArguments diff --git a/doc/overview-api.txt b/doc/overview-api.txt index fd51d70..d7f9d89 100644 --- a/doc/overview-api.txt +++ b/doc/overview-api.txt @@ -214,7 +214,7 @@ with spaces. In addition to WiFi naming, WiFi networks are subject to a grouping policy performed around SSID and security type. This means that one service will be -seen for N WiFi networks providing the same SSID and the same security metod. +seen for N WiFi networks providing the same SSID and the same security method. For instance, if 5 APs are servicing an SSID called "TEST" with WPA2 authentication and 3 APs are servicing the same SSID with open authentication method, the user will see only two services listed with the name "TEST" diff --git a/doc/peer-api.txt b/doc/peer-api.txt index cc094ff..5256c5e 100644 --- a/doc/peer-api.txt +++ b/doc/peer-api.txt @@ -98,5 +98,5 @@ Properties string State [readonly] [experimental] array{byte} WiFiDisplayIEs [readonly] - The TLV formated byte array representing the - WiFi Display Informations Elements. + The TLV formatted byte array representing the + WiFi Display Information Elements. diff --git a/doc/plugin-api.txt b/doc/plugin-api.txt index 36391e9..8a6e0e8 100644 --- a/doc/plugin-api.txt +++ b/doc/plugin-api.txt @@ -157,7 +157,7 @@ associate the new Network with the existing Device entity (the local Bluetooth Adapter). Then in the vtable's connect method all the needed pieces to perform a -connection shall be perfomed. +connection shall be performed. To learn how to use the connman_network_*() functions such as connman_network_set_index() and connman_network_set_connected() see diff --git a/doc/session-api.txt b/doc/session-api.txt index 46ac5f3..8bfcf6b 100644 --- a/doc/session-api.txt +++ b/doc/session-api.txt @@ -140,7 +140,7 @@ Settings string State [readonly] The services are sorted in the order of the bearer entries in this list. - Also "*" matches any bearer. This is usefull to prefer + Also "*" matches any bearer. This is useful to prefer certain bearers such as 'wifi' with a fallback to any other available bearer. diff --git a/doc/session-overview.txt b/doc/session-overview.txt index 976c351..70dd9ee 100644 --- a/doc/session-overview.txt +++ b/doc/session-overview.txt @@ -26,7 +26,7 @@ behind this is that a session doesn't request a connection for itself instead waits until another session actively requires to go online. This is comparable to piggy-backing. -Connnect() +Connect() +------+ | v +------------+ diff --git a/doc/vpn-agent-api.txt b/doc/vpn-agent-api.txt index 72bee9d..ffa6fad 100644 --- a/doc/vpn-agent-api.txt +++ b/doc/vpn-agent-api.txt @@ -78,13 +78,24 @@ Fields string Username string OpenConnect.ClientCert Informational field containing a pkcs11 URL or a path - name for the client certificate. + name for the client certificate. string OpenConnect.Cookie Return the OpenConnect cookie value that is used for authenticating the VPN session. + string OpenConnect.PKCSClientCert + + Informational field containing a PKCS#1/PKCS#8/PKCS#12 + URL or a path name for the PKCS#1/PKCS#8/PKCS#12 client + certificate. + + string OpenConnect.PKCSPassword + + Password for decrypting PKCS#8/PKCS#12 client + certificate. + string OpenConnect.ServerCert Return the OpenConnect server hash used to identify @@ -96,6 +107,48 @@ Fields string Username Return the final VPN server to use after possible web authentication logins, selections and redirections. + string OpenVPN.PrivateKeyPassword + + Return the private key password used to decrypt the + encrypted OpenVPN private key file. + + boolean AllowStoreCredentials + + Indicates to the receiving UI whether the values + entered by the user can be stored for future use. + "Requirement" should be set to "control". A "Value" + of true indicates that the option to store the + credentials can be offered to the user, false + indicates that no such option should be presented. + + boolean AllowRetrieveCredentials + + Tells the receiving UI whether to attempt to retrieve + previously stored values. "Requirement" should be set + to "control". "Value" should be set to true if + previously stored values can be used, false otherwise. + + boolean KeepCredentials + + Indicates to the receiving UI whether to keep ("Value" + is set "true") or clear ("Value" is set "false") the + credentials or not. "Requirement" should be set to + "control". By default this is not required to be set + and is handled only when explicitly defined as "true". + This is useful in case of having both the + AllowStoreCredentials and the AllowRetrieveCredentials + set as "false", but clearing credentials is not + required. In such case the value can be explicitly set + to "true". An example case is when the password for + encrypted Private Key is requested. + + string VpnAgent.AuthFailure + + Informational field that can be used to indicate VPN + agent that previous authentication has failed and new + credentials should be requested from user. Additional + information about the failure can be added as "Value". + Arguments string Type Contains the type of a field. For example "password", @@ -104,8 +157,8 @@ Arguments string Type string Requirement Contains the requirement option. Valid values are - "mandatory", "optional", "alternate" or - "informational". + "mandatory", "optional", "alternate", "informational" + and "control". The "alternate" value specifies that this field can be returned as an alternative to another one. @@ -117,6 +170,11 @@ Arguments string Type is here only to provide an information so a value is attached to it. + A "control" argument is used to specify behaviour. The + effect will depend on the field name and value, but + control fields will not usually be presented directly + to the user, and are not expected to be returned. + array{string} Alternates Contains the list of alternate field names this @@ -156,3 +214,19 @@ Examples Requesting a username and password for L2TP network "Requirement" : "informational" } } ==> { "OpenConnect.Cookie" : "0123456@adfsf@asasdf" } + + Requesting a username and password but without allowing + the values entered by the user to be stored. + + RequestInput("/vpn3", + { "Username" : { "Type" : "string", + "Requirement" : "mandatory" + } } + { "Password" : { "Type" : "password", + "Requirement" : "mandatory" + } } + { "AllowStoreCredentials" : { "Type" : "boolean", + "Requirement" : "control", + "Value" : false + } } + ==> { "Username" : "foo", "Password" : "secret123" } diff --git a/doc/vpn-config-format.txt b/doc/vpn-config-format.txt index 0bc62c0..91e2a63 100644 --- a/doc/vpn-config-format.txt +++ b/doc/vpn-config-format.txt @@ -32,7 +32,8 @@ Each provisioned provider must start with the [provider_*] tag. Replace * with an identifier unique to the config file. Allowed fields: -- Type: Provider type. Value of OpenConnect, OpenVPN, VPNC, L2TP or PPTP +- Type: Provider type. Value of OpenConnect, OpenVPN, VPNC, L2TP, PPTP or + WireGuard VPN related parameters (M = mandatory, O = optional): - Name: A user defined name for the VPN (M) @@ -54,8 +55,9 @@ OpenConnect VPN supports following options (see openconnect(8) for details): OpenConnect.CACert --cafile File containing other Certificate Authorities in addition to the ones in the system trust database (O) - OpenConnect.ClientCert --certificate Client certificate file, if needed - by web authentication (O) + OpenConnect.ClientCert --certificate Client certificate file, needed + by web authentication when AuthType + is set as "publickey" (O) VPN.MTU --mtu Request MTU from server as the MTU of the tunnel (O) OpenConnect.Cookie --cookie-on-stdin Cookie received as a result of the @@ -68,8 +70,73 @@ OpenConnect VPN supports following options (see openconnect(8) for details): Only usable for extremely simple VPN configurations and should normally be set only via the VPN Agent API. -If OpenConnect.Cookie or OpenConnect.ServerCert are missing, the VPN Agent will -be contacted to supply the information. + OpenConnect.AllowSelfSignedCert none Additional option to define if self + signed server certificates are + allowed. Boolean string and defaults + to false, value "true" enables the + option. Affects to the OpenConnect + internal function only: --servercert + is not added to startup parameters + and receiving self signed cert from + server terminates the connection if + set as false (or omitted) (O) + OpenConnect.AuthType Type of authentication used with + OpenConnect. Applicable values are + "cookie", "cookie_with_userpass", + "userpass", "publickey" and + "pkcs". Value "cookie" is basic + cookie based authentication. Value + "cookie_with_userpass" means that + credentials are used to retrieve the + connection cookie, which hides the + username from commandline. With + value "userpass" username and + password are used. Value "publickey" + requires CACert and UserPrivateKey + to be set. Value "pkcs" uses the + PKCSClientCert and requests password + input. Defaults to "cookie" (O) + cookie --cookie-on-stdin Default cookie based authentication + cookie_with_userpass Two phased connection, first + authentication: --cookieonly authenticate with credentials then + --passwd-on-stdin use cookie for connection. Username + --user is hidden from commandline during + connection: --cookie-on-stdin connection. + userpass --passwd-on-stdin Credential based authentication, + --user username is visible on commandline. + publickey --clientcert Non-encrypted client certificate and + --sslkey private key file is used for auth. + pkcs --cliencert Authenticate with PKCS#1/PKCS#8/ + PKCS#12 client certificate. + OpenConnect.DisableIPv6 --disable-ipv6 Do not ask for IPv6 connectivity. + Boolean string and defaults to + false, value "true" enables the + option (O) + OpenConnect.NoDTLS --no-dtls Disable DTLS and ESP (O) + OpenConnect.NoHTTPKeepalive --no-http-keepalive Disable HTTP connection + re-use to workaround issues with + some servers. Boolean string and + defaults to false, value "true" + enables the option (O) + OpenConnect.PKCSClientCert --certificate Certificate and private key in + a PKCS#1/PKCS#8/PKCS#12 structure. + Needed when AuthType is "pkcs" (O) + OpenConnect.Usergroup --usergroup Set login usergroup on remote server + (O) + OpenConnect.UserPrivateKey --sslkey SSL private key file needed by web + authentication when AuthType is set + as "publickey" (O) + +The VPN agent will be contacted to supply the information based on the +authentication type as follows: + Authentication type Information requested Saved with name + cookie OpenConnect.Cookie OpenConnect.Cookie + cookie_with_userpass Username OpenConnect.Username + Password OpenConnect.Password + userpass Username OpenConnect.Username + Password OpenConnect.Password + publickey + pkcs OpenConnect.PKCSPassword OpenConnect.PKCSPassword OpenVPN VPN supports following options (see openvpn(8) for details): Option name OpenVPN option Description @@ -92,6 +159,11 @@ OpenVPN VPN supports following options (see openvpn(8) for details): OpenVPN 2.3+. OpenVPN.TLSAuth sub-option of --tls-remote (O) OpenVPN.TLSAuthDir sub-option of --tls-remote (O) + OpenVPN.TLSCipher --tls-cipher Add an additional layer of HMAC + authentication on top of the TLS + control channel to mitigate DoS attacks + and attacks on the TLS stack. Static + key file given as parameter (0) OpenVPN.Cipher --cipher Encrypt packets with cipher algorithm given as parameter (O) OpenVPN.Auth --auth Authenticate packets with HMAC using @@ -182,7 +254,6 @@ L2TP VPN supports following options (see xl2tpd.conf(5) and pppd(8) for details) PPPD.ReqMPPEStateful mppe-stateful Allow MPPE to use stateful mode (O) PPPD.NoVJ novj No Van Jacobson compression (O) - PPTP VPN supports following options (see pptp(8) and pppd(8) for details) Option name pptp config value Description PPTP.User - PPTP user name, asked from the user @@ -207,6 +278,19 @@ PPTP VPN supports following options (see pptp(8) and pppd(8) for details) PPPD.RequirMPPEStateful mppe-stateful Allow MPPE to use stateful mode (O) PPPD.NoVJ novj No Van Jacobson compression (O) +WireGuard VPN supports following options + Option name Description + WireGuard.Address Internal IP address (local/netmask/peer) + WireGuard.ListPort Local listen port (optional) + WireGuard.DNS List of nameservers separated + by comma (optional) + WireGuard.PrivateKey Private key of interface + WireGuard.PublicKey Public key of peer + WireGuard.PresharedKey Preshared key of peer (optional) + WireGuard.AllowedIPs See Cryptokey Routing + WireGuard.EndpointPort Endpoint listen port (optional) + WireGuard.PersistentKeepalive Keep alive in seconds (optional) + Example ======= @@ -230,6 +314,7 @@ L2TP.User = username [provider_openconnect] Type = OpenConnect +AuthType = pkcs Name = Connection to corporate network using Cisco VPN Host = 7.6.5.4 Domain = corporate.com @@ -245,3 +330,16 @@ Domain = my.home.network OpenVPN.CACert = /etc/certs/cacert.pem OpenVPN.Cert = /etc/certs/cert.pem OpenVPN.Key = /etc/certs/cert.key + +[provider_wireguard] +Type = WireGuard +Name = Wireguard VPN Tunnel +Host = 3.2.5.6 +Domain = my.home.network +WireGuard.Address = 10.2.0.2/24 +WireGuard.ListenPort = 47824 +WireGuard.DNS = 10.2.0.1 +WireGuard.PrivateKey = qKIj010hDdWSjQQyVCnEgthLXusBgm3I6HWrJUaJymc= +WireGuard.PublicKey = zzqUfWGIil6QxrAGz77HE5BGUEdD2PgHYnCg3CDKagE= +WireGuard.AllowedIPs = 0.0.0.0/0, ::/0 +WireGuard.EndpointPort = 51820 diff --git a/doc/vpn-connection-api.txt b/doc/vpn-connection-api.txt index 1fd3be2..ec55788 100644 --- a/doc/vpn-connection-api.txt +++ b/doc/vpn-connection-api.txt @@ -14,13 +14,48 @@ Methods dict GetProperties() [experimental] void SetProperty(string name, variant value) [experimental] - Changes the value of the specified property. Only - properties that are listed as read-write are - changeable. On success a PropertyChanged signal - will be emitted. + Changes the value of the specified property or the + properties defined as a dict passed as variant, where + the format is equal to the dict returned by + GetProperties(). Only properties that are listed as + read-write are changeable. Property name "Properties" + indicates a dict of properties. On success a + PropertyChanged signal will be emitted for the + specified property or for all changed properties + individually. If there is no change in property value + no PropertyChanged signal is sent. Configuration is + written to disk when one or more values are changed. + In case a dict of properties are given, configuration + write is done after all properties are processed. + Specifics in dict use in contrast to setting a single + property: + - Dict can contain values set as empty strings + or arrays. This causes the values to be + cleared as if using ClearProperty(). + - If there are errors with the properties, + InvalidProperty or PermissionDenied error is + returned. InvalidProperty is sent when there + is at least one invalid property, in this + case there can be also properties that + cannot be changed (immutable properties). + If there are only immutable properties + PermissionDenied error is returned. + - The properties that are invalid or immutable + are reported back at the end of the error + message as a comma separated property name + list. + - One invalid/immutable property does not + cause the rest of the properties to be + ignored. If there are valid and invalid + properties, the valid properties emit + PropertyChanged signal and invalid are + reported back with an InvalidProperty + message. Possible Errors: [connection].Error.InvalidArguments [connection].Error.InvalidProperty + [connection].Error.PermissionDenied + [connection].Error.NotSupported void ClearProperty(string name) [experimental] @@ -28,6 +63,7 @@ Methods dict GetProperties() [experimental] Possible Errors: [connection].Error.InvalidArguments [connection].Error.InvalidProperty + [connection].Error.PermissionDenied void Connect() [experimental] @@ -149,7 +185,9 @@ Properties string State [readonly] int ProtocolFamily Protocol family of the route. Set to 4 - if IPv4 and 6 if IPv6 route. + if IPv4 and 6 if IPv6 route. Set to 0 + (PF_UNSPEC) or omit, to have it assigned + automatically. string Network diff --git a/doc/vpn-overview.txt b/doc/vpn-overview.txt index 42b6e94..d2d14a0 100644 --- a/doc/vpn-overview.txt +++ b/doc/vpn-overview.txt @@ -54,7 +54,46 @@ is established (meaning VPN client has managed to create a connection to VPN server), then State property is set to "ready" and PropertyChanged signal is sent. If the connection cannot be established, then State property is set to "failure". -After successfull connection, the relevant connection properties are sent +After successful connection, the relevant connection properties are sent by PropertyChanged signal; like IPv[4|6] information, the index of the VPN tunneling interface (if there is any), nameserver information, server specified routes etc. + +VPN agent interface +=================== + +VPN agent interface described in vpn-agent-api.txt is used for +interaction between the connectivity UI and ConnMan. A VPN agent +registered via Management interface gets requests from the VPN plugins +to input credentials or other authentication information for the VPN +connection and offers information about the VPN to be connected. + +In addition to basic credentials, there are additional types of optional +and control parameters. The user can dictate whether to store the +credentials with the optional SaveCredentials value. The VPN plugins can +also define with the control values AllowStoreCredentials, +AllowRetrieveCredentials and KeepCredentials how the VPN agent must +handle the credentials. AllowStoreCredentials as false indicates that +client cannot use SaveCredentials option. AllowRetrieveCredentials set +as false, without AllowStoreCredentials set as false should not have +that same effect and in that case user is allowed to save credentials. + +These three control values become useful when a VPN has two or more +sets of authentication credentials, second of which can be requested +when the VPN detects a need for them. The first, main credentials, +would be requested without these control values, so user is able to +select whether the credentials are saved or not with SaveCredentials +value. After the VPN initializes the connection and, e.g., needs to +decrypt a private key file, a new request is sent to VPN agent. In this +new request both AllowStoreCredentials and AllowRetrieveCredentials are +set as false indicating that in no circumstances existing credentials +stored are to be used and neither there should be option visible for +the user to select saving of the credentials. Depending on VPN agent +implementation these values can be interpreted as clearing of all the +existing credentials related to the VPN connection from the credential +storage. By including the KeepCredentials as true value the VPN can, +however, tell the VPN agent not to clear the credentials for this VPN +connection. The KeepCredentials is used to inform the VPN agent that +these new, second/third/etc. credentials are only to be queried from +the user and forgotten after that, when used in conjunction with the +AllowStoreCredentials and AllowRetrieveCredentials set as false. diff --git a/doc/wifi-p2p-overview.txt b/doc/wifi-p2p-overview.txt index 73b677c..3dbee7e 100644 --- a/doc/wifi-p2p-overview.txt +++ b/doc/wifi-p2p-overview.txt @@ -37,7 +37,7 @@ The UI willing to access to WiFi P2P technology should proceed this way: Internals ========= -Through such API, everything is made to hide irrelevant informations for the +Through such API, everything is made to hide irrelevant information for the applications, which are: - Everything related to the P2P group and the Group Owner (GO) diff --git a/gdhcp/client.c b/gdhcp/client.c index aab7437..09dfe5e 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -534,7 +534,8 @@ static int send_request(GDHCPClient *dhcp_client) if (dhcp_client->state == RENEWING) return dhcp_send_kernel_packet(&packet, dhcp_client->requested_ip, CLIENT_PORT, - dhcp_client->server_ip, SERVER_PORT); + dhcp_client->server_ip, SERVER_PORT, + dhcp_client->interface); return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, SERVER_PORT, @@ -558,7 +559,8 @@ static int send_release(GDHCPClient *dhcp_client, dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, server); return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, - server, SERVER_PORT); + server, SERVER_PORT, + dhcp_client->interface); } static gboolean ipv4ll_probe_timeout(gpointer dhcp_data); diff --git a/gdhcp/common.c b/gdhcp/common.c index 8f7a65c..1d667d1 100644 --- a/gdhcp/common.c +++ b/gdhcp/common.c @@ -595,7 +595,8 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port) + uint32_t dest_ip, int dest_port, + const char *interface) { struct sockaddr_in client; int fd, n, opt = 1; @@ -609,6 +610,13 @@ int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, if (fd < 0) return -errno; + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + interface, strlen(interface) + 1) < 0) { + int err = errno; + close(fd); + return -err; + } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { int err = errno; close(fd); diff --git a/gdhcp/common.h b/gdhcp/common.h index 6899499..9660231 100644 --- a/gdhcp/common.h +++ b/gdhcp/common.h @@ -210,7 +210,8 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len); int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port); + uint32_t dest_ip, int dest_port, + const char *interface); int dhcp_l3_socket(int port, const char *interface, int family); int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd); int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf, diff --git a/gsupplicant/gsupplicant.h b/gsupplicant/gsupplicant.h index bfb52db..7935c3a 100644 --- a/gsupplicant/gsupplicant.h +++ b/gsupplicant/gsupplicant.h @@ -268,6 +268,9 @@ int g_supplicant_interface_disconnect(GSupplicantInterface *interface, GSupplicantInterfaceCallback callback, void *user_data); +int g_supplicant_interface_set_bss_expiration_age(GSupplicantInterface *interface, + unsigned int bss_expiration_age); + int g_supplicant_interface_set_apscan(GSupplicantInterface *interface, unsigned int ap_scan); diff --git a/gsupplicant/supplicant.c b/gsupplicant/supplicant.c index 6052f7b..f56b595 100644 --- a/gsupplicant/supplicant.c +++ b/gsupplicant/supplicant.c @@ -981,6 +981,24 @@ static void interface_capability(const char *key, DBusMessageIter *iter, key, dbus_message_iter_get_arg_type(iter)); } +static void set_bss_expiration_age(DBusMessageIter *iter, void *user_data) +{ + unsigned int bss_expiration_age = GPOINTER_TO_UINT(user_data); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, + &bss_expiration_age); +} + +int g_supplicant_interface_set_bss_expiration_age(GSupplicantInterface *interface, + unsigned int bss_expiration_age) +{ + return supplicant_dbus_property_set(interface->path, + SUPPLICANT_INTERFACE ".Interface", + "BSSExpireAge", DBUS_TYPE_UINT32_AS_STRING, + set_bss_expiration_age, NULL, + GUINT_TO_POINTER(bss_expiration_age), NULL); +} + struct set_apscan_data { unsigned int ap_scan; diff --git a/gweb/gresolv.c b/gweb/gresolv.c index 38a554e..954e7cf 100644 --- a/gweb/gresolv.c +++ b/gweb/gresolv.c @@ -315,7 +315,8 @@ static int match_gai_table(struct sockaddr *sa, const struct gai_table *tbl) } } -#define DQUAD(_a,_b,_c,_d) ( ((_a)<<24) | ((_b)<<16) | ((_c)<<8) | (_d) ) +#define DQUAD(_a,_b,_c,_d) ( (((uint32_t)_a)<<24) | (((uint32_t)_b)<<16) | \ + (((uint32_t)_c)<<8) | ((uint32_t)_d) ) #define V4MATCH(addr, a,b,c,d, m) ( ((addr) ^ DQUAD(a,b,c,d)) >> (32 - (m)) ) #define RFC3484_SCOPE_LINK 2 @@ -326,7 +327,7 @@ static int addr_scope(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (void *)sa; - guint32 addr = ntohl(sin->sin_addr.s_addr); + uint32_t addr = ntohl(sin->sin_addr.s_addr); if (V4MATCH(addr, 169,254,0,0, 16) || V4MATCH(addr, 127,0,0,0, 8)) diff --git a/gweb/gweb.c b/gweb/gweb.c index 393afe0..12fcb1d 100644 --- a/gweb/gweb.c +++ b/gweb/gweb.c @@ -1274,7 +1274,8 @@ static bool is_ip_address(const char *host) addr = NULL; result = getaddrinfo(host, NULL, &hints, &addr); - freeaddrinfo(addr); + if(!result) + freeaddrinfo(addr); return result == 0; } diff --git a/include/inet.h b/include/inet.h index 9c1918f..fdc2155 100644 --- a/include/inet.h +++ b/include/inet.h @@ -51,6 +51,7 @@ int connman_inet_clear_gateway_address(int index, const char *gateway); int connman_inet_set_gateway_interface(int index); int connman_inet_clear_gateway_interface(int index); bool connman_inet_compare_subnet(int index, const char *host); +bool connman_inet_compare_ipv6_subnet(int index, const char *host); int connman_inet_set_ipv6_address(int index, struct connman_ipaddress *ipaddress); int connman_inet_clear_ipv6_address(int index, diff --git a/include/provider.h b/include/provider.h index d28651a..b585665 100644 --- a/include/provider.h +++ b/include/provider.h @@ -111,8 +111,8 @@ int connman_provider_set_domain(struct connman_provider *provider, const char *domain); int connman_provider_set_nameservers(struct connman_provider *provider, char * const *nameservers); -int connman_provider_append_route(struct connman_provider *provider, - const char *key, const char *value); +void connman_provider_set_autoconnect(struct connman_provider *provider, + bool flag); const char *connman_provider_get_driver_name(struct connman_provider *provider); const char *connman_provider_get_save_group(struct connman_provider *provider); diff --git a/include/service.h b/include/service.h index 97fdf7d..4a129b4 100644 --- a/include/service.h +++ b/include/service.h @@ -131,6 +131,8 @@ const char *connman_service_get_proxy_url(struct connman_service *service); const char *connman_service_get_proxy_autoconfig(struct connman_service *service); bool connman_service_get_favorite(struct connman_service *service); bool connman_service_get_autoconnect(struct connman_service *service); +bool connman_service_set_autoconnect(struct connman_service *service, + bool autoconnect); /* Return non-zero value to terminate the loop, zero to continue */ typedef int (* connman_service_iterate_cb) (struct connman_service *service, diff --git a/include/task.h b/include/task.h index 9977d63..b124db7 100644 --- a/include/task.h +++ b/include/task.h @@ -42,7 +42,11 @@ typedef void (* connman_task_exit_t) (struct connman_task *task, typedef DBusMessage * (* connman_task_notify_t) (struct connman_task *task, DBusMessage *message, void *user_data); -struct connman_task *connman_task_create(const char *program); +typedef void (* connman_task_setup_t) (void *setup_data); + +struct connman_task *connman_task_create(const char *program, + connman_task_setup_t task_setup, + void *setup_data); void connman_task_destroy(struct connman_task *task); const char *connman_task_get_path(struct connman_task *task); diff --git a/plugins/ethernet.c b/plugins/ethernet.c index b0395c8..ed4208a 100644 --- a/plugins/ethernet.c +++ b/plugins/ethernet.c @@ -73,7 +73,7 @@ static int get_vlan_vid(const char *ifname) return -errno; vifr.cmd = GET_VLAN_VID_CMD; - strncpy(vifr.device1, ifname, sizeof(vifr.device1)); + stpncpy(vifr.device1, ifname, sizeof(vifr.device1)); if(ioctl(sk, SIOCSIFVLAN, &vifr) >= 0) vid = vifr.u.VID; @@ -99,14 +99,14 @@ static int get_dsa_port(const char *ifname) return -errno; memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + stpncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* check if it is a vlan and get physical interface name*/ vifr.cmd = GET_VLAN_REALDEV_NAME_CMD; - strncpy(vifr.device1, ifname, sizeof(vifr.device1)); + stpncpy(vifr.device1, ifname, sizeof(vifr.device1)); if(ioctl(sk, SIOCSIFVLAN, &vifr) >= 0) - strncpy(ifr.ifr_name, vifr.u.device2, sizeof(ifr.ifr_name)); + stpncpy(ifr.ifr_name, vifr.u.device2, sizeof(ifr.ifr_name)); /* get driver info */ drvinfocmd.cmd = ETHTOOL_GDRVINFO; diff --git a/plugins/iwd.c b/plugins/iwd.c index ddc9201..bf6a2c2 100644 --- a/plugins/iwd.c +++ b/plugins/iwd.c @@ -42,6 +42,9 @@ static GDBusProxy *agent_proxy; static GHashTable *adapters; static GHashTable *devices; static GHashTable *networks; +static GHashTable *known_networks; +static GHashTable *stations; +static GHashTable *access_points; static bool agent_registered; #define IWD_SERVICE "net.connman.iwd" @@ -50,6 +53,9 @@ static bool agent_registered; #define IWD_ADAPTER_INTERFACE "net.connman.iwd.Adapter" #define IWD_DEVICE_INTERFACE "net.connman.iwd.Device" #define IWD_NETWORK_INTERFACE "net.connman.iwd.Network" +#define IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork" +#define IWD_STATION_INTERFACE "net.connman.iwd.Station" +#define IWD_AP_INTERFACE "net.connman.iwd.AccessPoint" #define IWD_AGENT_INTERFACE "net.connman.iwd.Agent" #define IWD_AGENT_ERROR_INTERFACE "net.connman.iwd.Agent.Error" @@ -61,6 +67,9 @@ struct iwd_adapter { char *vendor; char *model; bool powered; + bool ad_hoc; + bool station; + bool ap; }; struct iwd_device { @@ -70,7 +79,7 @@ struct iwd_device { char *name; char *address; bool powered; - bool scanning; + char *mode; struct connman_device *device; }; @@ -82,11 +91,41 @@ struct iwd_network { char *name; char *type; bool connected; + char *known_network; struct iwd_device *iwdd; struct connman_network *network; }; +struct iwd_known_network { + GDBusProxy *proxy; + char *path; + char *name; + char *type; + bool hidden; + char *last_connected_time; + bool auto_connect; + int auto_connect_id; +}; + +struct iwd_station { + GDBusProxy *proxy; + char *path; + char *state; + char *connected_network; + bool scanning; +}; + +struct iwd_ap { + GDBusProxy *proxy; + char *path; + bool started; + + int index; + char *bridge; + struct connman_technology *tech; +}; + static const char *proxy_get_string(GDBusProxy *proxy, const char *property) { DBusMessageIter iter; @@ -100,6 +139,27 @@ static const char *proxy_get_string(GDBusProxy *proxy, const char *property) return str; } +static GSList *proxy_get_strings(GDBusProxy *proxy, const char *property) +{ + DBusMessageIter array, entry; + GSList *list = NULL; + + if (!g_dbus_proxy_get_property(proxy, property, &array)) + return NULL; + + dbus_message_iter_recurse(&array, &entry); + + while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING){ + const char *val; + + dbus_message_iter_get_basic(&entry, &val); + list = g_slist_prepend(list, g_strdup(val)); + dbus_message_iter_next(&entry); + } + + return list; +} + static bool proxy_get_bool(GDBusProxy *proxy, const char *property) { DBusMessageIter iter; @@ -181,7 +241,11 @@ static void cm_network_connect_cb(DBusMessage *message, void *user_data) return; DBG("%s connect failed: %s", path, dbus_error); - connman_network_set_error(iwdn->network, + if (!strcmp(dbus_error, "net.connman.iwd.Failed")) + connman_network_set_error(iwdn->network, + CONNMAN_NETWORK_ERROR_INVALID_KEY); + else + connman_network_set_error(iwdn->network, CONNMAN_NETWORK_ERROR_CONNECT_FAIL); return; } @@ -238,16 +302,16 @@ static void cm_network_disconnect_cb(DBusMessage *message, void *user_data) static int cm_network_disconnect(struct connman_network *network) { struct iwd_network *iwdn = connman_network_get_data(network); - struct iwd_device *iwdd; + struct iwd_station *iwds; - if (!iwdn) + if (!iwdn && !iwdn->iwdd) return -EINVAL; - iwdd = g_hash_table_lookup(devices, iwdn->device); - if (!iwdd) + iwds = g_hash_table_lookup(stations, iwdn->iwdd->path); + if (!iwds) return -EIO; - if (!g_dbus_proxy_method_call(iwdd->proxy, "Disconnect", + if (!g_dbus_proxy_method_call(iwds->proxy, "Disconnect", NULL, cm_network_disconnect_cb, g_strdup(iwdn->path), g_free)) return -EIO; @@ -339,6 +403,42 @@ static int cm_device_disable(struct connman_device *device) return set_device_powered(device, false); } +static void cm_device_scan_cb(DBusMessage *message, void *user_data) +{ + const char *path = user_data; + struct iwd_station *iwds; + + iwds = g_hash_table_lookup(networks, path); + if (!iwds) + return; + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) { + const char *dbus_error = dbus_message_get_error_name(message); + + DBG("%s scan failed: %s", path, dbus_error); + } +} + +static int cm_device_scan(struct connman_device *device, + struct connman_device_scan_params *params) +{ + struct iwd_device *iwdd = connman_device_get_data(device); + struct iwd_station *iwds; + + if (strcmp(iwdd->mode, "station")) + return -EINVAL; + + iwds = g_hash_table_lookup(stations, iwdd->path); + if (!iwds) + return -EIO; + + if (!g_dbus_proxy_method_call(iwds->proxy, "Scan", + NULL, cm_device_scan_cb, g_strdup(iwds->path), g_free)) + return -EIO; + + return -EINPROGRESS; +} + static struct connman_device_driver device_driver = { .name = "iwd", .type = CONNMAN_DEVICE_TYPE_WIFI, @@ -346,6 +446,7 @@ static struct connman_device_driver device_driver = { .remove = cm_device_remove, .enable = cm_device_enable, .disable = cm_device_disable, + .scan = cm_device_scan, }; static int cm_tech_probe(struct connman_technology *technology) @@ -357,92 +458,269 @@ static void cm_tech_remove(struct connman_technology *technology) { } -static struct connman_technology_driver tech_driver = { - .name = "iwd", - .type = CONNMAN_SERVICE_TYPE_WIFI, - .probe = cm_tech_probe, - .remove = cm_tech_remove, +struct tech_cb_data { + struct iwd_device *iwdd; + char *path; + char *ssid; + char *passphrase; + char *bridge; + int index; + struct connman_technology *tech; }; -static unsigned char calculate_strength(int strength) +static void tech_cb_free(struct tech_cb_data *cbd) { - unsigned char res; + g_free(cbd->path); + g_free(cbd->ssid); + g_free(cbd->passphrase); + g_free(cbd->bridge); + g_free(cbd); +} - /* - * Network's maximum signal strength expressed in 100 * dBm. - * The value is the range of 0 (strongest signal) to -10000 - * (weakest signal) - * - * ConnMan expects it in the range from 100 (strongest) to 0 - * (weakest). - */ - res = (unsigned char)((strength * -10000) / 100); +static int cm_change_tethering(struct iwd_device *iwdd, + struct connman_technology *technology, + const char *identifier, const char *passphrase, + const char *bridge, bool enabled); - return res; +static void tech_ap_start_cb(DBusMessage *message, void *user_data) +{ + + struct tech_cb_data *cbd = user_data; + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) { + const char *dbus_error = dbus_message_get_error_name(message); + + connman_warn("iwd device %s could not enable AccessPoint mode: %s", + cbd->path, dbus_error); + goto out; + } + + /* wait for 'Started' signal */ + return; +out: + cm_change_tethering(cbd->iwdd, cbd->tech, + cbd->ssid, cbd->passphrase, cbd->bridge, false); + tech_cb_free(cbd); } -static void _update_signal_strength(const char *path, int16_t signal_strength) +static void tech_ap_stop_cb(DBusMessage *message, void *user_data) { - struct iwd_network *iwdn; + struct tech_cb_data *cbd = user_data; - iwdn = g_hash_table_lookup(networks, path); - if (!iwdn) - return; + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) { + const char *dbus_error = dbus_message_get_error_name(message); - if (!iwdn->network) - return; + connman_warn("iwd device %s could not disable AccessPoint mode: %s", + cbd->path, dbus_error); + goto out; + } - connman_network_set_strength(iwdn->network, - calculate_strength(signal_strength)); + return; +out: + tech_cb_free(cbd); } -static void ordered_networks_cb(DBusMessage *message, void *user_data) +static void ap_start_append(DBusMessageIter *iter, void *user_data) { - DBusMessageIter array, entry; + struct tech_cb_data *cbd = user_data; + + DBG("ssid %s", cbd->ssid); + DBG("passphrase %s", cbd->passphrase); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &cbd->ssid); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &cbd->passphrase); +} + +static void tech_enable_tethering_cb(const DBusError *error, void *user_data) +{ + struct tech_cb_data *cbd = user_data; + struct iwd_device *iwdd; + struct iwd_ap *iwdap; DBG(""); - if (!dbus_message_iter_init(message, &array)) - return; + iwdd = g_hash_table_lookup(devices, cbd->path); + if (!iwdd) { + DBG("device already removed"); + goto out; + } - if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) - return; + if (dbus_error_is_set(error)) { + connman_warn("iwd device %s could not enable AcessPoint mode: %s", + cbd->path, error->message); + goto out; + } - dbus_message_iter_recurse(&array, &entry); - while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) { - DBusMessageIter value; - const char *path, *name, *type; - int16_t signal_strength; + iwdap = g_hash_table_lookup(access_points, iwdd->path); + if (!iwdap) { + DBG("%s no ap object found", iwdd->path); + goto out; + } + iwdap->index = cbd->index; + iwdap->bridge = g_strdup(cbd->bridge); + iwdap->tech = cbd->tech; - dbus_message_iter_recurse(&entry, &value); + if (!g_dbus_proxy_method_call(iwdap->proxy, "Start", + ap_start_append, tech_ap_start_cb, cbd, NULL)) { + connman_warn("iwd ap %s could not start AccessPoint mode: %s", + cbd->path, error->message); + goto out; + } - dbus_message_iter_get_basic(&value, &path); + return; +out: + if (iwdap) { + iwdap->index = -1; + g_free(iwdap->bridge); + iwdap->bridge = NULL; + } + tech_cb_free(cbd); +} - dbus_message_iter_next(&value); - dbus_message_iter_get_basic(&value, &name); +static void tech_disable_tethering_cb(const DBusError *error, void *user_data) +{ + struct tech_cb_data *cbd = user_data; + struct iwd_device *iwdd; + struct iwd_ap *iwdap; - dbus_message_iter_next(&value); - dbus_message_iter_get_basic(&value, &signal_strength); + DBG(""); - dbus_message_iter_next(&value); - dbus_message_iter_get_basic(&value, &type); + iwdd = g_hash_table_lookup(devices, cbd->path); + if (!iwdd) { + DBG("device already removed"); + goto out; + } - _update_signal_strength(path, signal_strength); + if (dbus_error_is_set(error)) { + connman_warn("iwd device %s could not enable Station mode: %s", + cbd->path, error->message); + goto out; + } - dbus_message_iter_next(&entry); + iwdap = g_hash_table_lookup(access_points, iwdd->path); + if (!iwdap) { + DBG("%s no ap object found", iwdd->path); + goto out; + } + + g_free(iwdap->bridge); + iwdap->index = -1; + iwdap->bridge = NULL; + iwdap->tech = NULL; + + if (!connman_inet_remove_from_bridge(cbd->index, cbd->bridge)) + goto out; + + connman_technology_tethering_notify(cbd->tech, false); + + if (!g_dbus_proxy_method_call(iwdap->proxy, "Stop", + NULL, tech_ap_stop_cb, cbd, NULL)) { + connman_warn("iwd ap %s could not start AccessPoint mode: %s", + cbd->path, error->message); + goto out; + } + + return; +out: + tech_cb_free(cbd); +} + +static int cm_change_tethering(struct iwd_device *iwdd, + struct connman_technology *technology, + const char *identifier, const char *passphrase, + const char *bridge, bool enabled) +{ + struct tech_cb_data *cbd; + int index; + const char *mode; + GDBusResultFunction cb; + + index = connman_inet_ifindex(iwdd->name); + if (index < 0) + return -ENODEV; + + cbd = g_new(struct tech_cb_data, 1); + cbd->iwdd = iwdd; + cbd->path = g_strdup(iwdd->path); + cbd->ssid = g_strdup(identifier); + cbd->passphrase = g_strdup(passphrase); + cbd->bridge = g_strdup(bridge); + cbd->tech = technology; + cbd->index = index; + + if (enabled) { + mode = "ap"; + cb = tech_enable_tethering_cb; + } else { + mode = "station"; + cb = tech_disable_tethering_cb; + } + + if (!g_dbus_proxy_set_property_basic(iwdd->proxy, + "Mode", DBUS_TYPE_STRING, &mode, + cb, cbd, NULL)) { + tech_cb_free(cbd); + return -EIO; } + + return 0; } -static void update_signal_strength(struct iwd_device *iwdd) +static int cm_tech_tethering(struct connman_technology *technology, + const char *identifier, const char *passphrase, + const char *bridge, bool enabled) { - if (!g_dbus_proxy_method_call(iwdd->proxy, - "GetOrderedNetworks", - NULL, ordered_networks_cb, - NULL, NULL)) - DBG("GetOrderedNetworks() failed"); + GHashTableIter iter; + gpointer key, value; + int err = 0, res; + + g_hash_table_iter_init(&iter, devices); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + struct iwd_device *iwdd = value; + struct iwd_adapter *iwda; + + iwda = g_hash_table_lookup(adapters, iwdd->adapter); + if (!iwda) + continue; + + if (!iwda->station || !iwda->ap ) + /* No support for Station and AccessPoint mode */ + continue; + + if (!enabled && !g_strcmp0("ap", iwdd->mode)) { + res = cm_change_tethering(iwdd, technology, identifier, + passphrase, bridge, enabled); + if (res) + connman_warn("%s switching to Station mode failed", + iwdd->path); + if (!err) + err = res; + continue; + } + + if (enabled && !g_strcmp0("station", iwdd->mode)) { + err = cm_change_tethering(iwdd, technology, identifier, + passphrase, bridge, enabled); + if (err) + connman_warn("%s switching to AccessPoint mode failed", + iwdd->path); + break; + } + } + + return err; } +static struct connman_technology_driver tech_driver = { + .name = "iwd", + .type = CONNMAN_SERVICE_TYPE_WIFI, + .probe = cm_tech_probe, + .remove = cm_tech_remove, + .set_tethering = cm_tech_tethering, +}; + static const char *security_remap(const char *security) { if (!g_strcmp0(security, "open")) @@ -498,7 +776,7 @@ static void add_network(const char *path, struct iwd_network *iwdn) connman_network_set_blob(iwdn->network, "WiFi.SSID", iwdn->name, strlen(iwdn->name)); connman_network_set_string(iwdn->network, "WiFi.Security", - iwdn->type); + security_remap(iwdn->type)); connman_network_set_string(iwdn->network, "WiFi.Mode", "managed"); if (connman_device_add_network(iwdd->device, iwdn->network) < 0) { @@ -630,17 +908,14 @@ static void device_property_change(GDBusProxy *proxy, const char *name, iwdd->powered = powered; DBG("%s powered %d", path, iwdd->powered); - } else if (!strcmp(name, "Scanning")) { - dbus_bool_t scanning; + } else if (!strcmp(name, "Mode")) { + const char *mode; - dbus_message_iter_get_basic(iter, &scanning); - iwdd->scanning = scanning; - - DBG("%s scanning %d", path, iwdd->scanning); - - if (!iwdd->scanning) - update_signal_strength(iwdd); + dbus_message_iter_get_basic(iter, &mode); + g_free(iwdd->mode); + iwdd->mode = g_strdup(mode); + DBG("%s mode %s", path, iwdd->mode); } } @@ -670,6 +945,153 @@ static void network_property_change(GDBusProxy *proxy, const char *name, } } +static unsigned char calculate_strength(int strength) +{ + unsigned char res; + + /* + * Network's maximum signal strength expressed in 100 * dBm. + * The value is the range of 0 (strongest signal) to -10000 + * (weakest signal) + * + * ConnMan expects it in the range from 100 (strongest) to 0 + * (weakest). + */ + res = (unsigned char)((strength + 10000) / 100); + + return res; +} + +static void _update_signal_strength(const char *path, int16_t signal_strength) +{ + struct iwd_network *iwdn; + + iwdn = g_hash_table_lookup(networks, path); + if (!iwdn) + return; + + connman_network_set_strength(iwdn->network, + calculate_strength(signal_strength)); + connman_network_update(iwdn->network); +} + +static void ordered_networks_cb(DBusMessage *message, void *user_data) +{ + DBusMessageIter array, entry; + + DBG(""); + + if (!dbus_message_iter_init(message, &array)) + return; + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) + return; + + dbus_message_iter_recurse(&array, &entry); + while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) { + DBusMessageIter value; + const char *path; + int16_t signal_strength; + + + dbus_message_iter_recurse(&entry, &value); + + dbus_message_iter_get_basic(&value, &path); + + dbus_message_iter_next(&value); + dbus_message_iter_get_basic(&value, &signal_strength); + + _update_signal_strength(path, signal_strength); + + dbus_message_iter_next(&entry); + } +} + +static void update_signal_strength(struct iwd_station *iwds) +{ + if (!g_dbus_proxy_method_call(iwds->proxy, + "GetOrderedNetworks", + NULL, ordered_networks_cb, + NULL, NULL)) + DBG("GetOrderedNetworks() failed"); +} + +static void station_property_change(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + struct iwd_station *iwds; + const char *path; + + path = g_dbus_proxy_get_path(proxy); + iwds = g_hash_table_lookup(stations, path); + if (!iwds) + return; + + if (!strcmp(name, "State")) { + const char *state; + + dbus_message_iter_get_basic(iter, &state); + g_free(iwds->state); + iwds->state = g_strdup(state); + + DBG("%s state %s", path, iwds->state); + } else if (!strcmp(name, "ConnectedNetwork")) { + const char *connected_network; + + g_free(iwds->connected_network); + if (iter) { + dbus_message_iter_get_basic(iter, &connected_network); + iwds->connected_network = g_strdup(connected_network); + } else { + iwds->connected_network = NULL; + } + + DBG("%s connected_network %s", path, iwds->connected_network); + } else if (!strcmp(name, "Scanning")) { + dbus_bool_t scanning; + + dbus_message_iter_get_basic(iter, &scanning); + iwds->scanning = scanning; + + if (!iwds->scanning) + update_signal_strength(iwds); + + DBG("%s scanning %d", path, iwds->scanning); + } +} + +static void ap_property_change(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + struct iwd_ap *iwdap; + const char *path; + int err; + + path = g_dbus_proxy_get_path(proxy); + iwdap = g_hash_table_lookup(access_points, path); + if (!iwdap) + return; + + if (!strcmp(name, "Started")) { + dbus_bool_t started; + + dbus_message_iter_get_basic(iter, &started); + iwdap->started = started; + + DBG("%s started %d", path, iwdap->started); + + if (iwdap->started && iwdap->index != -1) { + DBG("index %d bridge %s", iwdap->index, iwdap->bridge); + err = connman_technology_tethering_notify( + iwdap->tech, true); + if (err) + return; + err = connman_inet_add_to_bridge( + iwdap->index, iwdap->bridge); + } + } +} + static void adapter_free(gpointer data) { struct iwd_adapter *iwda = data; @@ -718,13 +1140,59 @@ static void network_free(gpointer data) g_free(iwdn->device); g_free(iwdn->name); g_free(iwdn->type); + g_free(iwdn->known_network); g_free(iwdn); } +static void known_network_free(gpointer data) +{ + struct iwd_known_network *iwdkn = data; + + if (iwdkn->proxy) { + g_dbus_proxy_unref(iwdkn->proxy); + iwdkn->proxy = NULL; + } + + if (iwdkn->auto_connect_id) + g_source_remove(iwdkn->auto_connect_id); + + g_free(iwdkn->path); + g_free(iwdkn->name); + g_free(iwdkn->type); + g_free(iwdkn->last_connected_time); + g_free(iwdkn); +} + +static void station_free(gpointer data) +{ + struct iwd_station *iwds = data; + + if (iwds->proxy) { + g_dbus_proxy_unref(iwds->proxy); + iwds->proxy = NULL; + } + g_free(iwds->path); + g_free(iwds->connected_network); + g_free(iwds); +} + +static void ap_free(gpointer data) +{ + struct iwd_ap *iwdap = data; + + if (iwdap->proxy) { + g_dbus_proxy_unref(iwdap->proxy); + iwdap->proxy = NULL; + } + g_free(iwdap->bridge); + g_free(iwdap); +} + static void create_adapter(GDBusProxy *proxy) { const char *path = g_dbus_proxy_get_path(proxy); struct iwd_adapter *iwda; + GSList *modes, *list; iwda = g_try_new0(struct iwd_adapter, 1); @@ -748,8 +1216,25 @@ static void create_adapter(GDBusProxy *proxy) iwda->model = g_strdup(proxy_get_string(proxy, "Model")); iwda->powered = proxy_get_bool(proxy, "Powered"); - DBG("%s vendor '%s' model '%s' powered %d", path, iwda->vendor, - iwda->model, iwda->powered); + modes = proxy_get_strings(proxy, "SupportedModes"); + for (list = modes; list; list = list->next) { + char *m = list->data; + + if (!m) + continue; + + if (!strcmp(m, "ad-hoc")) + iwda->ad_hoc = true; + else if (!strcmp(m, "station")) + iwda->station = true; + else if (!strcmp(m, "ap")) + iwda->ap = true; + } + g_slist_free_full(modes, g_free); + + DBG("%s vendor '%s' model '%s' powered %d ad-hoc %d station %d ap %d", + path, iwda->vendor, iwda->model, iwda->powered, + iwda->ad_hoc, iwda->station, iwda->ap); g_dbus_proxy_set_property_watch(iwda->proxy, adapter_property_change, NULL); @@ -782,11 +1267,11 @@ static void create_device(GDBusProxy *proxy) iwdd->name = g_strdup(proxy_get_string(proxy, "Name")); iwdd->address = g_strdup(proxy_get_string(proxy, "Address")); iwdd->powered = proxy_get_bool(proxy, "Powered"); - iwdd->scanning = proxy_get_bool(proxy, "Scanning"); + iwdd->mode = g_strdup(proxy_get_string(proxy, "Mode")); - DBG("adapter %s name %s address %s powered %d scanning %d", + DBG("adapter %s name %s address %s powered %d mode %s", iwdd->adapter, iwdd->name, iwdd->address, - iwdd->powered, iwdd->scanning); + iwdd->powered, iwdd->mode); g_dbus_proxy_set_property_watch(iwdd->proxy, device_property_change, NULL); @@ -955,12 +1440,11 @@ static void create_network(GDBusProxy *proxy) iwdn->name = g_strdup(proxy_get_string(proxy, "Name")); iwdn->type = g_strdup(proxy_get_string(proxy, "Type")); iwdn->connected = proxy_get_bool(proxy, "Connected"); + iwdn->known_network = g_strdup(proxy_get_string(proxy, "KnownNetwork")); - DBG("device %s name '%s' type %s connected %d", - iwdn->device, - iwdn->name, - iwdn->type, - iwdn->connected); + DBG("device %s name '%s' type %s connected %d known_network %s", + iwdn->device, iwdn->name, iwdn->type, iwdn->connected, + iwdn->known_network); g_dbus_proxy_set_property_watch(iwdn->proxy, network_property_change, NULL); @@ -968,6 +1452,214 @@ static void create_network(GDBusProxy *proxy) add_network(path, iwdn); } +struct auto_connect_cb_data { + char *path; + bool auto_connect; +}; + +static void auto_connect_cb_free(struct auto_connect_cb_data *cbd) +{ + g_free(cbd->path); + g_free(cbd); +} + +static void auto_connect_cb(const DBusError *error, void *user_data) +{ + struct auto_connect_cb_data *cbd = user_data; + struct iwd_known_network *iwdkn; + + iwdkn = g_hash_table_lookup(known_networks, cbd->path); + if (!iwdkn) + goto out; + + if (dbus_error_is_set(error)) + connman_warn("WiFi known network %s property auto connect %s", + cbd->path, error->message); + + /* property is updated via watch known_network_property_change() */ +out: + auto_connect_cb_free(cbd); +} + +static int set_auto_connect(struct iwd_known_network *iwdkn, bool auto_connect) +{ + dbus_bool_t dbus_auto_connect = auto_connect; + struct auto_connect_cb_data *cbd; + + if (proxy_get_bool(iwdkn->proxy, "AutoConnect") == auto_connect) + return -EALREADY; + + cbd = g_new(struct auto_connect_cb_data, 1); + cbd->path = g_strdup(iwdkn->path); + cbd->auto_connect = auto_connect; + + if (!g_dbus_proxy_set_property_basic(iwdkn->proxy, "AutoConnect", + DBUS_TYPE_BOOLEAN, + &dbus_auto_connect, + auto_connect_cb, cbd, NULL)) { + auto_connect_cb_free(cbd); + return -EIO; + } + + return -EINPROGRESS; +} + +static gboolean disable_auto_connect_cb(gpointer data) +{ + char *path = data; + struct iwd_known_network *iwdkn; + + iwdkn = g_hash_table_lookup(known_networks, path); + if (!iwdkn) + return FALSE; + + if (set_auto_connect(iwdkn, false) != -EINPROGRESS) + connman_warn("Failed to disable auto connect"); + + iwdkn->auto_connect_id = 0; + return FALSE; +} + +static void disable_auto_connect(struct iwd_known_network *iwdkn) +{ + if (iwdkn->auto_connect_id) + return; + + iwdkn->auto_connect_id = g_timeout_add_full(G_PRIORITY_DEFAULT, + 0, + disable_auto_connect_cb, + g_strdup(iwdkn->path), + g_free); +} + +static void known_network_property_change(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + struct iwd_known_network *iwdkn; + const char *path; + + path = g_dbus_proxy_get_path(proxy); + iwdkn = g_hash_table_lookup(known_networks, path); + if (!iwdkn) + return; + + if (!strcmp(name, "AutoConnect")) { + dbus_bool_t auto_connect; + + dbus_message_iter_get_basic(iter, &auto_connect); + iwdkn->auto_connect = auto_connect; + + DBG("%p auto_connect %d", path, iwdkn->auto_connect); + + if (iwdkn->auto_connect) + disable_auto_connect(iwdkn); + } +} + +static void create_know_network(GDBusProxy *proxy) +{ + const char *path = g_dbus_proxy_get_path(proxy); + struct iwd_known_network *iwdkn; + + iwdkn = g_try_new0(struct iwd_known_network, 1); + if (!iwdkn) { + connman_error("Out of memory creating IWD known network"); + return; + } + + iwdkn->path = g_strdup(path); + g_hash_table_replace(known_networks, iwdkn->path, iwdkn); + + iwdkn->proxy = g_dbus_proxy_ref(proxy); + + if (!iwdkn->proxy) { + connman_error("Cannot create IWD known network watcher %s", path); + g_hash_table_remove(known_networks, path); + return; + } + + iwdkn->name = g_strdup(proxy_get_string(proxy, "Name")); + iwdkn->type = g_strdup(proxy_get_string(proxy, "Type")); + iwdkn->hidden = proxy_get_bool(proxy, "Hidden"); + iwdkn->last_connected_time = + g_strdup(proxy_get_string(proxy, "LastConnectedTime")); + iwdkn->auto_connect = proxy_get_bool(proxy, "AutoConnect"); + + DBG("name '%s' type %s hidden %d, last_connection_time %s auto_connect %d", + iwdkn->name, iwdkn->type, iwdkn->hidden, + iwdkn->last_connected_time, iwdkn->auto_connect); + + g_dbus_proxy_set_property_watch(iwdkn->proxy, + known_network_property_change, NULL); + + if (iwdkn->auto_connect) + disable_auto_connect(iwdkn); +} + +static void create_station(GDBusProxy *proxy) +{ + const char *path = g_dbus_proxy_get_path(proxy); + struct iwd_station *iwds; + + iwds = g_try_new0(struct iwd_station, 1); + if (!iwds) { + connman_error("Out of memory creating IWD station"); + return; + } + + iwds->path = g_strdup(path); + g_hash_table_replace(stations, iwds->path, iwds); + + iwds->proxy = g_dbus_proxy_ref(proxy); + + if (!iwds->proxy) { + connman_error("Cannot create IWD station watcher %s", path); + g_hash_table_remove(stations, path); + return; + } + + iwds->state = g_strdup(proxy_get_string(proxy, "State")); + iwds->connected_network = g_strdup(proxy_get_string(proxy, "ConnectedNetwork")); + iwds->scanning = proxy_get_bool(proxy, "Scanning"); + + DBG("state '%s' connected_network %s scanning %d", + iwds->state, iwds->connected_network, iwds->scanning); + + g_dbus_proxy_set_property_watch(iwds->proxy, + station_property_change, NULL); +} + +static void create_ap(GDBusProxy *proxy) +{ + const char *path = g_dbus_proxy_get_path(proxy); + struct iwd_ap *iwdap; + + iwdap = g_try_new0(struct iwd_ap, 1); + if (!iwdap) { + connman_error("Out of memory creating IWD access point"); + return; + } + iwdap->index = -1; + + iwdap->path = g_strdup(path); + g_hash_table_replace(access_points, iwdap->path, iwdap); + + iwdap->proxy = g_dbus_proxy_ref(proxy); + + if (!iwdap->proxy) { + connman_error("Cannot create IWD access point watcher %s", path); + g_hash_table_remove(access_points, path); + return; + } + + iwdap->started = proxy_get_bool(proxy, "Started"); + + DBG("started %d", iwdap->started); + + g_dbus_proxy_set_property_watch(iwdap->proxy, + ap_property_change, NULL); +} + static void object_added(GDBusProxy *proxy, void *user_data) { const char *interface; @@ -989,6 +1681,12 @@ static void object_added(GDBusProxy *proxy, void *user_data) create_device(proxy); else if (!strcmp(interface, IWD_NETWORK_INTERFACE)) create_network(proxy); + else if (!strcmp(interface, IWD_KNOWN_NETWORK_INTERFACE)) + create_know_network(proxy); + else if (!strcmp(interface, IWD_STATION_INTERFACE)) + create_station(proxy); + else if (!strcmp(interface, IWD_AP_INTERFACE)) + create_ap(proxy); } static void object_removed(GDBusProxy *proxy, void *user_data) @@ -1013,6 +1711,12 @@ static void object_removed(GDBusProxy *proxy, void *user_data) g_hash_table_remove(devices, path); else if (!strcmp(interface, IWD_NETWORK_INTERFACE)) g_hash_table_remove(networks, path); + else if (!strcmp(interface, IWD_KNOWN_NETWORK_INTERFACE)) + g_hash_table_remove(known_networks, path); + else if (!strcmp(interface, IWD_STATION_INTERFACE)) + g_hash_table_remove(stations, path); + else if (!strcmp(interface, IWD_AP_INTERFACE)) + g_hash_table_remove(access_points, path); } static int iwd_init(void) @@ -1030,6 +1734,15 @@ static int iwd_init(void) networks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, network_free); + known_networks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + known_network_free); + + stations = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + station_free); + + access_points = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + ap_free); + if (connman_technology_driver_register(&tech_driver) < 0) { connman_warn("Failed to initialize technology for IWD"); goto out; @@ -1069,6 +1782,15 @@ out: if (networks) g_hash_table_destroy(networks); + if (known_networks) + g_hash_table_destroy(known_networks); + + if (stations) + g_hash_table_destroy(stations); + + if (access_points) + g_hash_table_destroy(access_points); + if (adapters) g_hash_table_destroy(adapters); @@ -1086,6 +1808,9 @@ static void iwd_exit(void) g_dbus_client_unref(client); + g_hash_table_destroy(access_points); + g_hash_table_destroy(stations); + g_hash_table_destroy(known_networks); g_hash_table_destroy(networks); g_hash_table_destroy(devices); g_hash_table_destroy(adapters); diff --git a/plugins/ofono.c b/plugins/ofono.c index 82413b6..36873d5 100644 --- a/plugins/ofono.c +++ b/plugins/ofono.c @@ -614,7 +614,7 @@ static void context_set_active_reply(struct modem_data *modem, if (success) { /* * Don't handle do anything on success here. oFono will send - * the change via PropertyChanged singal. + * the change via PropertyChanged signal. */ return; } @@ -667,7 +667,7 @@ static void cdma_cm_set_powered_reply(struct modem_data *modem, if (success) { /* * Don't handle do anything on success here. oFono will send - * the change via PropertyChanged singal. + * the change via PropertyChanged signal. */ return; } @@ -2968,7 +2968,7 @@ static void ofono_exit(void) if (modem_hash) { /* - * We should propably wait for the SetProperty() reply + * We should probably wait for the SetProperty() reply * message, because ... */ g_hash_table_foreach(modem_hash, modem_power_down, NULL); diff --git a/plugins/session_policy_local.c b/plugins/session_policy_local.c index 9beb098..32b9c69 100644 --- a/plugins/session_policy_local.c +++ b/plugins/session_policy_local.c @@ -66,7 +66,7 @@ static GHashTable *gid_hash; /* (gid, policy_group) */ struct policy_file { /* * A valid file is a keyfile with one ore more groups. All - * groups are keept in this list. + * groups are kept in this list. */ GSList *groups; }; @@ -134,7 +134,7 @@ static char *parse_selinux_type(const char *context) /* * SELinux combines Role-Based Access Control (RBAC), Type - * Enforcment (TE) and optionally Multi-Level Security (MLS). + * Enforcement (TE) and optionally Multi-Level Security (MLS). * * When SELinux is enabled all processes and files are labeled * with a contex that contains information such as user, role @@ -145,7 +145,7 @@ static char *parse_selinux_type(const char *context) * * For identifyng application we (ab)using the type * information. In the above example the haifux_exec_t type - * will be transfered to haifux_t as defined in the domain + * will be transferred to haifux_t as defined in the domain * transition and thus we are able to identify the application * as haifux_t. */ diff --git a/plugins/tist.c b/plugins/tist.c index cc2800a..c3a5e69 100644 --- a/plugins/tist.c +++ b/plugins/tist.c @@ -578,7 +578,7 @@ static int tist_init(void) err = install_ldisc(install_channel, true); if (err < 0) { - connman_error("ldisc installtion failed"); + connman_error("ldisc installation failed"); return err; } } diff --git a/plugins/vpn.c b/plugins/vpn.c index 11bab15..5668c00 100644 --- a/plugins/vpn.c +++ b/plugins/vpn.c @@ -477,6 +477,9 @@ static int errorstr2val(const char *error) { if (g_strcmp0(error, CONNMAN_ERROR_INTERFACE ".AlreadyConnected") == 0) return -EISCONN; + if (g_strcmp0(error, CONNMAN_ERROR_INTERFACE ".OperationCanceled") == 0) + return -ECANCELED; + return -ECONNREFUSED; } @@ -490,16 +493,34 @@ static void connect_reply(DBusPendingCall *call, void *user_data) if (!dbus_pending_call_get_completed(call)) return; + if (call != data->call) { + connman_error("invalid call %p to VPN connect_reply data %p " + " call %p ", call, data, data->call); + dbus_pending_call_unref(call); + return; + } + DBG("user_data %p path %s", user_data, cb_data ? cb_data->path : NULL); reply = dbus_pending_call_steal_reply(call); + if (!reply) + goto out; dbus_error_init(&error); if (dbus_set_error_from_message(&error, reply)) { int err = errorstr2val(error.name); - if (err != -EINPROGRESS) { + /* + * ECANCELED means that user has canceled authentication + * dialog. That's not really an error, it's part of a normal + * workflow. We also take it as a request to turn autoconnect + * off, in case if it was on. + */ + if (err == -ECANCELED) { + DBG("%s connect canceled", data->path); + connman_provider_set_autoconnect(data->provider, false); + } else if (err != -EINPROGRESS) { connman_error("Connect reply: %s (%s)", error.message, error.name); DBG("data %p cb_data %p", data, cb_data); @@ -522,7 +543,11 @@ static void connect_reply(DBusPendingCall *call, void *user_data) dbus_message_unref(reply); - dbus_pending_call_unref(call); +out: + dbus_pending_call_unref(data->call); + + data->call = NULL; + data->connect_pending = false; } static int connect_provider(struct connection_data *data, void *user_data, @@ -541,7 +566,10 @@ static int connect_provider(struct connection_data *data, void *user_data, return -EINVAL; } - data->connect_pending = false; + if (data->connect_pending && data->call) { + connman_info("connect already pending"); + return -EALREADY; + } /* We need to pass original dbus sender to connman-vpnd, * use a Connect2 method for that if the original dbus sender is set. @@ -575,6 +603,14 @@ static int connect_provider(struct connection_data *data, void *user_data, return -EINVAL; } + if (data->call) { + dbus_pending_call_cancel(data->call); + dbus_pending_call_unref(data->call); + } + + data->call = call; + data->connect_pending = true; + if (cb_data) { g_free(cb_data->path); cb_data->path = g_strdup(data->path); @@ -984,6 +1020,8 @@ static int disconnect_provider(struct connection_data *data) static int provider_disconnect(struct connman_provider *provider) { + int err = 0; + struct connection_data *data; DBG("provider %p", provider); @@ -993,9 +1031,17 @@ static int provider_disconnect(struct connman_provider *provider) return -EINVAL; if (provider_is_connected(data)) - return disconnect_provider(data); + err = disconnect_provider(data); - return 0; + if (data->call) { + dbus_pending_call_cancel(data->call); + dbus_pending_call_unref(data->call); + data->call = NULL; + } + + data->connect_pending = false; + + return err; } static void configuration_create_reply(DBusPendingCall *call, void *user_data) @@ -1909,13 +1955,14 @@ static bool vpn_is_valid_transport(struct connman_service *transport) static void vpn_disconnect_check_provider(struct connection_data *data) { - if (data->service_ident && provider_is_connected(data)) { + if (provider_is_connected(data)) { + /* With NULL service ident NULL is returned immediately */ struct connman_service *service = connman_service_lookup_from_identifier (data->service_ident); if (!vpn_is_valid_transport(service)) { - disconnect_provider(data); + connman_provider_disconnect(data->provider); } } } diff --git a/plugins/wifi.c b/plugins/wifi.c index f8c22be..fc304e3 100644 --- a/plugins/wifi.c +++ b/plugins/wifi.c @@ -59,11 +59,13 @@ #include +#include "src/shared/util.h" + #define CLEANUP_TIMEOUT 8 /* in seconds */ #define INACTIVE_TIMEOUT 12 /* in seconds */ #define FAVORITE_MAXIMUM_RETRIES 2 -#define BGSCAN_DEFAULT "simple:30:-45:300" +#define BGSCAN_DEFAULT "simple:30:-65:300" #define AUTOSCAN_EXPONENTIAL "exponential:3:300" #define AUTOSCAN_SINGLE "single:3" @@ -1611,15 +1613,15 @@ static int wifi_disable(struct connman_device *device) } struct last_connected { - GTimeVal modified; + struct timeval modified; gchar *ssid; int freq; }; static gint sort_entry(gconstpointer a, gconstpointer b, gpointer user_data) { - GTimeVal *aval = (GTimeVal *)a; - GTimeVal *bval = (GTimeVal *)b; + struct timeval *aval = (struct timeval *)a; + struct timeval *bval = (struct timeval *)b; /* Note that the sort order is descending */ if (aval->tv_sec < bval->tv_sec) @@ -1646,7 +1648,7 @@ static int get_latest_connections(int max_ssids, GSequence *latest_list; struct last_connected *entry; GKeyFile *keyfile; - GTimeVal modified; + struct timeval modified; gchar **services; gchar *str; char *ssid; @@ -1690,7 +1692,7 @@ static int get_latest_connections(int max_ssids, g_key_file_free(keyfile); continue; } - g_time_val_from_iso8601(str, &modified); + util_iso8601_to_timeval(str, &modified); g_free(str); ssid = g_key_file_get_string(keyfile, @@ -2228,7 +2230,7 @@ static void disconnect_callback(int result, GSupplicantInterface *interface, return; } - if (wifi->network != wifi->pending_network) + if (wifi->network && wifi->network != wifi->pending_network) connman_network_set_connected(wifi->network, false); wifi->network = NULL; @@ -3356,15 +3358,15 @@ static void sta_remove_callback(int result, info->wifi->tethering = false; connman_technology_tethering_notify(info->technology, false); - g_free(info->ifname); - g_free(info->ssid); - g_free(info); - if (info->wifi->ap_supported == WIFI_AP_SUPPORTED) { g_free(info->wifi->tethering_param->ssid); g_free(info->wifi->tethering_param); info->wifi->tethering_param = NULL; } + + g_free(info->ifname); + g_free(info->ssid); + g_free(info); return; } diff --git a/scripts/openconnect-script.c b/scripts/vpn-script.c similarity index 95% rename from scripts/openconnect-script.c rename to scripts/vpn-script.c index 5e04144..6e020e9 100644 --- a/scripts/openconnect-script.c +++ b/scripts/vpn-script.c @@ -54,9 +54,11 @@ static void append(DBusMessageIter *dict, const char *pattern) key = pattern; value = delim + 1; - /* We clean the environment before invoking openconnect, but - might as well still filter out the few things that get - added that we're not interested in */ + /* + * We clean the environment before invoking openconnect/vpnc, + * but might as well still filter out the few things that get + * added that we're not interested in + */ if (!strcmp(key, "PWD") || !strcmp(key, "_") || !strcmp(key, "SHLVL") || !strcmp(key, "connman_busname") || !strcmp(key, "connman_network")) diff --git a/src/agent.c b/src/agent.c index 8f7b19b..d6113af 100644 --- a/src/agent.c +++ b/src/agent.c @@ -238,12 +238,12 @@ int connman_agent_queue_message(void *user_context, driver = get_driver(); DBG("driver %p", driver); - if (driver && driver->context_ref) { + if (driver && driver->context_ref) queue_data->user_context = driver->context_ref(user_context); - queue_data->driver = driver; - } else + else queue_data->user_context = user_context; + queue_data->driver = driver; queue_data->msg = dbus_message_ref(msg); queue_data->timeout = timeout; queue_data->callback = callback; diff --git a/src/config.c b/src/config.c index af4f07e..62023b1 100644 --- a/src/config.c +++ b/src/config.c @@ -72,6 +72,7 @@ struct connman_config_service { char *ipv6_gateway; char *ipv6_privacy; char *mac; + char *devname; bool mdns; char **nameservers; char **search_domains; @@ -119,6 +120,7 @@ static bool cleanup = false; #define SERVICE_KEY_IPv6 "IPv6" #define SERVICE_KEY_IPv6_PRIVACY "IPv6.Privacy" #define SERVICE_KEY_MAC "MAC" +#define SERVICE_KEY_DEVICE_NAME "DeviceName" #define SERVICE_KEY_NAMESERVERS "Nameservers" #define SERVICE_KEY_SEARCH_DOMAINS "SearchDomains" #define SERVICE_KEY_TIMESERVERS "Timeservers" @@ -154,6 +156,7 @@ static const char *service_possible_keys[] = { SERVICE_KEY_IPv6, SERVICE_KEY_IPv6_PRIVACY, SERVICE_KEY_MAC, + SERVICE_KEY_DEVICE_NAME, SERVICE_KEY_MDNS, SERVICE_KEY_NAMESERVERS, SERVICE_KEY_SEARCH_DOMAINS, @@ -257,6 +260,7 @@ free_only: g_free(config_service->ipv6_gateway); g_free(config_service->ipv6_privacy); g_free(config_service->mac); + g_free(config_service->devname); g_strfreev(config_service->nameservers); g_strfreev(config_service->search_domains); g_strfreev(config_service->timeservers); @@ -478,6 +482,12 @@ static bool load_service_generic(GKeyFile *keyfile, service->mac = str; } + str = __connman_config_get_string(keyfile, group, SERVICE_KEY_DEVICE_NAME, NULL); + if (str) { + g_free(service->devname); + service->devname = str; + } + str = __connman_config_get_string(keyfile, group, SERVICE_KEY_DOMAIN, NULL); if (str) { g_free(service->domain_name); @@ -531,6 +541,7 @@ err: g_free(service->ipv6_address); g_free(service->ipv6_gateway); g_free(service->mac); + g_free(service->devname); g_free(service); return false; @@ -1271,6 +1282,22 @@ static int try_provision_service(struct connman_config_service *config, if (g_ascii_strcasecmp(device_addr, config->mac) != 0) return -ENOENT; + } else if (config->devname) { + struct connman_device *device; + const char *devname; + + device = connman_network_get_device(network); + if (!device) { + connman_error("Network device is missing"); + return -ENODEV; + } + + devname = connman_device_get_string(device, "Interface"); + + DBG("wants %s has %s", config->devname, devname); + + if (g_ascii_strcasecmp(devname, config->devname) != 0) + return -ENOENT; } if (!config->ipv6_address) { diff --git a/src/connection.c b/src/connection.c index 7a1fbce..303e992 100644 --- a/src/connection.c +++ b/src/connection.c @@ -234,6 +234,15 @@ static void set_vpn_routes(struct gateway_data *new_gateway, if (!active_gateway->ipv4_gateway) return; + + /* + * If VPN server is on same subnet as we are, skip adding + * route. + */ + if (connman_inet_compare_subnet(active_gateway->index, + gateway)) + return; + DBG("active gw %s", active_gateway->ipv4_gateway->gateway); if (g_strcmp0(active_gateway->ipv4_gateway->gateway, @@ -250,6 +259,10 @@ static void set_vpn_routes(struct gateway_data *new_gateway, if (!active_gateway->ipv6_gateway) return; + if (connman_inet_compare_ipv6_subnet(active_gateway->index, + gateway)) + return; + DBG("active gw %s", active_gateway->ipv6_gateway->gateway); if (g_strcmp0(active_gateway->ipv6_gateway->gateway, @@ -958,7 +971,7 @@ void __connman_connection_gateway_remove(struct connman_service *service, data->ipv6_gateway, do_ipv6); /* with vpn this will be called after the network was deleted, - * we need to call set_default here because we will not recieve any + * we need to call set_default here because we will not receive any * gateway delete notification. * We hit the same issue if remove_gateway() fails. */ diff --git a/src/connman.h b/src/connman.h index 8101c7b..3bdc0dc 100644 --- a/src/connman.h +++ b/src/connman.h @@ -54,6 +54,7 @@ DBusMessage *__connman_error_operation_aborted(DBusMessage *msg); DBusMessage *__connman_error_operation_timeout(DBusMessage *msg); DBusMessage *__connman_error_invalid_service(DBusMessage *msg); DBusMessage *__connman_error_invalid_property(DBusMessage *msg); +DBusMessage *__connman_error_operation_canceled(DBusMessage *msg); int __connman_manager_init(void); void __connman_manager_cleanup(void); @@ -272,7 +273,6 @@ void __connman_storage_delete_global(void); GKeyFile *__connman_storage_load_config(const char *ident); GKeyFile *__connman_storage_load_provider_config(const char *ident); -GKeyFile *__connman_storage_open_service(const char *ident); int __connman_storage_save_service(GKeyFile *keyfile, const char *ident); GKeyFile *__connman_storage_load_provider(const char *identifier); void __connman_storage_save_provider(GKeyFile *keyfile, const char *identifier); @@ -329,7 +329,6 @@ __connman_ipconfig_ref_debug(struct connman_ipconfig *ipconfig, void __connman_ipconfig_unref_debug(struct connman_ipconfig *ipconfig, const char *file, int line, const char *caller); -void __connman_ipconfig_clear_address(struct connman_ipconfig *ipconfig); void *__connman_ipconfig_get_data(struct connman_ipconfig *ipconfig); void __connman_ipconfig_set_data(struct connman_ipconfig *ipconfig, void *data); @@ -422,9 +421,9 @@ void __connman_ipconfig_set_dhcpv6_prefixes(struct connman_ipconfig *ipconfig, char **prefixes); char **__connman_ipconfig_get_dhcpv6_prefixes(struct connman_ipconfig *ipconfig); -int __connman_ipconfig_load(struct connman_ipconfig *ipconfig, +void __connman_ipconfig_load(struct connman_ipconfig *ipconfig, GKeyFile *keyfile, const char *identifier, const char *prefix); -int __connman_ipconfig_save(struct connman_ipconfig *ipconfig, +void __connman_ipconfig_save(struct connman_ipconfig *ipconfig, GKeyFile *keyfile, const char *identifier, const char *prefix); bool __connman_ipconfig_ipv6_privacy_enabled(struct connman_ipconfig *ipconfig); int __connman_ipconfig_ipv6_reset_privacy(struct connman_ipconfig *ipconfig); diff --git a/src/dnsproxy.c b/src/dnsproxy.c index 2dc7340..a7bf87a 100644 --- a/src/dnsproxy.c +++ b/src/dnsproxy.c @@ -453,7 +453,7 @@ static void send_cached_response(int sk, unsigned char *buf, int len, hdr->nscount = 0; hdr->arcount = 0; - /* if this is a negative reply, we are authorative */ + /* if this is a negative reply, we are authoritative */ if (answers == 0) hdr->aa = 1; else @@ -2912,10 +2912,46 @@ static void dnsproxy_default_changed(struct connman_service *service) cache_refresh(); } +static void dnsproxy_service_state_changed(struct connman_service *service, + enum connman_service_state state) +{ + GSList *list; + int index; + + switch (state) { + case CONNMAN_SERVICE_STATE_DISCONNECT: + case CONNMAN_SERVICE_STATE_IDLE: + break; + case CONNMAN_SERVICE_STATE_ASSOCIATION: + case CONNMAN_SERVICE_STATE_CONFIGURATION: + case CONNMAN_SERVICE_STATE_FAILURE: + case CONNMAN_SERVICE_STATE_ONLINE: + case CONNMAN_SERVICE_STATE_READY: + case CONNMAN_SERVICE_STATE_UNKNOWN: + return; + } + + index = __connman_service_get_index(service); + list = server_list; + + while (list) { + struct server_data *data = list->data; + + /* Get next before the list is changed by destroy_server() */ + list = list->next; + + if (data->index == index) { + DBG("removing server data of index %d", index); + destroy_server(data); + } + } +} + static const struct connman_notifier dnsproxy_notifier = { .name = "dnsproxy", .default_changed = dnsproxy_default_changed, .offline_mode = dnsproxy_offline_mode, + .service_state_changed = dnsproxy_service_state_changed, }; static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 }; diff --git a/src/error.c b/src/error.c index 4f24ae2..a7a8a1d 100644 --- a/src/error.c +++ b/src/error.c @@ -39,6 +39,7 @@ DBusMessage *__connman_error_failed(DBusMessage *msg, int errnum) return __connman_error_not_registered(msg); case ENXIO: return __connman_error_not_found(msg); + case EPERM: case EACCES: return __connman_error_permission_denied(msg); case EEXIST: @@ -67,6 +68,8 @@ DBusMessage *__connman_error_failed(DBusMessage *msg, int errnum) return __connman_error_in_progress(msg); case ENOKEY: return __connman_error_passphrase_required(msg); + case ECANCELED: + return __connman_error_operation_canceled(msg); } return g_dbus_create_error(msg, CONNMAN_ERROR_INTERFACE @@ -185,3 +188,9 @@ DBusMessage *__connman_error_invalid_property(DBusMessage *msg) return g_dbus_create_error(msg, CONNMAN_ERROR_INTERFACE ".InvalidProperty", "Invalid property"); } + +DBusMessage *__connman_error_operation_canceled(DBusMessage *msg) +{ + return g_dbus_create_error(msg, CONNMAN_ERROR_INTERFACE + ".OperationCanceled", "Operation canceled"); +} diff --git a/src/firewall-nftables.c b/src/firewall-nftables.c index 262b2a9..d73d661 100644 --- a/src/firewall-nftables.c +++ b/src/firewall-nftables.c @@ -22,7 +22,7 @@ /* * This file is based on the libnftnl examples: * https://git.netfilter.org/libnftnl/tree/examples - * by Pablo Neira Ayuso. and inspiration from systemd nft implemention + * by Pablo Neira Ayuso. and inspiration from systemd nft implementation * https://github.com/zonque/systemd/blob/rfc-nftnl/src/shared/firewall-util.c * by Daniel Mack. */ @@ -507,8 +507,8 @@ static int rule_delete(struct firewall_handle *handle) if (!rule) return -ENOMEM; - nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); - nftnl_rule_set(rule, NFTNL_RULE_CHAIN, handle->chain); + nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, handle->chain); nftnl_rule_set_u64(rule, NFTNL_RULE_HANDLE, handle->handle); err = socket_open_and_bind(&nl); @@ -568,8 +568,8 @@ static int build_rule_nat(const char *address, unsigned char prefixlen, if (!rule) return -ENOMEM; - nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); - nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); + nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); /* family ipv4 */ nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, NFPROTO_IPV4); @@ -673,8 +673,8 @@ static int build_rule_snat(int index, const char *address, if (!rule) return -ENOMEM; - nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); - nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); + nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); /* OIF */ expr = nftnl_expr_alloc("meta"); @@ -770,8 +770,8 @@ static int build_rule_marking(uid_t uid, uint32_t mark, struct nftnl_rule **res) if (!rule) return -ENOMEM; - nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); - nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); + nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); expr = nftnl_expr_alloc("meta"); if (!expr) @@ -826,8 +826,8 @@ static int build_rule_src_ip(const char *src_ip, uint32_t mark, struct nftnl_rul if (!rule) return -ENOMEM; - nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); - nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); + nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); /* family ipv4 */ nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, NFPROTO_IPV4); @@ -954,8 +954,8 @@ static struct nftnl_chain *build_chain(const char *name, const char *table, if (!chain) return NULL; - nftnl_chain_set(chain, NFTNL_CHAIN_TABLE, table); - nftnl_chain_set(chain, NFTNL_CHAIN_NAME, name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table); + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, name); if (type) nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, type); diff --git a/src/inet.c b/src/inet.c index b128e57..4c34143 100644 --- a/src/inet.c +++ b/src/inet.c @@ -1116,6 +1116,67 @@ bool connman_inet_compare_subnet(int index, const char *host) return ((if_addr & netmask_addr) == (host_addr & netmask_addr)); } +static bool mem_mask_equal(const void *a, const void *b, + const void *mask, size_t n) +{ + const unsigned char *addr1 = a; + const unsigned char *addr2 = b; + const unsigned char *bitmask = mask; + size_t i; + + for (i = 0; i < n; i++) { + if ((addr1[i] ^ addr2[i]) & bitmask[i]) + return false; + } + + return true; +} + +bool connman_inet_compare_ipv6_subnet(int index, const char *host) +{ + struct ifaddrs *ifaddr, *ifa; + bool rv = false; + char name[IF_NAMESIZE]; + struct in6_addr haddr; + + if (inet_pton(AF_INET6, host, &haddr) <= 0) + return false; + + if (!if_indextoname(index, name)) + return false; + + DBG("index %d interface %s", index, name); + + if (getifaddrs(&ifaddr) < 0) { + DBG("Cannot get addresses err %d/%s", errno, strerror(errno)); + return false; + } + + for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { + struct sockaddr_in6 *iaddr; + struct sockaddr_in6 *imask; + + if (!ifa->ifa_addr) + continue; + + if (strncmp(ifa->ifa_name, name, IF_NAMESIZE) != 0 || + ifa->ifa_addr->sa_family != AF_INET6) + continue; + + iaddr = (struct sockaddr_in6 *)ifa->ifa_addr; + imask = (struct sockaddr_in6 *)ifa->ifa_netmask; + + rv = mem_mask_equal(&iaddr->sin6_addr, &haddr, + &imask->sin6_addr, + sizeof(haddr)); + goto out; + } + +out: + freeifaddrs(ifaddr); + return rv; +} + int connman_inet_remove_from_bridge(int index, const char *bridge) { struct ifreq ifr; @@ -2563,11 +2624,21 @@ int __connman_inet_get_route(const char *dest_address, rth->req.u.r.rt.rtm_scope = 0; rth->req.u.r.rt.rtm_type = 0; rth->req.u.r.rt.rtm_src_len = 0; - rth->req.u.r.rt.rtm_dst_len = rp->ai_addrlen << 3; rth->req.u.r.rt.rtm_tos = 0; - __connman_inet_rtnl_addattr_l(&rth->req.n, sizeof(rth->req), RTA_DST, - &rp->ai_addr, rp->ai_addrlen); + if (rp->ai_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)rp->ai_addr; + + rth->req.u.r.rt.rtm_dst_len = 32; + __connman_inet_rtnl_addattr_l(&rth->req.n, sizeof(rth->req), + RTA_DST, &sin->sin_addr, sizeof(sin->sin_addr)); + } else if (rp->ai_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)rp->ai_addr; + + rth->req.u.r.rt.rtm_dst_len = 128; + __connman_inet_rtnl_addattr_l(&rth->req.n, sizeof(rth->req), + RTA_DST, &sin6->sin6_addr, sizeof(sin6->sin6_addr)); + } freeaddrinfo(rp); @@ -3165,7 +3236,7 @@ static int get_nfs_server_ip(const char *cmdline_file, const char *pnp_file, if (cmdline[len - 1] == '\n') cmdline[--len] = '\0'; - /* split in arguments (seperated by space) */ + /* split in arguments (separated by space) */ args = g_strsplit(cmdline, " ", 0); if (!args) { connman_error("%s: Cannot split cmdline \"%s\"\n", __func__, @@ -3368,7 +3439,7 @@ char **__connman_inet_get_pnp_nameservers(const char *pnp_file) } /* - * Perform two passes to retreive a char ** array of + * Perform two passes to retrieve a char ** array of * nameservers that are not 0.0.0.0 * * The first pass counts them, the second fills in the diff --git a/src/ipconfig.c b/src/ipconfig.c index 2565773..915c082 100644 --- a/src/ipconfig.c +++ b/src/ipconfig.c @@ -85,16 +85,101 @@ struct connman_ipdevice { int ipv6_privacy; }; +struct ipconfig_store { + GKeyFile *file; + const char *group; + const char *prefix; +}; + static GHashTable *ipdevice_hash = NULL; static GList *ipconfig_list = NULL; static bool is_ipv6_supported = false; -void __connman_ipconfig_clear_address(struct connman_ipconfig *ipconfig) +static void store_set_str(struct ipconfig_store *store, + const char *key, const char *val) + { - if (!ipconfig) + char *pk; + + if (!val || strlen(val) == 0) + return; + + pk = g_strdup_printf("%s%s", store->prefix, key); + g_key_file_set_string(store->file, store->group, pk, val); + g_free(pk); +} + +static char *store_get_str(struct ipconfig_store *store, const char *key) +{ + char *pk, *val; + + pk = g_strdup_printf("%s%s", store->prefix, key); + val = g_key_file_get_string(store->file, store->group, pk, NULL); + g_free(pk); + + return val; +} + +static void store_set_strs(struct ipconfig_store *store, + const char *key, char **val) +{ + guint len; + char *pk; + + if (!val) + return; + + len = g_strv_length(val); + if (len == 0) return; - connman_ipaddress_clear(ipconfig->address); + pk = g_strdup_printf("%s%s", store->prefix, key); + g_key_file_set_string_list(store->file, store->group, + pk, (const gchar **)val, len); + g_free(pk); +} + +static char **store_get_strs(struct ipconfig_store *store, const char *key) +{ + gsize len; + char *pk, **val; + + pk = g_strdup_printf("%s%s", store->prefix, key); + val = g_key_file_get_string_list(store->file, store->group, + pk, &len, NULL); + g_free(pk); + + if (val && len == 0) { + g_free(val); + return NULL; + } + + return val; +} + +static void store_set_int(struct ipconfig_store *store, + const char *key, int val) +{ + char *pk; + + if (val == 0) + return; + + pk = g_strdup_printf("%s%s", store->prefix, key); + g_key_file_set_integer(store->file, store->group, pk, val); + g_free(pk); +} + +static int store_get_int(struct ipconfig_store *store, const char *key) +{ + int val; + char *pk; + + pk = g_strdup_printf("%s%s", store->prefix, key); + val = g_key_file_get_integer(store->file, store->group, pk, 0); + g_free(pk); + + return val; } static void free_address_list(struct connman_ipdevice *ipdevice) @@ -1334,13 +1419,14 @@ int __connman_ipconfig_address_remove(struct connman_ipconfig *ipconfig) case CONNMAN_IPCONFIG_METHOD_OFF: break; case CONNMAN_IPCONFIG_METHOD_AUTO: - case CONNMAN_IPCONFIG_METHOD_FIXED: case CONNMAN_IPCONFIG_METHOD_DHCP: - case CONNMAN_IPCONFIG_METHOD_MANUAL: err = __connman_ipconfig_address_unset(ipconfig); connman_ipaddress_clear(ipconfig->address); return err; + case CONNMAN_IPCONFIG_METHOD_FIXED: + case CONNMAN_IPCONFIG_METHOD_MANUAL: + return __connman_ipconfig_address_unset(ipconfig); } return 0; @@ -2124,65 +2210,56 @@ void __connman_ipconfig_append_ethernet(struct connman_ipconfig *ipconfig, DBUS_TYPE_UINT16, &ipdevice->mtu); } -int __connman_ipconfig_load(struct connman_ipconfig *ipconfig, +void __connman_ipconfig_load(struct connman_ipconfig *ipconfig, GKeyFile *keyfile, const char *identifier, const char *prefix) { char *method; - char *key; char *str; + struct ipconfig_store is = { .file = keyfile, + .group = identifier, + .prefix = prefix }; DBG("ipconfig %p identifier %s", ipconfig, identifier); - key = g_strdup_printf("%smethod", prefix); - method = g_key_file_get_string(keyfile, identifier, key, NULL); + method = store_get_str(&is, "method"); if (!method) { switch (ipconfig->type) { case CONNMAN_IPCONFIG_TYPE_IPV4: ipconfig->method = CONNMAN_IPCONFIG_METHOD_DHCP; break; + case CONNMAN_IPCONFIG_TYPE_IPV6: ipconfig->method = CONNMAN_IPCONFIG_METHOD_AUTO; break; + case CONNMAN_IPCONFIG_TYPE_UNKNOWN: case CONNMAN_IPCONFIG_TYPE_ALL: ipconfig->method = CONNMAN_IPCONFIG_METHOD_OFF; break; } - } else + } else { ipconfig->method = __connman_ipconfig_string2method(method); + g_free(method); + } if (ipconfig->method == CONNMAN_IPCONFIG_METHOD_UNKNOWN) ipconfig->method = CONNMAN_IPCONFIG_METHOD_OFF; if (ipconfig->type == CONNMAN_IPCONFIG_TYPE_IPV6) { - gsize length; - char *pprefix; - if (ipconfig->method == CONNMAN_IPCONFIG_METHOD_AUTO || - ipconfig->method == CONNMAN_IPCONFIG_METHOD_MANUAL) { + ipconfig->method == CONNMAN_IPCONFIG_METHOD_MANUAL) { char *privacy; - pprefix = g_strdup_printf("%sprivacy", prefix); - privacy = g_key_file_get_string(keyfile, identifier, - pprefix, NULL); + privacy = store_get_str(&is, "privacy"); ipconfig->ipv6_privacy_config = string2privacy(privacy); - g_free(pprefix); g_free(privacy); } - pprefix = g_strdup_printf("%sDHCP.LastPrefixes", prefix); + g_strfreev(ipconfig->last_dhcpv6_prefixes); ipconfig->last_dhcpv6_prefixes = - g_key_file_get_string_list(keyfile, identifier, pprefix, - &length, NULL); - if (ipconfig->last_dhcpv6_prefixes && length == 0) { - g_free(ipconfig->last_dhcpv6_prefixes); - ipconfig->last_dhcpv6_prefixes = NULL; - } - g_free(pprefix); + store_get_strs(&is, "DHCP.LastPrefixes"); } - g_free(method); - g_free(key); switch (ipconfig->method) { case CONNMAN_IPCONFIG_METHOD_UNKNOWN: @@ -2191,39 +2268,27 @@ int __connman_ipconfig_load(struct connman_ipconfig *ipconfig, case CONNMAN_IPCONFIG_METHOD_FIXED: case CONNMAN_IPCONFIG_METHOD_MANUAL: + ipconfig->address->prefixlen = + store_get_int(&is, "netmask_prefixlen"); - key = g_strdup_printf("%snetmask_prefixlen", prefix); - ipconfig->address->prefixlen = g_key_file_get_integer( - keyfile, identifier, key, NULL); - g_free(key); - - key = g_strdup_printf("%slocal_address", prefix); g_free(ipconfig->address->local); - ipconfig->address->local = g_key_file_get_string( - keyfile, identifier, key, NULL); - g_free(key); + ipconfig->address->local = + store_get_str(&is, "local_address"); - key = g_strdup_printf("%speer_address", prefix); g_free(ipconfig->address->peer); - ipconfig->address->peer = g_key_file_get_string( - keyfile, identifier, key, NULL); - g_free(key); + ipconfig->address->peer = + store_get_str(&is, "peer_address"); - key = g_strdup_printf("%sbroadcast_address", prefix); g_free(ipconfig->address->broadcast); - ipconfig->address->broadcast = g_key_file_get_string( - keyfile, identifier, key, NULL); - g_free(key); + ipconfig->address->broadcast = + store_get_str(&is, "broadcast_address"); - key = g_strdup_printf("%sgateway", prefix); g_free(ipconfig->address->gateway); - ipconfig->address->gateway = g_key_file_get_string( - keyfile, identifier, key, NULL); - g_free(key); + ipconfig->address->gateway = + store_get_str(&is, "gateway"); break; case CONNMAN_IPCONFIG_METHOD_AUTO: - if (ipconfig->type != CONNMAN_IPCONFIG_TYPE_IPV4) break; @@ -2237,120 +2302,61 @@ int __connman_ipconfig_load(struct connman_ipconfig *ipconfig, /* fall through */ case CONNMAN_IPCONFIG_METHOD_DHCP: - - key = g_strdup_printf("%sDHCP.LastAddress", prefix); - str = g_key_file_get_string(keyfile, identifier, key, NULL); + str = store_get_str(&is, "DHCP.LastAddress"); if (str) { g_free(ipconfig->last_dhcp_address); ipconfig->last_dhcp_address = str; } - g_free(key); break; } - - return 0; } -int __connman_ipconfig_save(struct connman_ipconfig *ipconfig, +void __connman_ipconfig_save(struct connman_ipconfig *ipconfig, GKeyFile *keyfile, const char *identifier, const char *prefix) { const char *method; - char *key; + struct ipconfig_store is = { .file = keyfile, + .group = identifier, + .prefix = prefix }; method = __connman_ipconfig_method2string(ipconfig->method); - DBG("ipconfig %p identifier %s method %s", ipconfig, identifier, method); - if (method) { - key = g_strdup_printf("%smethod", prefix); - g_key_file_set_string(keyfile, identifier, key, method); - g_free(key); - } + store_set_str(&is, "method", method); if (ipconfig->type == CONNMAN_IPCONFIG_TYPE_IPV6) { - const char *privacy; - privacy = privacy2string(ipconfig->ipv6_privacy_config); - key = g_strdup_printf("%sprivacy", prefix); - g_key_file_set_string(keyfile, identifier, key, privacy); - g_free(key); - - key = g_strdup_printf("%sDHCP.LastAddress", prefix); - if (ipconfig->last_dhcp_address && - strlen(ipconfig->last_dhcp_address) > 0) - g_key_file_set_string(keyfile, identifier, key, - ipconfig->last_dhcp_address); - else - g_key_file_remove_key(keyfile, identifier, key, NULL); - g_free(key); - - key = g_strdup_printf("%sDHCP.LastPrefixes", prefix); - if (ipconfig->last_dhcpv6_prefixes && - ipconfig->last_dhcpv6_prefixes[0]) { - guint len = - g_strv_length(ipconfig->last_dhcpv6_prefixes); - - g_key_file_set_string_list(keyfile, identifier, key, - (const gchar **)ipconfig->last_dhcpv6_prefixes, - len); - } else - g_key_file_remove_key(keyfile, identifier, key, NULL); - g_free(key); + store_set_str(&is, "privacy", + privacy2string(ipconfig->ipv6_privacy_config)); + + store_set_str(&is, "DHCP.LastAddress", + ipconfig->last_dhcp_address); + + store_set_strs(&is, "DHCP.LastPrefixes", + ipconfig->last_dhcpv6_prefixes); } switch (ipconfig->method) { case CONNMAN_IPCONFIG_METHOD_FIXED: case CONNMAN_IPCONFIG_METHOD_MANUAL: break; + case CONNMAN_IPCONFIG_METHOD_DHCP: - key = g_strdup_printf("%sDHCP.LastAddress", prefix); - if (ipconfig->last_dhcp_address && - strlen(ipconfig->last_dhcp_address) > 0) - g_key_file_set_string(keyfile, identifier, key, - ipconfig->last_dhcp_address); - else - g_key_file_remove_key(keyfile, identifier, key, NULL); - g_free(key); + store_set_str(&is, "DHCP.LastAddress", + ipconfig->last_dhcp_address); /* fall through */ + case CONNMAN_IPCONFIG_METHOD_UNKNOWN: case CONNMAN_IPCONFIG_METHOD_OFF: case CONNMAN_IPCONFIG_METHOD_AUTO: - return 0; + return; } - key = g_strdup_printf("%snetmask_prefixlen", prefix); - if (ipconfig->address->prefixlen != 0) - g_key_file_set_integer(keyfile, identifier, - key, ipconfig->address->prefixlen); - g_free(key); - - key = g_strdup_printf("%slocal_address", prefix); - if (ipconfig->address->local) - g_key_file_set_string(keyfile, identifier, - key, ipconfig->address->local); - g_free(key); - - key = g_strdup_printf("%speer_address", prefix); - if (ipconfig->address->peer) - g_key_file_set_string(keyfile, identifier, - key, ipconfig->address->peer); - g_free(key); - - key = g_strdup_printf("%sbroadcast_address", prefix); - if (ipconfig->address->broadcast) - g_key_file_set_string(keyfile, identifier, - key, ipconfig->address->broadcast); - g_free(key); - - key = g_strdup_printf("%sgateway", prefix); - if (ipconfig->address->gateway) - g_key_file_set_string(keyfile, identifier, - key, ipconfig->address->gateway); - else - g_key_file_remove_key(keyfile, identifier, key, NULL); - g_free(key); - - return 0; + store_set_int(&is, "netmask_prefixlen", ipconfig->address->prefixlen); + store_set_str(&is, "local_address", ipconfig->address->local); + store_set_str(&is, "peer_address", ipconfig->address->peer); + store_set_str(&is, "broadcast_address", ipconfig->address->broadcast); + store_set_str(&is, "gateway", ipconfig->address->gateway); } int __connman_ipconfig_init(void) diff --git a/src/iptables.c b/src/iptables.c index 9cfd80f..47ea1c2 100644 --- a/src/iptables.c +++ b/src/iptables.c @@ -62,7 +62,7 @@ * - ipt_entry->target_offset = Size of ipt_entry + matches * - ipt_entry->next_offset = Size of ipt_entry + matches + target * - IPT_SO_SET_REPLACE is used to write a table (contains the complete - * - hook_entry and overflow mark the begining and the end of a chain, e.g + * - hook_entry and overflow mark the beginning and the end of a chain, e.g * entry hook: pre/in/fwd/out/post -1/0/352/504/-1 * underflow: pre/in/fwd/out/post -1/200/352/904/-1 * means that INPUT starts at offset 0 and ends at 200 (the start offset to @@ -868,7 +868,7 @@ static int iptables_add_entry(struct connman_iptables *table, entry_before = before->data; /* - * We've just appended/insterted a new entry. All references + * We've just appended/inserted a new entry. All references * should be bumped accordingly. */ update_targets_reference(table, entry_before, e, false); @@ -3231,7 +3231,7 @@ static int parse_rule_spec(struct connman_iptables *table, * - if '!' is found, set the invert flag to true and * removes the '!' from the optarg string and jumps * back to getopt to reparse the current optarg string. - * After reparsing the invert flag is reseted to false. + * After reparsing the invert flag is reset to false. * - If 'm' or 'j' is found then call either * prepare_matches() or prepare_target(). Those function * will modify (extend) the longopts for getopt_long. @@ -3499,7 +3499,7 @@ static int parse_rule_spec(struct connman_iptables *table, optarg[0] = '\0'; /* - * And recall getopt_long without reseting + * And recall getopt_long without resetting * invert. */ continue; diff --git a/src/network.c b/src/network.c index 56fe24f..f2ab16b 100644 --- a/src/network.c +++ b/src/network.c @@ -1253,7 +1253,7 @@ static void network_destruct(struct connman_network *network) /** * connman_network_create: - * @identifier: network identifier (for example an unqiue name) + * @identifier: network identifier (for example an unique name) * * Allocate a new network and assign the #identifier to it. * diff --git a/src/notifier.c b/src/notifier.c index 47eb72f..a39d54c 100644 --- a/src/notifier.c +++ b/src/notifier.c @@ -241,7 +241,7 @@ void __connman_notifier_service_remove(struct connman_service *service) if (g_hash_table_lookup(service_hash, service)) { /* - * This is a tempory check for consistency. It can be + * This is a temporary check for consistency. It can be * removed when there are no reports for the following * error message. */ diff --git a/src/provider.c b/src/provider.c index 9d9741e..7d663e0 100644 --- a/src/provider.c +++ b/src/provider.c @@ -176,17 +176,24 @@ int __connman_provider_connect(struct connman_provider *provider, else return -EOPNOTSUPP; - if (err < 0) { - if (err != -EINPROGRESS) - return err; + switch (err) { + case 0: + return 0; + case -EINPROGRESS: provider_indicate_state(provider, CONNMAN_SERVICE_STATE_ASSOCIATION); - + /* fall through */ + /* + * Return EINPROGRESS also for when there is an existing pending call. + * The state should not be indicated again but the real state is + * still in progress for the provider. + */ + case -EALREADY: return -EINPROGRESS; } - return 0; + return err; } int __connman_provider_remove_by_path(const char *path) @@ -485,7 +492,7 @@ void connman_provider_set_index(struct connman_provider *provider, int index) ipconfig = __connman_service_get_ip4config(service); if (!ipconfig) { - DBG("Couldnt create ipconfig"); + DBG("Couldn't create ipconfig"); goto done; } } @@ -500,7 +507,7 @@ void connman_provider_set_index(struct connman_provider *provider, int index) ipconfig = __connman_service_get_ip6config(service); if (!ipconfig) { - DBG("Couldnt create ipconfig for IPv6"); + DBG("Couldn't create ipconfig for IPv6"); goto done; } } @@ -579,6 +586,17 @@ int connman_provider_set_nameservers(struct connman_provider *provider, return 0; } +void connman_provider_set_autoconnect(struct connman_provider *provider, + bool flag) +{ + if (!provider || !provider->vpn_service) + return; + + /* Save VPN service if autoconnect value changes */ + if (connman_service_set_autoconnect(provider->vpn_service, flag)) + __connman_service_save(provider->vpn_service); +} + static void unregister_provider(gpointer data) { struct connman_provider *provider = data; diff --git a/src/resolver.c b/src/resolver.c index 10121aa..618353f 100644 --- a/src/resolver.c +++ b/src/resolver.c @@ -83,9 +83,22 @@ static void resolvfile_remove_entries(GList *entries) g_list_free(entries); } -static int resolvfile_export(void) +static bool already_exported(GList *export_list, const char *str) { GList *list; + + for (list = export_list; list; list = g_list_next(list)) { + const char *str0 = list->data; + if (g_strcmp0(str0, str) == 0) + return true; + } + + return false; +} + +static int resolvfile_export(void) +{ + GList *list, *export_list; GString *content; int fd, err; unsigned int count; @@ -99,6 +112,7 @@ static int resolvfile_export(void) * MAXDNSRCH/MAXNS entries are used. */ + export_list = NULL; for (count = 0, list = g_list_first(resolvfile_list); list && (count < MAXDNSRCH); list = g_list_next(list)) { @@ -107,16 +121,25 @@ static int resolvfile_export(void) if (!entry->domain) continue; + if (already_exported(export_list, entry->domain)) + continue; + if (count == 0) g_string_append_printf(content, "search "); g_string_append_printf(content, "%s ", entry->domain); + + export_list = g_list_append(export_list, entry->domain); + count++; } + g_list_free(export_list); + if (count) g_string_append_printf(content, "\n"); + export_list = NULL; for (count = 0, list = g_list_first(resolvfile_list); list && (count < MAXNS); list = g_list_next(list)) { @@ -125,10 +148,16 @@ static int resolvfile_export(void) if (!entry->server) continue; - g_string_append_printf(content, "nameserver %s\n", - entry->server); + if (already_exported(export_list, entry->server)) + continue; + + g_string_append_printf(content, "nameserver %s\n", entry->server); + + export_list = g_list_append(export_list, entry->server); + count++; } + g_list_free(export_list); old_umask = umask(022); @@ -172,7 +201,7 @@ int __connman_resolvfile_append(int index, const char *domain, { struct resolvfile_entry *entry; - DBG("index %d server %s", index, server); + DBG("index %d domain %s server %s", index, domain, server); if (index < 0) return -ENOENT; @@ -195,7 +224,7 @@ int __connman_resolvfile_remove(int index, const char *domain, { GList *list, *matches = NULL; - DBG("index %d server %s", index, server); + DBG("index %d domain %s server %s", index, domain, server); for (list = resolvfile_list; list; list = g_list_next(list)) { struct resolvfile_entry *entry = list->data; diff --git a/src/rtnl.c b/src/rtnl.c index cba5ef7..dfe6bb6 100644 --- a/src/rtnl.c +++ b/src/rtnl.c @@ -494,14 +494,15 @@ static void process_newlink(unsigned short type, int index, unsigned flags, __connman_technology_add_interface(interface->service_type, interface->index, interface->ident); - for (list = watch_list; list; list = list->next) { + list = watch_list; + while (list) { + GSList *next = list->next; struct watch_data *watch = list->data; - if (watch->index != index) - continue; - - if (watch->newlink) + if (watch->index == index && watch->newlink) watch->newlink(flags, change, watch->user_data); + + list = next; } } diff --git a/src/service.c b/src/service.c index 3202f26..2f497d1 100644 --- a/src/service.c +++ b/src/service.c @@ -35,10 +35,16 @@ #include #include +#include "src/shared/util.h" + #include "connman.h" #define CONNECT_TIMEOUT 120 +#define VPN_AUTOCONNECT_TIMEOUT_DEFAULT 1 +#define VPN_AUTOCONNECT_TIMEOUT_STEP 30 +#define VPN_AUTOCONNECT_TIMEOUT_ATTEMPTS_THRESHOLD 270 + static DBusConnection *connection = NULL; static GList *service_list = NULL; @@ -80,7 +86,7 @@ struct connman_service { bool hidden; bool ignore; bool autoconnect; - GTimeVal modified; + struct timeval modified; unsigned int order; char *name; char *passphrase; @@ -144,6 +150,7 @@ static struct connman_ipconfig *create_ip4config(struct connman_service *service static struct connman_ipconfig *create_ip6config(struct connman_service *service, int index); static void dns_changed(struct connman_service *service); +static void vpn_auto_connect(void); struct find_data { const char *path; @@ -414,7 +421,7 @@ int __connman_service_load_modifiable(struct connman_service *service) str = g_key_file_get_string(keyfile, service->identifier, "Modified", NULL); if (str) { - g_time_val_from_iso8601(str, &service->modified); + util_iso8601_to_timeval(str, &service->modified); g_free(str); } @@ -531,7 +538,7 @@ static int service_load(struct connman_service *service) str = g_key_file_get_string(keyfile, service->identifier, "Modified", NULL); if (str) { - g_time_val_from_iso8601(str, &service->modified); + util_iso8601_to_timeval(str, &service->modified); g_free(str); } @@ -624,7 +631,7 @@ static int service_save(struct connman_service *service) if (service->new_service) return -ESRCH; - keyfile = __connman_storage_open_service(service->identifier); + keyfile = g_key_file_new(); if (!keyfile) return -EIO; @@ -686,9 +693,6 @@ static int service_save(struct connman_service *service) g_key_file_set_boolean(keyfile, service->identifier, "Favorite", service->favorite); - g_key_file_remove_key(keyfile, service->identifier, - "Failure", NULL); - /* fall through */ case CONNMAN_SERVICE_TYPE_ETHERNET: @@ -698,57 +702,48 @@ static int service_save(struct connman_service *service) break; } - str = g_time_val_to_iso8601(&service->modified); + str = util_timeval_to_iso8601(&service->modified); if (str) { g_key_file_set_string(keyfile, service->identifier, - "Modified", str); + "Modified", str); g_free(str); } if (service->passphrase && strlen(service->passphrase) > 0) g_key_file_set_string(keyfile, service->identifier, - "Passphrase", service->passphrase); - else - g_key_file_remove_key(keyfile, service->identifier, - "Passphrase", NULL); + "Passphrase", service->passphrase); if (service->ipconfig_ipv4) __connman_ipconfig_save(service->ipconfig_ipv4, keyfile, - service->identifier, "IPv4."); + service->identifier, "IPv4."); if (service->ipconfig_ipv6) __connman_ipconfig_save(service->ipconfig_ipv6, keyfile, - service->identifier, "IPv6."); + service->identifier, "IPv6."); if (service->nameservers_config) { guint len = g_strv_length(service->nameservers_config); g_key_file_set_string_list(keyfile, service->identifier, - "Nameservers", + "Nameservers", (const gchar **) service->nameservers_config, len); - } else - g_key_file_remove_key(keyfile, service->identifier, - "Nameservers", NULL); + } if (service->timeservers_config) { guint len = g_strv_length(service->timeservers_config); g_key_file_set_string_list(keyfile, service->identifier, - "Timeservers", + "Timeservers", (const gchar **) service->timeservers_config, len); - } else - g_key_file_remove_key(keyfile, service->identifier, - "Timeservers", NULL); + } if (service->domains) { guint len = g_strv_length(service->domains); g_key_file_set_string_list(keyfile, service->identifier, - "Domains", + "Domains", (const gchar **) service->domains, len); - } else - g_key_file_remove_key(keyfile, service->identifier, - "Domains", NULL); + } cst_str = proxymethod2string(service->proxy_config); if (cst_str) @@ -761,9 +756,7 @@ static int service_save(struct connman_service *service) g_key_file_set_string_list(keyfile, service->identifier, "Proxy.Servers", (const gchar **) service->proxies, len); - } else - g_key_file_remove_key(keyfile, service->identifier, - "Proxy.Servers", NULL); + } if (service->excludes) { guint len = g_strv_length(service->excludes); @@ -771,34 +764,25 @@ static int service_save(struct connman_service *service) g_key_file_set_string_list(keyfile, service->identifier, "Proxy.Excludes", (const gchar **) service->excludes, len); - } else - g_key_file_remove_key(keyfile, service->identifier, - "Proxy.Excludes", NULL); + } if (service->pac && strlen(service->pac) > 0) g_key_file_set_string(keyfile, service->identifier, - "Proxy.URL", service->pac); - else - g_key_file_remove_key(keyfile, service->identifier, - "Proxy.URL", NULL); + "Proxy.URL", service->pac); if (service->mdns_config) g_key_file_set_boolean(keyfile, service->identifier, - "mDNS", TRUE); - else - g_key_file_remove_key(keyfile, service->identifier, - "mDNS", NULL); + "mDNS", TRUE); if (service->hidden_service) - g_key_file_set_boolean(keyfile, service->identifier, "Hidden", - TRUE); + g_key_file_set_boolean(keyfile, service->identifier, + "Hidden", TRUE); if (service->config_file && strlen(service->config_file) > 0) g_key_file_set_string(keyfile, service->identifier, "Config.file", service->config_file); - if (service->config_entry && - strlen(service->config_entry) > 0) + if (service->config_entry && strlen(service->config_entry) > 0) g_key_file_set_string(keyfile, service->identifier, "Config.ident", service->config_entry); @@ -1170,11 +1154,12 @@ int __connman_service_nameserver_append(struct connman_service *service, else nameservers = service->nameservers; - for (i = 0; nameservers && nameservers[i]; i++) - if (g_strcmp0(nameservers[i], nameserver) == 0) - return -EEXIST; - if (nameservers) { + for (i = 0; nameservers[i]; i++) { + if (g_strcmp0(nameservers[i], nameserver) == 0) + return -EEXIST; + } + len = g_strv_length(nameservers); nameservers = g_try_renew(char *, nameservers, len + 2); } else { @@ -1388,6 +1373,56 @@ void __connman_service_nameserver_del_routes(struct connman_service *service, nameserver_del_routes(index, service->nameservers, type); } +static bool check_proxy_setup(struct connman_service *service) +{ + /* + * We start WPAD if we haven't got a PAC URL from DHCP and + * if our proxy manual configuration is either empty or set + * to AUTO with an empty URL. + */ + + if (service->proxy != CONNMAN_SERVICE_PROXY_METHOD_UNKNOWN) + return true; + + if (service->proxy_config != CONNMAN_SERVICE_PROXY_METHOD_UNKNOWN && + (service->proxy_config != CONNMAN_SERVICE_PROXY_METHOD_AUTO || + service->pac)) + return true; + + if (__connman_wpad_start(service) < 0) { + service->proxy = CONNMAN_SERVICE_PROXY_METHOD_DIRECT; + __connman_notifier_proxy_changed(service); + return true; + } + + return false; +} + +static void cancel_online_check(struct connman_service *service) +{ + if (service->online_timeout == 0) + return; + + g_source_remove(service->online_timeout); + service->online_timeout = 0; + connman_service_unref(service); +} + +static void start_online_check(struct connman_service *service, + enum connman_ipconfig_type type) +{ + if (!connman_setting_get_bool("EnableOnlineCheck")) { + connman_info("Online check disabled. " + "Default service remains in READY state."); + return; + } + + if (type != CONNMAN_IPCONFIG_TYPE_IPV4 || check_proxy_setup(service)) { + cancel_online_check(service); + __connman_service_wispr_start(service, type); + } +} + static void address_updated(struct connman_service *service, enum connman_ipconfig_type type) { @@ -1395,6 +1430,7 @@ static void address_updated(struct connman_service *service, service == connman_service_get_default()) { nameserver_remove_all(service, type); nameserver_add_all(service, type); + start_online_check(service, type); __connman_timeserver_sync(service); } @@ -1538,6 +1574,16 @@ static void default_changed(void) if (service->domainname && connman_setting_get_bool("AllowDomainnameUpdates")) __connman_utsname_set_domainname(service->domainname); + + /* + * Connect VPN automatically when new default service + * is set and connected, unless new default is VPN + */ + if (is_connected(service->state) && + service->type != CONNMAN_SERVICE_TYPE_VPN) { + DBG("running vpn_auto_connect"); + vpn_auto_connect(); + } } __connman_notifier_default_changed(service); @@ -1638,6 +1684,18 @@ static void autoconnect_changed(struct connman_service *service) DBUS_TYPE_BOOLEAN, &autoconnect); } +bool connman_service_set_autoconnect(struct connman_service *service, + bool autoconnect) +{ + if (service->autoconnect == autoconnect) + return false; + + service->autoconnect = autoconnect; + autoconnect_changed(service); + + return true; +} + static void append_security(DBusMessageIter *iter, void *user_data) { struct connman_service *service = user_data; @@ -3358,6 +3416,31 @@ error: return -EINVAL; } +static void do_auto_connect(struct connman_service *service, + enum connman_service_connect_reason reason) +{ + /* + * CONNMAN_SERVICE_CONNECT_REASON_NONE must be ignored for VPNs. VPNs + * always have reason CONNMAN_SERVICE_CONNECT_REASON_USER/AUTO. + */ + if (!service || (service->type == CONNMAN_SERVICE_TYPE_VPN && + reason == CONNMAN_SERVICE_CONNECT_REASON_NONE)) + return; + + /* + * Run service auto connect for other than VPN services. Afterwards + * start also VPN auto connect process. + */ + if (service->type != CONNMAN_SERVICE_TYPE_VPN) + __connman_service_auto_connect(reason); + /* Only user interaction should get VPN connected in failure state. */ + else if (service->state == CONNMAN_SERVICE_STATE_FAILURE && + reason != CONNMAN_SERVICE_CONNECT_REASON_USER) + return; + + vpn_auto_connect(); +} + int __connman_service_reset_ipconfig(struct connman_service *service, enum connman_ipconfig_type type, DBusMessageIter *array, enum connman_service_state *new_state) @@ -3422,7 +3505,7 @@ int __connman_service_reset_ipconfig(struct connman_service *service, settings_changed(service, new_ipconfig); address_updated(service, type); - __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_AUTO); + do_auto_connect(service, CONNMAN_SERVICE_CONNECT_REASON_AUTO); } DBG("err %d ipconfig %p type %d method %d state %s", err, @@ -3455,6 +3538,9 @@ void __connman_service_wispr_start(struct connman_service *service, __connman_wispr_start(service, type); } +static void set_error(struct connman_service *service, + enum connman_service_error error); + static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, void *user_data) { @@ -3492,17 +3578,26 @@ static DBusMessage *set_property(DBusConnection *conn, dbus_message_iter_get_basic(&value, &autoconnect); - if (service->autoconnect == autoconnect) - return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); - - service->autoconnect = autoconnect; - - autoconnect_changed(service); + if (autoconnect && service->type == CONNMAN_SERVICE_TYPE_VPN) { + /* + * Changing the autoconnect flag on VPN to "on" should + * have the same effect as user connecting the VPN = + * clear previous error and change state to idle. + */ + set_error(service, CONNMAN_SERVICE_ERROR_UNKNOWN); - if (autoconnect) - __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_AUTO); + if (service->state == CONNMAN_SERVICE_STATE_FAILURE) { + service->state = CONNMAN_SERVICE_STATE_IDLE; + state_changed(service); + } + } - service_save(service); + if (connman_service_set_autoconnect(service, autoconnect)) { + service_save(service); + if (autoconnect) + do_auto_connect(service, + CONNMAN_SERVICE_CONNECT_REASON_AUTO); + } } else if (g_str_equal(name, "Nameservers.Configuration")) { DBusMessageIter entry; GString *str; @@ -3826,9 +3921,9 @@ static void service_complete(struct connman_service *service) reply_pending(service, EIO); if (service->connect_reason != CONNMAN_SERVICE_CONNECT_REASON_USER) - __connman_service_auto_connect(service->connect_reason); + do_auto_connect(service, service->connect_reason); - g_get_current_time(&service->modified); + gettimeofday(&service->modified, NULL); service_save(service); } @@ -4046,6 +4141,8 @@ static bool autoconnect_already_connecting(struct connman_service *service, return false; } +static int service_indicate_state(struct connman_service *service); + static bool auto_connect_service(GList *services, enum connman_service_connect_reason reason, bool preferred) @@ -4104,7 +4201,8 @@ static bool auto_connect_service(GList *services, DBG("service %p %s %s", service, service->name, (preferred) ? "preferred" : reason2string(reason)); - __connman_service_connect(service, reason); + if (__connman_service_connect(service, reason) == 0) + service_indicate_state(service); if (autoconnect_no_session_active(service)) return true; @@ -4155,8 +4253,28 @@ void __connman_service_auto_connect(enum connman_service_connect_reason reason) static gboolean run_vpn_auto_connect(gpointer data) { GList *list; bool need_split = false; + bool autoconnectable_vpns = false; + int attempts = 0; + int timeout = VPN_AUTOCONNECT_TIMEOUT_DEFAULT; + struct connman_service *def_service; - vpn_autoconnect_id = 0; + attempts = GPOINTER_TO_INT(data); + def_service = connman_service_get_default(); + + /* + * Stop auto connecting VPN if there is no transport service or the + * transport service is not connected or if the current default service + * is a connected VPN (in ready state). + */ + if (!def_service || !is_connected(def_service->state) || + (def_service->type == CONNMAN_SERVICE_TYPE_VPN && + is_connected(def_service->state))) { + + DBG("stopped, default service %s connected %d", + def_service ? def_service->identifier : "NULL", + def_service ? is_connected(def_service->state) : -1); + goto out; + } for (list = service_list; list; list = list->next) { struct connman_service *service = list->data; @@ -4166,9 +4284,17 @@ static gboolean run_vpn_auto_connect(gpointer data) { continue; if (is_connected(service->state) || - is_connecting(service->state)) { + is_connecting(service->state)) { if (!service->do_split_routing) need_split = true; + + /* + * If the service is connecting it must be accounted + * for to keep the autoconnection in main loop. + */ + if (is_connecting(service->state)) + autoconnectable_vpns = true; + continue; } @@ -4186,20 +4312,64 @@ static gboolean run_vpn_auto_connect(gpointer data) { res = __connman_service_connect(service, CONNMAN_SERVICE_CONNECT_REASON_AUTO); - if (res < 0 && res != -EINPROGRESS) + + switch (res) { + case 0: + service_indicate_state(service); + /* fall through */ + case -EINPROGRESS: + autoconnectable_vpns = true; + break; + default: continue; + } if (!service->do_split_routing) need_split = true; } - return FALSE; + /* Stop if there is no VPN to automatically connect.*/ + if (!autoconnectable_vpns) { + DBG("stopping, no autoconnectable VPNs found"); + goto out; + } + + /* Increase the attempt count up to the threshold.*/ + if (attempts < VPN_AUTOCONNECT_TIMEOUT_ATTEMPTS_THRESHOLD) + attempts++; + + /* + * Timeout increases with 1s after VPN_AUTOCONNECT_TIMEOUT_STEP amount + * of attempts made. After VPN_AUTOCONNECT_TIMEOUT_ATTEMPTS_THRESHOLD is + * reached the delay does not increase. + */ + timeout = timeout + (int)(attempts / VPN_AUTOCONNECT_TIMEOUT_STEP); + + /* Re add this to main loop */ + vpn_autoconnect_id = + g_timeout_add_seconds(timeout, run_vpn_auto_connect, + GINT_TO_POINTER(attempts)); + + DBG("re-added to main loop, next VPN autoconnect in %d seconds (#%d)", + timeout, attempts); + + return G_SOURCE_REMOVE; + +out: + vpn_autoconnect_id = 0; + return G_SOURCE_REMOVE; } static void vpn_auto_connect(void) { - if (vpn_autoconnect_id) - return; + /* + * Remove existing autoconnect from main loop to reset the attempt + * counter in order to get VPN connected when there is a network change. + */ + if (vpn_autoconnect_id) { + if (!g_source_remove(vpn_autoconnect_id)) + return; + } vpn_autoconnect_id = g_idle_add(run_vpn_auto_connect, NULL); @@ -4302,7 +4472,7 @@ static gboolean connect_timeout(gpointer user_data) if (autoconnect && service->connect_reason != CONNMAN_SERVICE_CONNECT_REASON_USER) - __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_AUTO); + do_auto_connect(service, CONNMAN_SERVICE_CONNECT_REASON_AUTO); return FALSE; } @@ -4681,7 +4851,7 @@ static DBusMessage *move_service(DBusConnection *conn, } } - g_get_current_time(&service->modified); + gettimeofday(&service->modified, NULL); service_save(service); service_save(target); @@ -5486,12 +5656,15 @@ static void request_input_cb(struct connman_service *service, __connman_service_return_error(service, ECONNABORTED, user_data); - goto done; } else { + err = -ETIMEDOUT; + if (service->hidden) __connman_service_return_error(service, ETIMEDOUT, user_data); } + + goto done; } if (service->hidden && name_len > 0 && name_len <= 32) { @@ -5760,7 +5933,7 @@ static int service_indicate_state(struct connman_service *service) "WiFi.UseWPS", false); } - g_get_current_time(&service->modified); + gettimeofday(&service->modified, NULL); service_save(service); domain_changed(service); @@ -5806,7 +5979,7 @@ static int service_indicate_state(struct connman_service *service) */ downgrade_connected_services(); - __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_AUTO); + do_auto_connect(service, CONNMAN_SERVICE_CONNECT_REASON_AUTO); break; case CONNMAN_SERVICE_STATE_FAILURE: @@ -5927,34 +6100,6 @@ enum connman_service_state __connman_service_ipconfig_get_state( return CONNMAN_SERVICE_STATE_UNKNOWN; } -static void check_proxy_setup(struct connman_service *service) -{ - /* - * We start WPAD if we haven't got a PAC URL from DHCP and - * if our proxy manual configuration is either empty or set - * to AUTO with an empty URL. - */ - - if (service->proxy != CONNMAN_SERVICE_PROXY_METHOD_UNKNOWN) - goto done; - - if (service->proxy_config != CONNMAN_SERVICE_PROXY_METHOD_UNKNOWN && - (service->proxy_config != CONNMAN_SERVICE_PROXY_METHOD_AUTO || - service->pac)) - goto done; - - if (__connman_wpad_start(service) < 0) { - service->proxy = CONNMAN_SERVICE_PROXY_METHOD_DIRECT; - __connman_notifier_proxy_changed(service); - goto done; - } - - return; - -done: - __connman_service_wispr_start(service, CONNMAN_IPCONFIG_TYPE_IPV4); -} - /* * How many networks are connected at the same time. If more than 1, * then set the rp_filter setting properly (loose mode routing) so that network @@ -6068,16 +6213,6 @@ int __connman_service_online_check_failed(struct connman_service *service, return EAGAIN; } -static void cancel_online_check(struct connman_service *service) -{ - if (service->online_timeout == 0) - return; - - g_source_remove(service->online_timeout); - service->online_timeout = 0; - connman_service_unref(service); -} - int __connman_service_ipconfig_indicate_state(struct connman_service *service, enum connman_service_state new_state, enum connman_ipconfig_type type) @@ -6147,15 +6282,6 @@ int __connman_service_ipconfig_indicate_state(struct connman_service *service, case CONNMAN_SERVICE_STATE_CONFIGURATION: break; case CONNMAN_SERVICE_STATE_READY: - if (connman_setting_get_bool("EnableOnlineCheck")) - if (type == CONNMAN_IPCONFIG_TYPE_IPV4) { - check_proxy_setup(service); - } else { - __connman_service_wispr_start(service, type); - } - else - connman_info("Online check disabled. " - "Default service remains in READY state."); if (type == CONNMAN_IPCONFIG_TYPE_IPV4) service_rp_filter(service, true); set_mdns(service, service->mdns_config); @@ -7204,7 +7330,8 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne case CONNMAN_SERVICE_TYPE_VPN: case CONNMAN_SERVICE_TYPE_WIFI: case CONNMAN_SERVICE_TYPE_CELLULAR: - __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_AUTO); + do_auto_connect(service, + CONNMAN_SERVICE_CONNECT_REASON_AUTO); break; } } diff --git a/src/shared/mnlg.c b/src/shared/mnlg.c new file mode 100644 index 0000000..1399ce4 --- /dev/null +++ b/src/shared/mnlg.c @@ -0,0 +1,331 @@ +/* + * mnlg.c Generic Netlink helpers for libmnl + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Jiri Pirko + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mnlg.h" + +#ifndef MNL_ARRAY_SIZE +#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif + +#ifndef NETLINK_CAP_ACK +#define NETLINK_CAP_ACK 10 +#endif +#ifndef NETLINK_EXT_ACK +#define NETLINK_EXT_ACK 11 +#endif + +struct mnlg_socket { + struct mnl_socket *nl; + char *buf; + uint32_t id; + uint8_t version; + unsigned int seq; + unsigned int portid; +}; + +static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, + uint16_t flags, uint32_t id, + uint8_t version) +{ + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + + nlh = mnl_nlmsg_put_header(nlg->buf); + nlh->nlmsg_type = id; + nlh->nlmsg_flags = flags; + nlg->seq = time(NULL); + nlh->nlmsg_seq = nlg->seq; + + genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd = cmd; + genl->version = version; + + return nlh; +} + +struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, + uint16_t flags) +{ + return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version); +} + +int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh) +{ + return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len); +} + +static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data) +{ + return MNL_CB_OK; +} + +static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + + /* Netlink subsystems returns the errno value with different signess */ + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data) +{ + int len = *(int *)NLMSG_DATA(nlh); + + if (len < 0) { + errno = -len; + return MNL_CB_ERROR; + } + return MNL_CB_STOP; +} + +static mnl_cb_t mnlg_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = mnlg_cb_noop, + [NLMSG_ERROR] = mnlg_cb_error, + [NLMSG_DONE] = mnlg_cb_stop, + [NLMSG_OVERRUN] = mnlg_cb_noop, +}; + +int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data) +{ + int err; + + do { + err = mnl_socket_recvfrom(nlg->nl, nlg->buf, + MNL_SOCKET_BUFFER_SIZE); + if (err <= 0) + break; + err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid, + data_cb, data, mnlg_cb_array, + ARRAY_SIZE(mnlg_cb_array)); + } while (err > 0); + + return err; +} + +struct group_info { + bool found; + uint32_t id; + const char *name; +}; + +static int parse_mc_grps_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MCAST_GRP_MAX) < 0) + return MNL_CB_OK; + + switch (type) { + case CTRL_ATTR_MCAST_GRP_ID: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + case CTRL_ATTR_MCAST_GRP_NAME: + if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) + return MNL_CB_ERROR; + break; + } + tb[type] = attr; + return MNL_CB_OK; +} + +static void parse_genl_mc_grps(struct nlattr *nested, + struct group_info *group_info) +{ + struct nlattr *pos; + const char *name; + + mnl_attr_for_each_nested(pos, nested) { + struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {}; + + mnl_attr_parse_nested(pos, parse_mc_grps_cb, tb); + if (!tb[CTRL_ATTR_MCAST_GRP_NAME] || + !tb[CTRL_ATTR_MCAST_GRP_ID]) + continue; + + name = mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]); + if (strcmp(name, group_info->name) != 0) + continue; + + group_info->id = mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]); + group_info->found = true; + } +} + +static int get_group_id_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) + return MNL_CB_ERROR; + + if (type == CTRL_ATTR_MCAST_GROUPS && + mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0) + return MNL_CB_ERROR; + tb[type] = attr; + return MNL_CB_OK; +} + +static int get_group_id_cb(const struct nlmsghdr *nlh, void *data) +{ + struct group_info *group_info = data; + struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; + struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); + + mnl_attr_parse(nlh, sizeof(*genl), get_group_id_attr_cb, tb); + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return MNL_CB_ERROR; + parse_genl_mc_grps(tb[CTRL_ATTR_MCAST_GROUPS], group_info); + return MNL_CB_OK; +} + +int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name) +{ + struct nlmsghdr *nlh; + struct group_info group_info; + int err; + + nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY, + NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1); + mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, nlg->id); + + err = mnlg_socket_send(nlg, nlh); + if (err < 0) + return err; + + group_info.found = false; + group_info.name = group_name; + err = mnlg_socket_recv_run(nlg, get_group_id_cb, &group_info); + if (err < 0) + return err; + + if (!group_info.found) { + errno = ENOENT; + return -1; + } + + err = mnl_socket_setsockopt(nlg->nl, NETLINK_ADD_MEMBERSHIP, + &group_info.id, sizeof(group_info.id)); + if (err < 0) + return err; + + return 0; +} + +static int get_family_id_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) + return MNL_CB_ERROR; + + if (type == CTRL_ATTR_FAMILY_ID && + mnl_attr_validate(attr, MNL_TYPE_U16) < 0) + return MNL_CB_ERROR; + tb[type] = attr; + return MNL_CB_OK; +} + +static int get_family_id_cb(const struct nlmsghdr *nlh, void *data) +{ + uint32_t *p_id = data; + struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; + struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); + + mnl_attr_parse(nlh, sizeof(*genl), get_family_id_attr_cb, tb); + if (!tb[CTRL_ATTR_FAMILY_ID]) + return MNL_CB_ERROR; + *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + return MNL_CB_OK; +} + +struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version) +{ + struct mnlg_socket *nlg; + struct nlmsghdr *nlh; + int one = 1; + int err; + + nlg = malloc(sizeof(*nlg)); + if (!nlg) + return NULL; + + nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE); + if (!nlg->buf) + goto err_buf_alloc; + + nlg->nl = mnl_socket_open(NETLINK_GENERIC); + if (!nlg->nl) + goto err_mnl_socket_open; + + /* Older kernels may no support capped/extended ACK reporting */ + mnl_socket_setsockopt(nlg->nl, NETLINK_CAP_ACK, &one, sizeof(one)); + mnl_socket_setsockopt(nlg->nl, NETLINK_EXT_ACK, &one, sizeof(one)); + + err = mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID); + if (err < 0) + goto err_mnl_socket_bind; + + nlg->portid = mnl_socket_get_portid(nlg->nl); + + nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY, + NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1); + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name); + + err = mnlg_socket_send(nlg, nlh); + if (err < 0) + goto err_mnlg_socket_send; + + err = mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id); + if (err < 0) + goto err_mnlg_socket_recv_run; + + nlg->version = version; + return nlg; + +err_mnlg_socket_recv_run: +err_mnlg_socket_send: +err_mnl_socket_bind: + mnl_socket_close(nlg->nl); +err_mnl_socket_open: + free(nlg->buf); +err_buf_alloc: + free(nlg); + return NULL; +} + +void mnlg_socket_close(struct mnlg_socket *nlg) +{ + mnl_socket_close(nlg->nl); + free(nlg->buf); + free(nlg); +} diff --git a/src/shared/mnlg.h b/src/shared/mnlg.h new file mode 100644 index 0000000..4d1babf --- /dev/null +++ b/src/shared/mnlg.h @@ -0,0 +1,27 @@ +/* + * mnlg.h Generic Netlink helpers for libmnl + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Jiri Pirko + */ + +#ifndef _MNLG_H_ +#define _MNLG_H_ + +#include + +struct mnlg_socket; + +struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, + uint16_t flags); +int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh); +int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data); +int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name); +struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version); +void mnlg_socket_close(struct mnlg_socket *nlg); + +#endif /* _MNLG_H_ */ diff --git a/src/shared/netlink.c b/src/shared/netlink.c deleted file mode 100644 index b32ab85..0000000 --- a/src/shared/netlink.c +++ /dev/null @@ -1,666 +0,0 @@ -/* - * - * Connection Manager - * - * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. - * Copyright (C) 2013-2014 BMW Car IT GmbH. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -/* - * This file is a copy from ELL which has been ported to use GLib's - * data structures. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#include - -#include "src/shared/util.h" -#include "src/shared/netlink.h" - -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -struct command { - unsigned int id; - uint32_t seq; - uint32_t len; - netlink_command_func_t handler; - netlink_destroy_func_t destroy; - void *user_data; -}; - -struct notify { - uint32_t group; - netlink_notify_func_t handler; - netlink_destroy_func_t destroy; - void *user_data; -}; - -struct netlink_info { - uint32_t pid; - GIOChannel *channel; - uint32_t next_seq; - GQueue *command_queue; - GHashTable *command_pending; - GHashTable *command_lookup; - unsigned int next_command_id; - GHashTable *notify_groups; - GHashTable *notify_lookup; - unsigned int next_notify_id; - netlink_debug_func_t debug_handler; - netlink_destroy_func_t debug_destroy; - void *debug_data; -}; - - -static void destroy_command(struct command *command) -{ - if (command->destroy) - command->destroy(command->user_data); - - g_free(command); -} - -static void destroy_notify(struct notify *notify) -{ - if (notify->destroy) - notify->destroy(notify->user_data); - - g_free(notify); -} - -static gboolean can_write_data(GIOChannel *chan, - GIOCondition cond, gpointer user_data) -{ - struct netlink_info *netlink = user_data; - struct command *command; - struct sockaddr_nl addr; - const void *data; - ssize_t written; - int sk; - - command = g_queue_pop_head(netlink->command_queue); - if (!command) - return FALSE; - - sk = g_io_channel_unix_get_fd(chan); - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - addr.nl_pid = 0; - - data = ((void *) command) + NLMSG_ALIGN(sizeof(struct command)); - - written = sendto(sk, data, command->len, 0, - (struct sockaddr *) &addr, sizeof(addr)); - if (written < 0 || (uint32_t) written != command->len) { - g_hash_table_remove(netlink->command_lookup, - GUINT_TO_POINTER(command->id)); - destroy_command(command); - return FALSE; - } - - util_hexdump('<', data, command->len, - netlink->debug_handler, netlink->debug_data); - - g_hash_table_replace(netlink->command_pending, - GUINT_TO_POINTER(command->seq), command); - - return g_queue_get_length(netlink->command_queue) > 0; -} - -static void do_notify(gpointer key, gpointer value, gpointer user_data) -{ - struct nlmsghdr *nlmsg = user_data; - struct notify *notify = value; - - if (notify->handler) { - notify->handler(nlmsg->nlmsg_type, NLMSG_DATA(nlmsg), - nlmsg->nlmsg_len - NLMSG_HDRLEN, notify->user_data); - } -} - -static void process_broadcast(struct netlink_info *netlink, uint32_t group, - struct nlmsghdr *nlmsg) -{ - GHashTable *notify_list; - - notify_list = g_hash_table_lookup(netlink->notify_groups, - GUINT_TO_POINTER(group)); - if (!notify_list) - return; - - g_hash_table_foreach(notify_list, do_notify, nlmsg); -} - -static void process_message(struct netlink_info *netlink, - struct nlmsghdr *nlmsg) -{ - const void *data = nlmsg; - struct command *command; - - command = g_hash_table_lookup(netlink->command_pending, - GUINT_TO_POINTER(nlmsg->nlmsg_seq)); - if (!command) - return; - - g_hash_table_remove(netlink->command_pending, - GUINT_TO_POINTER(nlmsg->nlmsg_seq)); - - if (!command->handler) - goto done; - - if (nlmsg->nlmsg_type < NLMSG_MIN_TYPE) { - const struct nlmsgerr *err; - - switch (nlmsg->nlmsg_type) { - case NLMSG_ERROR: - err = data + NLMSG_HDRLEN; - - command->handler(-err->error, 0, NULL, 0, - command->user_data); - break; - } - } else { - command->handler(0, nlmsg->nlmsg_type, data + NLMSG_HDRLEN, - nlmsg->nlmsg_len - NLMSG_HDRLEN, - command->user_data); - } - -done: - g_hash_table_remove(netlink->command_lookup, - GUINT_TO_POINTER(command->id)); - - destroy_command(command); -} - -static void process_multi(struct netlink_info *netlink, struct nlmsghdr *nlmsg) -{ - const void *data = nlmsg; - struct command *command; - - command = g_hash_table_lookup(netlink->command_pending, - GUINT_TO_POINTER(nlmsg->nlmsg_seq)); - if (!command) - return; - - if (!command->handler) - goto done; - - if (nlmsg->nlmsg_type < NLMSG_MIN_TYPE) { - const struct nlmsgerr *err; - - switch (nlmsg->nlmsg_type) { - case NLMSG_DONE: - case NLMSG_ERROR: - err = data + NLMSG_HDRLEN; - - command->handler(-err->error, 0, NULL, 0, - command->user_data); - break; - } - } else { - command->handler(0, nlmsg->nlmsg_type, data + NLMSG_HDRLEN, - nlmsg->nlmsg_len - NLMSG_HDRLEN, - command->user_data); - return; - } - -done: - g_hash_table_remove(netlink->command_pending, - GUINT_TO_POINTER(nlmsg->nlmsg_seq)); - - g_hash_table_remove(netlink->command_lookup, - GUINT_TO_POINTER(command->id)); - - destroy_command(command); -} - -static gboolean can_read_data(GIOChannel *chan, - GIOCondition cond, gpointer data) -{ - struct netlink_info *netlink = data; - struct cmsghdr *cmsg; - struct msghdr msg; - struct iovec iov; - struct nlmsghdr *nlmsg; - unsigned char buffer[4096]; - unsigned char control[32]; - uint32_t group = 0; - ssize_t len; - int sk; - - sk = g_io_channel_unix_get_fd(chan); - - iov.iov_base = buffer; - iov.iov_len = sizeof(buffer); - - memset(&msg, 0, sizeof(msg)); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - len = recvmsg(sk, &msg, 0); - if (len < 0) - return FALSE; - - util_hexdump('>', buffer, len, netlink->debug_handler, - netlink->debug_data); - - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - struct nl_pktinfo *pktinfo; - - if (cmsg->cmsg_level != SOL_NETLINK) - continue; - - if (cmsg->cmsg_type != NETLINK_PKTINFO) - continue; - - pktinfo = (void *) CMSG_DATA(cmsg); - - group = pktinfo->group; - } - - for (nlmsg = iov.iov_base; NLMSG_OK(nlmsg, (uint32_t) len); - nlmsg = NLMSG_NEXT(nlmsg, len)) { - if (group > 0 && nlmsg->nlmsg_seq == 0) { - process_broadcast(netlink, group, nlmsg); - continue; - } - - if (nlmsg->nlmsg_pid != netlink->pid) - continue; - - if (nlmsg->nlmsg_flags & NLM_F_MULTI) - process_multi(netlink, nlmsg); - else - process_message(netlink, nlmsg); - } - - return TRUE; -} - -static gboolean netlink_event(GIOChannel *chan, - GIOCondition cond, gpointer data) -{ - if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) - return FALSE; - - return TRUE; -} - -static int create_netlink_socket(int protocol, uint32_t *pid) -{ - struct sockaddr_nl addr; - socklen_t addrlen = sizeof(addr); - int sk, pktinfo = 1; - - sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - protocol); - if (sk < 0) - return -1; - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { - close(sk); - return -1; - } - - if (getsockname(sk, (struct sockaddr *) &addr, &addrlen) < 0) { - close(sk); - return -1; - } - - if (setsockopt(sk, SOL_NETLINK, NETLINK_PKTINFO, - &pktinfo, sizeof(pktinfo)) < 0) { - close(sk); - return -1; - } - - if (pid) - *pid = addr.nl_pid; - - return sk; -} - -struct netlink_info *netlink_new(int protocol) -{ - struct netlink_info *netlink; - int sk; - - netlink = g_try_new0(struct netlink_info, 1); - if (!netlink) - return NULL; - - netlink->next_seq = 1; - netlink->next_command_id = 1; - netlink->next_notify_id = 1; - - sk = create_netlink_socket(protocol, &netlink->pid); - if (sk < 0) { - g_free(netlink); - return NULL; - } - - netlink->channel = g_io_channel_unix_new(sk); - g_io_channel_set_close_on_unref(netlink->channel, TRUE); - - g_io_channel_set_encoding(netlink->channel, NULL, NULL); - g_io_channel_set_buffered(netlink->channel, FALSE); - - g_io_add_watch(netlink->channel, G_IO_IN, can_read_data, netlink); - g_io_add_watch(netlink->channel, G_IO_NVAL | G_IO_HUP | G_IO_ERR, - netlink_event, netlink); - - netlink->command_queue = g_queue_new(); - netlink->command_pending = g_hash_table_new(g_direct_hash, - g_direct_equal); - netlink->command_lookup = g_hash_table_new(g_direct_hash, - g_direct_equal); - - netlink->notify_groups = g_hash_table_new(g_direct_hash, - g_direct_equal); - netlink->notify_lookup = g_hash_table_new(g_direct_hash, - g_direct_equal); - - return netlink; -} - -static gboolean cleanup_notify(gpointer key, gpointer value, gpointer user_data) -{ - struct notify *notify = value; - - destroy_notify(notify); - - return TRUE; - -} - -static gboolean cleanup_notify_group(gpointer key, gpointer value, - gpointer user_data) -{ - GHashTable *notify_list = value; - - g_hash_table_foreach_remove(notify_list, cleanup_notify, user_data); - g_hash_table_destroy(notify_list); - - return TRUE; -} - -static gboolean cleanup_command(gpointer key, gpointer value, - gpointer user_data) -{ - struct command *command = value; - - destroy_command(command); - - return TRUE; -} - -void netlink_destroy(struct netlink_info *netlink) -{ - g_hash_table_destroy(netlink->notify_lookup); - - g_hash_table_foreach_remove(netlink->notify_groups, - cleanup_notify_group, NULL); - g_hash_table_destroy(netlink->notify_groups); - - g_queue_free(netlink->command_queue); - - g_hash_table_destroy(netlink->command_pending); - - g_hash_table_foreach_remove(netlink->command_lookup, - cleanup_command, NULL); - g_hash_table_destroy(netlink->command_lookup); - - g_io_channel_shutdown(netlink->channel, TRUE, NULL); - g_io_channel_unref(netlink->channel); - - g_free(netlink); -} - -unsigned int netlink_send(struct netlink_info *netlink, - uint16_t type, uint16_t flags, const void *data, - uint32_t len, netlink_command_func_t function, - void *user_data, netlink_destroy_func_t destroy) -{ - struct command *command; - struct nlmsghdr *nlmsg; - size_t size; - - if (!netlink) - return 0; - - if (!netlink->command_queue || - !netlink->command_pending || - !netlink->command_lookup) - return 0; - - size = NLMSG_ALIGN(sizeof(struct command)) + - NLMSG_HDRLEN + NLMSG_ALIGN(len); - - command = g_try_malloc0(size); - if (!command) - return 0; - - command->handler = function; - command->destroy = destroy; - command->user_data = user_data; - - command->id = netlink->next_command_id; - - g_hash_table_replace(netlink->command_lookup, - GUINT_TO_POINTER(command->id), command); - - command->seq = netlink->next_seq++; - command->len = NLMSG_HDRLEN + NLMSG_ALIGN(len); - - nlmsg = ((void *) command) + NLMSG_ALIGN(sizeof(struct command)); - - nlmsg->nlmsg_len = command->len; - nlmsg->nlmsg_type = type; - nlmsg->nlmsg_flags = NLM_F_REQUEST | flags; - nlmsg->nlmsg_seq = command->seq; - nlmsg->nlmsg_pid = netlink->pid; - - if (data && len > 0) - memcpy(((void *) nlmsg) + NLMSG_HDRLEN, data, len); - - g_queue_push_tail(netlink->command_queue, command); - - netlink->next_command_id++; - - /* Arm IOChannel to call can_write_data in case it is not armed yet. */ - if (g_queue_get_length(netlink->command_queue) == 1) - g_io_add_watch(netlink->channel, G_IO_OUT, can_write_data, - netlink); - - return command->id; -} - -bool netlink_cancel(struct netlink_info *netlink, unsigned int id) -{ - struct command *command; - - if (!netlink || id == 0) - return false; - - if (!netlink->command_queue || - !netlink->command_pending || - !netlink->command_lookup) - return false; - - command = g_hash_table_lookup(netlink->command_lookup, - GUINT_TO_POINTER(id)); - if (!command) - return false; - - g_hash_table_remove(netlink->command_lookup, GUINT_TO_POINTER(id)); - - if (!g_queue_remove(netlink->command_queue, command)) { - g_hash_table_remove(netlink->command_pending, - GUINT_TO_POINTER(command->seq)); - } - - destroy_command(command); - - return true; -} - -static bool add_membership(struct netlink_info *netlink, uint32_t group) -{ - int sk, value = group; - - sk = g_io_channel_unix_get_fd(netlink->channel); - - if (setsockopt(sk, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, - &value, sizeof(value)) < 0) - return false; - - return true; -} - -static bool drop_membership(struct netlink_info *netlink, uint32_t group) -{ - int sk, value = group; - - sk = g_io_channel_unix_get_fd(netlink->channel); - - if (setsockopt(sk, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, - &value, sizeof(value)) < 0) - return false; - - return true; -} - -unsigned int netlink_register(struct netlink_info *netlink, - uint32_t group, netlink_notify_func_t function, - void *user_data, netlink_destroy_func_t destroy) -{ - GHashTable *notify_list; - struct notify *notify; - unsigned int id; - - if (!netlink) - return 0; - - if (!netlink->notify_groups || !netlink->notify_lookup) - return 0; - - notify_list = g_hash_table_lookup(netlink->notify_groups, - GUINT_TO_POINTER(group)); - if (!notify_list) { - notify_list = g_hash_table_new(g_direct_hash, g_direct_equal); - if (!notify_list) - return 0; - - g_hash_table_replace(netlink->notify_groups, - GUINT_TO_POINTER(group), notify_list); - } - - notify = g_new(struct notify, 1); - - notify->group = group; - notify->handler = function; - notify->destroy = destroy; - notify->user_data = user_data; - - id = netlink->next_notify_id; - - g_hash_table_replace(netlink->notify_lookup, - GUINT_TO_POINTER(id), notify_list); - g_hash_table_replace(notify_list, GUINT_TO_POINTER(id), notify); - - if (g_hash_table_size(notify_list) == 1) { - if (add_membership(netlink, notify->group) == false) - goto remove_notify; - } - - netlink->next_notify_id++; - - return id; - -remove_notify: - g_hash_table_remove(notify_list, GUINT_TO_POINTER(id)); - g_hash_table_remove(netlink->notify_lookup, GUINT_TO_POINTER(id)); - g_free(notify); - - return 0; -} - -bool netlink_unregister(struct netlink_info *netlink, unsigned int id) -{ - GHashTable *notify_list; - struct notify *notify; - - if (!netlink || id == 0) - return false; - - if (!netlink->notify_groups || !netlink->notify_lookup) - return false; - - notify_list = g_hash_table_lookup(netlink->notify_lookup, - GUINT_TO_POINTER(id)); - - if (!notify_list) - return false; - - g_hash_table_remove(netlink->notify_lookup, GUINT_TO_POINTER(id)); - - notify = g_hash_table_lookup(notify_list, GUINT_TO_POINTER(id)); - if (!notify) - return false; - - g_hash_table_remove(notify_list, GUINT_TO_POINTER(id)); - - if (g_hash_table_size(notify_list) == 0) - drop_membership(netlink, notify->group); - - destroy_notify(notify); - - return true; -} - -bool netlink_set_debug(struct netlink_info *netlink, - netlink_debug_func_t function, - void *user_data, netlink_destroy_func_t destroy) -{ - if (!netlink) - return false; - - if (netlink->debug_destroy) - netlink->debug_destroy(netlink->debug_data); - - netlink->debug_handler = function; - netlink->debug_destroy = destroy; - netlink->debug_data = user_data; - - return true; -} diff --git a/src/shared/netlink.h b/src/shared/netlink.h deleted file mode 100644 index 62bb3e0..0000000 --- a/src/shared/netlink.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * Connection Manager - * - * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. - * Copyright (C) 2013 BMW Car IT GbmH. - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include -#include - -typedef void (*netlink_debug_func_t) (const char *str, void *user_data); -typedef void (*netlink_command_func_t) (unsigned int error, - uint16_t type, const void *data, - uint32_t len, void *user_data); -typedef void (*netlink_notify_func_t) (uint16_t type, const void *data, - uint32_t len, void *user_data); -typedef void (*netlink_destroy_func_t) (void *user_data); - -struct netlink_info; - -struct netlink_info *netlink_new(int protocol); -void netlink_destroy(struct netlink_info *netlink); - -unsigned int netlink_send(struct netlink_info *netlink, - uint16_t type, uint16_t flags, const void *data, - uint32_t len, netlink_command_func_t function, - void *user_data, netlink_destroy_func_t destroy); -bool netlink_cancel(struct netlink_info *netlink, unsigned int id); - -unsigned int netlink_register(struct netlink_info *netlink, - uint32_t group, netlink_notify_func_t function, - void *user_data, netlink_destroy_func_t destroy); -bool netlink_unregister(struct netlink_info *netlink, unsigned int id); - -bool netlink_set_debug(struct netlink_info *netlink, - netlink_debug_func_t function, - void *user_data, netlink_destroy_func_t destroy); diff --git a/src/shared/util.c b/src/shared/util.c index df045c5..73c24ae 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -88,3 +88,42 @@ void util_hexdump(const char dir, const unsigned char *buf, size_t len, function(str, user_data); } } + +void util_iso8601_to_timeval(char *str, struct timeval *time) +{ + struct tm tm; + time_t t; + char *p; + + p = strptime(str, "%FT%T", &tm); + if (!p) + return; + + if (*p != 'Z') { + /* backwards compatibility */ + if (*p != '.' || p[strlen(p) - 1] != 'Z') + return; + } + + t = mktime(&tm); + if (t < 0) + return; + + time->tv_sec = t; + time->tv_usec = 0; +} + +char *util_timeval_to_iso8601(struct timeval *time) +{ + char buf[255]; + struct tm tm; + time_t t; + + t = time->tv_sec; + if (!localtime_r(&t, &tm)) + return NULL; + if (!strftime(buf, sizeof(buf), "%FT%TZ", &tm)) + return NULL; + + return g_strdup(buf); +} diff --git a/src/shared/util.h b/src/shared/util.h index 293fb3a..430b821 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -21,6 +21,8 @@ * */ +#include + #include typedef void (*util_debug_func_t)(const char *str, void *user_data); @@ -48,3 +50,6 @@ static inline struct cb_data *cb_data_new(void *cb, void *user_data) return ret; } + +void util_iso8601_to_timeval(char *str, struct timeval *time); +char *util_timeval_to_iso8601(struct timeval *time); diff --git a/src/stats.c b/src/stats.c index 6f7ce20..1df41f1 100644 --- a/src/stats.c +++ b/src/stats.c @@ -53,7 +53,7 @@ * * File properties: * The ring buffer is mmap to a file - * Initialy only the smallest possible amount of disk space is allocated + * Initially only the smallest possible amount of disk space is allocated * The files grow to the configured maximal size * The grows by _SC_PAGESIZE step size * For each service a file is created @@ -80,8 +80,8 @@ * * History file: * Same format as the ring buffer file - * For a period of at least 2 months dayly records are keept - * If older, then only a monthly record is keept + * For a period of at least 2 months daily records are kept + * If older, then only a monthly record is kept */ @@ -348,7 +348,7 @@ static int stats_open_temp(struct stats_file *file) STORAGEDIR); file->fd = g_mkstemp_full(file->name, O_RDWR | O_CREAT, 0644); if (file->fd < 0) { - connman_error("create tempory file error %s for %s", + connman_error("create temporary file error %s for %s", strerror(errno), file->name); g_free(file->name); file->name = NULL; diff --git a/src/storage.c b/src/storage.c index 5e877ef..90f03eb 100644 --- a/src/storage.c +++ b/src/storage.c @@ -161,28 +161,6 @@ GKeyFile *__connman_storage_load_provider_config(const char *ident) return keyfile; } -GKeyFile *__connman_storage_open_service(const char *service_id) -{ - gchar *pathname; - GKeyFile *keyfile = NULL; - - pathname = g_strdup_printf("%s/%s/%s", STORAGEDIR, service_id, SETTINGS); - if (!pathname) - return NULL; - - keyfile = storage_load(pathname); - if (keyfile) { - g_free(pathname); - return keyfile; - } - - g_free(pathname); - - keyfile = g_key_file_new(); - - return keyfile; -} - gchar **connman_storage_get_services(void) { struct dirent *d; diff --git a/src/task.c b/src/task.c index 953cc40..280b5e4 100644 --- a/src/task.c +++ b/src/task.c @@ -45,8 +45,10 @@ struct connman_task { GPtrArray *argv; GPtrArray *envp; connman_task_exit_t exit_func; + connman_task_setup_t setup_func; void *exit_data; GHashTable *notify; + void *setup_data; }; static GHashTable *task_hash = NULL; @@ -93,7 +95,9 @@ static void free_task(gpointer data) * * Returns: a newly-allocated #connman_task structure */ -struct connman_task *connman_task_create(const char *program) +struct connman_task *connman_task_create(const char *program, + connman_task_setup_t custom_task_setup, + void *setup_data) { struct connman_task *task; gint counter; @@ -116,9 +120,13 @@ struct connman_task *connman_task_create(const char *program) str = g_strdup(program); g_ptr_array_add(task->argv, str); + task->setup_func = custom_task_setup; + task->notify = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + task->setup_data = setup_data; + DBG("task %p", task); g_hash_table_insert(task_hash, task->path, task); @@ -130,7 +138,7 @@ struct connman_task *connman_task_create(const char *program) * connman_task_destory: * @task: task structure * - * Remove and destory #task + * Remove and destroy #task */ void connman_task_destroy(struct connman_task *task) { @@ -220,7 +228,7 @@ int connman_task_add_variable(struct connman_task *task, /** * connman_task_set_notify: * @task: task structure - * @member: notifcation method name + * @member: notification method name * @function: notification callback * @user_data: optional notification user data * @@ -277,6 +285,9 @@ static void task_setup(gpointer user_data) sigemptyset(&mask); if (sigprocmask(SIG_SETMASK, &mask, NULL) < 0) connman_error("Failed to clean signal mask"); + + if (task->setup_func) + task->setup_func(task->setup_data); } /** diff --git a/src/tethering.c b/src/tethering.c index e04756f..e2687b6 100644 --- a/src/tethering.c +++ b/src/tethering.c @@ -637,8 +637,8 @@ void __connman_tethering_client_register(const char *addr) void __connman_tethering_client_unregister(const char *addr) { - g_hash_table_remove(clients_table, addr); client_removed(addr); + g_hash_table_remove(clients_table, addr); } int __connman_tethering_init(void) diff --git a/src/timeserver.c b/src/timeserver.c index 9832c2a..decca15 100644 --- a/src/timeserver.c +++ b/src/timeserver.c @@ -328,7 +328,7 @@ static void ts_recheck_enable(void) } /* - * This function must be called everytime the default service changes, the + * This function must be called every time the default service changes, the * service timeserver(s) or gateway changes or the global timeserver(s) changes. */ int __connman_timeserver_sync(struct connman_service *default_service) diff --git a/src/timezone.c b/src/timezone.c index 8e91267..cc49909 100644 --- a/src/timezone.c +++ b/src/timezone.c @@ -187,7 +187,10 @@ static char *find_origin(void *src_map, struct stat *src_st, subpath, d->d_name); if (compare_file(src_map, src_st, pathname) == 0) { - str = g_strdup_printf("%s/%s", + if (!subpath) + str = g_strdup(d->d_name); + else + str = g_strdup_printf("%s/%s", subpath, d->d_name); closedir(dir); return str; diff --git a/src/wispr.c b/src/wispr.c index 473c0e0..4115758 100644 --- a/src/wispr.c +++ b/src/wispr.c @@ -555,12 +555,31 @@ static void wispr_portal_browser_reply_cb(struct connman_service *service, const char *error, void *user_data) { struct connman_wispr_portal_context *wp_context = user_data; + struct connman_wispr_portal *wispr_portal; + int index; DBG(""); if (!service || !wp_context) return; + /* + * No way to cancel this if wp_context has been freed, so we lookup + * from the service and check that this is still the right context. + */ + index = __connman_service_get_index(service); + if (index < 0) + return; + + wispr_portal = g_hash_table_lookup(wispr_portal_list, + GINT_TO_POINTER(index)); + if (!wispr_portal) + return; + + if (wp_context != wispr_portal->ipv4_context && + wp_context != wispr_portal->ipv6_context) + return; + if (!authentication_done) { wispr_portal_error(wp_context); free_wispr_routes(wp_context); diff --git a/test/backtrace b/test/backtrace index c906f36..c624709 100755 --- a/test/backtrace +++ b/test/backtrace @@ -6,7 +6,7 @@ import sys import subprocess if (len(sys.argv) < 3): - print "Usage: %s [binary] [log]" % (sys.argv[0]) + print("Usage: %s [binary] [log]" % (sys.argv[0])) sys.exit(1) binary = sys.argv[1] @@ -50,8 +50,8 @@ child_stdout.close() frame_count = len(frames); count = 0 -print "-------- backtrace --------" +print("-------- backtrace --------") while count < frame_count: - print "[%d]: %s() [%s]" % (count/2, frames[count], frames[count + 1]) + print("[%d]: %s() [%s]" % (count/2, frames[count], frames[count + 1])) count = count + 2 -print "---------------------------" +print("---------------------------") diff --git a/test/connect-provider b/test/connect-provider index 15128c8..504bce4 100755 --- a/test/connect-provider +++ b/test/connect-provider @@ -4,15 +4,15 @@ import sys import dbus if (len(sys.argv) < 4): - print "Usage: %s ... " % (sys.argv[0]) - print " type: openconnect" - print " [servercert]" - print " type: openvpn" - print " [ ]" - print " type: pptp" - print " " - print " type: l2tp" - print " " + print("Usage: %s ... " % (sys.argv[0])) + print(" type: openconnect") + print(" [servercert]") + print(" type: openvpn") + print(" [ ]") + print(" type: pptp") + print(" ") + print(" type: l2tp") + print(" ") sys.exit(1) bus = dbus.SystemBus() @@ -20,7 +20,7 @@ bus = dbus.SystemBus() manager = dbus.Interface(bus.get_object("net.connman", "/"), "net.connman.Manager") -print "Attempting to connect service %s" % (sys.argv[3]) +print("Attempting to connect service %s" % (sys.argv[3])) if sys.argv[1] == "openconnect": if (len(sys.argv) > 6): @@ -67,7 +67,7 @@ elif sys.argv[1] == "l2tp": "L2TP.Password": sys.argv[6]})) else: - print "Unknown VPN type" + print("Unknown VPN type") sys.exit(1) -print "VPN service path is %s" %(path) +print("VPN service path is %s" %(path)) diff --git a/test/disable-tethering b/test/disable-tethering index a3d5908..41f3d79 100755 --- a/test/disable-tethering +++ b/test/disable-tethering @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) != 2): - print "Usage: %s type" % (sys.argv[0]) + print("Usage: %s type" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -18,10 +18,10 @@ def technology_disable_tethering(path, tech_type): properties = tech.GetProperties() - for key in properties.keys(): + for key in list(properties.keys()): if key in ["Type"]: if properties[key] == tech_type: - print "Disabling %s tethering" % tech_type + print("Disabling %s tethering" % tech_type) tech.SetProperty("Tethering", dbus.Boolean(0)) return tech_type @@ -37,4 +37,4 @@ for path,_ in technologies: break; if tech == None: - print "Failed to disable %s tethering" % (sys.argv[1]) + print("Failed to disable %s tethering" % (sys.argv[1])) diff --git a/test/enable-tethering b/test/enable-tethering index cbcd4e7..13e5a18 100755 --- a/test/enable-tethering +++ b/test/enable-tethering @@ -4,10 +4,10 @@ import sys import dbus if (len(sys.argv) >= 3 and len(sys.argv) != 4 and sys.argv[1] == "wifi"): - print "Usage: %s wifi [SSID] [passphrase]" % (sys.argv[0]) + print("Usage: %s wifi [SSID] [passphrase]" % (sys.argv[0])) sys.exit(1) elif (len(sys.argv) < 2): - print "Usage: %s type" % (sys.argv[0]) + print("Usage: %s type" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -21,7 +21,7 @@ def technology_enable_tethering(path, tech_type, ssid, psk): properties = tech.GetProperties() - for key in properties.keys(): + for key in list(properties.keys()): if key in ["Type"]: if properties[key] == tech_type: if len(ssid) > 0: @@ -30,7 +30,7 @@ def technology_enable_tethering(path, tech_type, ssid, psk): if len(psk) > 0: tech.SetProperty("TetheringPassphrase", psk) - print "Enabling %s tethering" % tech_type + print("Enabling %s tethering" % tech_type) tech.SetProperty("Tethering", dbus.Boolean(1)) return tech_type @@ -51,4 +51,4 @@ for path,_ in technologies: break; if tech == None: - print "Failed to enable %s tethering" % (sys.argv[1]) + print("Failed to enable %s tethering" % (sys.argv[1])) diff --git a/test/get-global-timeservers b/test/get-global-timeservers index adcf175..2436b36 100755 --- a/test/get-global-timeservers +++ b/test/get-global-timeservers @@ -9,4 +9,4 @@ clock = dbus.Interface(bus.get_object('net.connman', '/'), properties = clock.GetProperties() -print "Timeserver is %s" % (properties["Timeservers"]) +print("Timeserver is %s" % (properties["Timeservers"])) diff --git a/test/get-proxy-autoconfig b/test/get-proxy-autoconfig index 6709a9e..b96e800 100755 --- a/test/get-proxy-autoconfig +++ b/test/get-proxy-autoconfig @@ -1,12 +1,12 @@ #!/usr/bin/python import dbus -import urllib +import urllib.request, urllib.parse, urllib.error def get_pac(url): - conn = urllib.urlopen(url, proxies={}) + conn = urllib.request.urlopen(url, proxies={}) data = conn.read() - print data + print(data) conn.close() bus = dbus.SystemBus() @@ -23,15 +23,15 @@ for entry in services: proxy = properties["Proxy"] if "Method" in proxy: - print "[ %s ]" % (path) + print("[ %s ]" % (path)) method = proxy["Method"] - print "Method = %s" % (method) + print("Method = %s" % (method)) if method in ["auto"]: url = proxy["URL"] - print "URL = %s" % (url) - print + print("URL = %s" % (url)) + print() get_pac(url) else: - print + print() diff --git a/test/get-services b/test/get-services index 0956480..2fa8b5b 100755 --- a/test/get-services +++ b/test/get-services @@ -4,7 +4,7 @@ import dbus def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["Servers", "Excludes"]: val += extract_list(values[key]) @@ -31,9 +31,9 @@ for entry in services: path = entry[0] properties = entry[1] - print "[ %s ]" % (path) + print("[ %s ]" % (path)) - for key in properties.keys(): + for key in list(properties.keys()): if key in ["IPv4", "IPv4.Configuration", "IPv6", "IPv6.Configuration", "Proxy", "Proxy.Configuration", @@ -53,6 +53,6 @@ for entry in services: val = int(properties[key]) else: val = str(properties[key]) - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) - print + print() diff --git a/test/get-state b/test/get-state index 75d5a16..e991f58 100755 --- a/test/get-state +++ b/test/get-state @@ -9,4 +9,4 @@ manager = dbus.Interface(bus.get_object('net.connman', "/"), properties = manager.GetProperties() -print "System is %s" % (properties["State"]) +print("System is %s" % (properties["State"])) diff --git a/test/list-services b/test/list-services index a2610d7..4accf77 100755 --- a/test/list-services +++ b/test/list-services @@ -4,7 +4,7 @@ import dbus def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["PrefixLength"]: val += "%s" % (int(values[key])) @@ -34,9 +34,9 @@ for path, properties in manager.GetServices(): service = dbus.Interface(bus.get_object("net.connman", path), "net.connman.Service") identifier = path[path.rfind("/") + 1:] - print "[ %s ]" % (identifier) + print("[ %s ]" % (identifier)) - for key in properties.keys(): + for key in list(properties.keys()): if key in ["IPv4", "IPv4.Configuration", "IPv6", "IPv6.Configuration", "Proxy", "Proxy.Configuration", @@ -58,6 +58,6 @@ for path, properties in manager.GetServices(): val = int(properties[key]) else: val = properties[key] - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) - print + print() diff --git a/test/monitor-connman b/test/monitor-connman index 1b3b84c..8403f91 100755 --- a/test/monitor-connman +++ b/test/monitor-connman @@ -19,7 +19,7 @@ def extract_list(list): def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["PrefixLength"]: val += "%s" % (int(values[key])) @@ -54,7 +54,7 @@ def property_changed(name, value, path, interface): iface = interface[interface.rfind(".") + 1:] val = extract(name, value) - print "{%s} [%s] %s = %s" % (iface, path, name, val) + print("{%s} [%s] %s = %s" % (iface, path, name, val)) def message_filter(connection, message): if not isinstance(message, MethodCallMessage): diff --git a/test/monitor-services b/test/monitor-services index 9476bf8..d570e5f 100755 --- a/test/monitor-services +++ b/test/monitor-services @@ -7,7 +7,7 @@ import dbus.mainloop.glib def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["Servers", "Excludes"]: val += extract_list(values[key]) @@ -42,27 +42,27 @@ def property_changed(name, value, path): val = int(value) else: val = str(value) - print "[%s] %s = %s" % (service, name, val) + print("[%s] %s = %s" % (service, name, val)) def services_changed(services, removed): for i in services: service = i[0][i[0].rfind("/") + 1:] - print "[%s] changed" % (service) - for n in i[1].keys(): + print("[%s] changed" % (service)) + for n in list(i[1].keys()): property_changed(n, i[1][n], i[0]) for i in removed: service = i[i.rfind("/") + 1:] - print "[%s] removed" % (service) + print("[%s] removed" % (service)) def technology_added(path, properties): technology = path[path.rfind("/") + 1:] - print "[%s] added" % (technology) - for n in properties.keys(): + print("[%s] added" % (technology)) + for n in list(properties.keys()): property_changed(n, properties[n], technology) def technology_removed(path): technology = path[path.rfind("/") + 1:] - print "[%s] removed" % (technology) + print("[%s] removed" % (technology)) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) diff --git a/test/monitor-vpn b/test/monitor-vpn index 2b63687..e019e6e 100755 --- a/test/monitor-vpn +++ b/test/monitor-vpn @@ -21,7 +21,7 @@ def extract_list(list): def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["ProtocolFamily"]: val += "%s" % (int(values[key])) @@ -50,7 +50,7 @@ def property_changed(name, value, path, interface): iface = interface[interface.rfind(".") + 1:] val = extract(name, value) - print "{%s} [%s] %s = %s" % (iface, path, name, val) + print("{%s} [%s] %s = %s" % (iface, path, name, val)) def message_filter(connection, message): if not isinstance(message, MethodCallMessage): diff --git a/test/p2p-on-supplicant b/test/p2p-on-supplicant index 8cc76e8..339d5eb 100755 --- a/test/p2p-on-supplicant +++ b/test/p2p-on-supplicant @@ -38,7 +38,7 @@ class InputLine: def prompt(self): self.line = '' - print '> ', + print('> ', end=' ') stdout.flush() def input_cb(self, fd, event): @@ -58,7 +58,7 @@ class InputLine: return True def error_print(ex): - print 'Command Error: %s' % ex + print('Command Error: %s' % ex) def checkarg(nb_args = 0, min_args = False): def under(function): @@ -81,11 +81,11 @@ def print_dict(d): for k in d: try: if type(d[k]) is dbus.Byte: - print 'Key %s --> 0x%x' % (k, d[k]) + print('Key %s --> 0x%x' % (k, d[k])) else: - print 'Key %s --> %s' % (k, d[k]) + print('Key %s --> %s' % (k, d[k])) except: - print "Error: Key %s content cannot be printed" % k + print("Error: Key %s content cannot be printed" % k) pass def print_tuple(t): @@ -93,7 +93,7 @@ def print_tuple(t): if type(e) is dbus.Dictionary: print_dict(e) else: - print 'Element: %s' % e + print('Element: %s' % e) class Wpa_s: def __init__(self, bus, iface_name, command): @@ -116,41 +116,41 @@ class Wpa_s: try: self.create_if([iface_name]) except: - print "Error creating interface: %s" % iface_name + print("Error creating interface: %s" % iface_name) if len(command.strip(' ')): self.__command(command) def help(self, args): - list = self.command_list.keys() + list = list(self.command_list.keys()) list.sort() for key in list: help = '' - if (self.command_list[key].has_key(ArgFields.help)): + if (ArgFields.help in self.command_list[key]): help = self.command_list[key][ArgFields.help] - print "%s\t%s" % (key.rjust(25), help.ljust(50)) + print("%s\t%s" % (key.rjust(25), help.ljust(50))) def __command(self, cmd_line): cmd = cmd_line.split(' ') try: func = getattr(self, cmd[0]) - except Exception, e: - print 'Error: command unknown - %s' % e + except Exception as e: + print('Error: command unknown - %s' % e) return try: func(cmd[1:]) - except Exception, e: + except Exception as e: error_print(e) def __wpa_property_changed(*args, **kwargs): - print 'WPA - Signal: %s' % kwargs.get('signal') + print('WPA - Signal: %s' % kwargs.get('signal')) def __if_property_changed(*args, **kwargs): signal = kwargs.get('signal') - print 'IF - Signal: %s' % signal + print('IF - Signal: %s' % signal) if signal == 'BSSAdded': return @@ -159,12 +159,12 @@ class Wpa_s: print_tuple(args[1:]) def __p2p_property_changed(*args, **kwargs): - print 'IF P2P - Signal: %s' % kwargs.get('signal') + print('IF P2P - Signal: %s' % kwargs.get('signal')) if args[0].debug: print_tuple(args[1:]) def __peer_if_p2p_property_changed(*args, **kwargs): - print 'Peer - ', + print('Peer - ', end=' ') args[0].__p2p_property_changed(*args, **kwargs) def __DeviceFound(self, object_path): @@ -187,31 +187,31 @@ class Wpa_s: del self.peers[object_path] def __PeerJoined(self, object_path): - print 'Peer %s joined' % object_path + print('Peer %s joined' % object_path) def __PeerDisconnected(self, object_path): - print 'Peer %s disconnected' % object_path + print('Peer %s disconnected' % object_path) def __group_if_property_changed(*args, **kwargs): - print 'Group - ', + print('Group - ', end=' ') args[0].__if_property_changed(*args, **kwargs) def __group_if_p2p_property_changed(*args, **kwargs): - print 'Group - ', + print('Group - ', end=' ') args[0].__p2p_property_changed(*args, **kwargs) def __GroupFinished(self, properties): - print 'Group running on %s is being removed' % ifname + print('Group running on %s is being removed' % ifname) self.group_obj = self.group_if = self.group_iface_path = None if self.debug: print_dict(properties) def __InvitationResult(self, response): - print 'Invitation result status: %d ' % response['status'] + print('Invitation result status: %d ' % response['status']) - if response.has_key('bssid'): - print 'bssid: %s' % response['bssid'] + if 'bssid' in response: + print('bssid: %s' % response['bssid']) if self.debug: print_dict(response) @@ -257,11 +257,11 @@ class Wpa_s: def __ServiceDiscoveryResponse(self, response): peer = response['peer_object'] if peer in self.peers: - print 'Peer %s has this TLVs:' % (self.peers[peer]['DeviceName']) - print response['tlvs'] + print('Peer %s has this TLVs:' % (self.peers[peer]['DeviceName'])) + print(response['tlvs']) def __InterfaceAdded(self, path, properties): - print 'Interface %s Added (%s)' % (properties['Ifname'], path) + print('Interface %s Added (%s)' % (properties['Ifname'], path)) if self.debug: print_dict(properties) p2p = dbus.Interface(self.bus.get_object(WPA_INTF, @@ -269,7 +269,7 @@ class Wpa_s: print_dict(p2p.GetAll(WPA_P2P_INTF)) def __InterfaceRemoved(self, path): - print 'Interface Removed (%s)' % (path) + print('Interface Removed (%s)' % (path)) def __listen_if_signals(self): self.bus.add_signal_receiver(self.__if_property_changed, @@ -312,7 +312,7 @@ class Wpa_s: p2p_if.Set(WPA_P2P_INTF, 'P2PDeviceConfig', dbus.Dictionary({ 'DeviceName' : 'ConnManP2P' }, signature='sv')) - print 'Interface %s: %s' % (iface_name, self.iface_path) + print('Interface %s: %s' % (iface_name, self.iface_path)) self.iface_name = iface_name self.__listen_if_signals() @@ -342,7 +342,7 @@ class Wpa_s: return self.wpa.RemoveInterface(self.iface_path) - print 'Interface %s removed' % self.iface_name + print('Interface %s removed' % self.iface_name) self.__reset() @checkarg() @@ -351,7 +351,7 @@ class Wpa_s: return self.iface.Scan(({ 'Type': 'passive' })) - print 'Scan started' + print('Scan started') @checkarg() def quit(self, args = None): @@ -382,7 +382,7 @@ class Wpa_s: return for p in self.peers: - print 'Peer Name=%s' % (self.peers[p]['DeviceName']) + print('Peer Name=%s' % (self.peers[p]['DeviceName'])) def __find_peer(self, peer_name, ret_object_path = False): if len(self.peers) == 0: @@ -395,7 +395,7 @@ class Wpa_s: break if not peer: - print 'No peer found under the name: %s' % peer_name + print('No peer found under the name: %s' % peer_name) p = None if ret_object_path: @@ -426,19 +426,19 @@ class Wpa_s: if (peer['groupcapability'] & P2P_GROUP_CAPAB_GROUP_OWNER == P2P_GROUP_CAPAB_GROUP_OWNER): - print 'Joining an existing P2P group' + print('Joining an existing P2P group') pin = self.p2p.Connect(({ 'peer' : peer_path, 'wps_method' : 'pbc', 'join' : True, 'go_intent' : 0 })) else: - print 'Associating with another P2P device' + print('Associating with another P2P device') pin = self.p2p.Connect(({ 'peer' : peer_path, 'wps_method' : 'pbc', 'join' : False, 'go_intent' : 7 })) if not pin: - print 'WPS PIN in use: %s' % pin + print('WPS PIN in use: %s' % pin) @checkarg(nb_args = 1) def p2p_disconnect(self, args): @@ -450,7 +450,7 @@ class Wpa_s: return if not self.group_if: - print 'Peer %s is not connected' % (peer['DeviceName']) + print('Peer %s is not connected' % (peer['DeviceName'])) return self.group_if.Disconnect() @@ -496,7 +496,7 @@ class Wpa_s: sd_req.append(dbus.Byte(a)) ref = self.p2p.ServiceDiscoveryRequest(({ 'tlv' : sd_req })) - print 'Service discovery reference: %s' % ref + print('Service discovery reference: %s' % ref) @checkarg(nb_args = 1) def p2p_serv_disc_cancel_req(self, args): @@ -518,7 +518,7 @@ class Wpa_s: service['query'] = args[1] service['response'] = args[2] else: - print 'Unknown service: %s' % args[0] + print('Unknown service: %s' % args[0]) return self.p2p.AddService((service)) @@ -535,7 +535,7 @@ class Wpa_s: elif args[0] == 'bonjour': service['query'] = args[1] else: - print 'Unknown service: %s' % args[0] + print('Unknown service: %s' % args[0]) return self.p2p.DeleteService((service)) @@ -593,16 +593,16 @@ def build_args(parser): command['p2p_service_flush'] = {} command['p2p_invite'] = {ArgFields.help:''} - command_list = command.keys() + command_list = list(command.keys()) command_list.sort() subparsers = parser.add_subparsers(help='commands', dest='command') subparsers.add_parser('') for key in command_list: help=None metavar=None - if command[key].has_key(ArgFields.help): + if ArgFields.help in command[key]: help = command[key][ArgFields.help] - if command[key].has_key(ArgFields.metavar): + if ArgFields.metavar in command[key]: metavar = command[key][ArgFields.metavar] command_parser = subparsers.add_parser(key, help=help) command_parser.add_argument(key, nargs='*', metavar=metavar, help=help) @@ -611,7 +611,7 @@ def build_args(parser): def main(): if version_info.major != 2: - print 'You need to run this under Python 2.x' + print('You need to run this under Python 2.x') exit(1) parser = argparse.ArgumentParser(description='Connman P2P Test') diff --git a/test/remove-provider b/test/remove-provider index 39f8de7..a2f3e6f 100755 --- a/test/remove-provider +++ b/test/remove-provider @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage: %s " % (sys.argv[0]) + print("Usage: %s " % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -14,6 +14,6 @@ manager = dbus.Interface(bus.get_object("net.connman", "/"), path = "" + sys.argv[1] -print "remove path is %s" %(path) +print("remove path is %s" %(path)) manager.RemoveProvider(sys.argv[1]) diff --git a/test/service-move-before b/test/service-move-before index d912c88..8152637 100755 --- a/test/service-move-before +++ b/test/service-move-before @@ -4,7 +4,7 @@ import sys import dbus def print_usage(): - print "Usage: %s " % (sys.argv[0]) + print("Usage: %s " % (sys.argv[0])) if (len(sys.argv) < 2): @@ -20,8 +20,8 @@ path2 = "/net/connman/service/" + sys.argv[2] service2 = dbus.Interface(bus.get_object('net.connman', path2), 'net.connman.Service') -print "Moving %s before %s" % (sys.argv[1], sys.argv[2]) +print("Moving %s before %s" % (sys.argv[1], sys.argv[2])) service.MoveBefore(service2) -print +print() diff --git a/test/set-clock b/test/set-clock index a9db3e3..bb443d0 100755 --- a/test/set-clock +++ b/test/set-clock @@ -4,7 +4,7 @@ import sys import dbus def print_usage(): - print "Usage: %s TimeUpdates|TimezoneUpdates manual|auto" % (sys.argv[0]) + print("Usage: %s TimeUpdates|TimezoneUpdates manual|auto" % (sys.argv[0])) sys.exit(1) @@ -26,7 +26,7 @@ bus = dbus.SystemBus() clock = dbus.Interface(bus.get_object('net.connman', '/'), 'net.connman.Clock') -print "Setting %s to %s" % (sys.argv[1], sys.argv[2]) +print("Setting %s to %s" % (sys.argv[1], sys.argv[2])) clock.SetProperty(sys.argv[1], make_variant(sys.argv[2]), signature=dbus.Signature('sv')) diff --git a/test/set-domains b/test/set-domains index 87e563e..ec98c5e 100755 --- a/test/set-domains +++ b/test/set-domains @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage: %s [domain*]" % (sys.argv[0]) + print("Usage: %s [domain*]" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -14,7 +14,7 @@ service = dbus.Interface(bus.get_object('net.connman', path), properties = service.GetProperties() -print "Setting domains to %s" % (sys.argv[2:]) +print("Setting domains to %s" % (sys.argv[2:])) service.SetProperty("Domains.Configuration", dbus.Array(sys.argv[2:], signature=dbus.Signature('s'))) diff --git a/test/set-global-timeservers b/test/set-global-timeservers index d7551a1..1489ec7 100755 --- a/test/set-global-timeservers +++ b/test/set-global-timeservers @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 1): - print "Usage: %s [timeserver*]" % (sys.argv[0]) + print("Usage: %s [timeserver*]" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -12,7 +12,7 @@ bus = dbus.SystemBus() clock = dbus.Interface(bus.get_object('net.connman', '/'), 'net.connman.Clock') -print "Setting timeserver to %s" % (sys.argv[1:]) +print("Setting timeserver to %s" % (sys.argv[1:])) clock.SetProperty("Timeservers", dbus.Array(sys.argv[1:], signature=dbus.Signature('s'))) diff --git a/test/set-ipv4-method b/test/set-ipv4-method index 235113f..09c194d 100755 --- a/test/set-ipv4-method +++ b/test/set-ipv4-method @@ -7,7 +7,7 @@ def make_variant(string): return dbus.String(string, variant_level=1) def print_usage(): - print "Usage: %s [off|dhcp|manual
[netmask] [gateway]]" % (sys.argv[0]) + print("Usage: %s [off|dhcp|manual
[netmask] [gateway]]" % (sys.argv[0])) if (len(sys.argv) < 3): @@ -21,7 +21,7 @@ service = dbus.Interface(bus.get_object('net.connman', path), properties = service.GetProperties() -print "Setting method %s for %s" % (sys.argv[2], sys.argv[1]) +print("Setting method %s for %s" % (sys.argv[2], sys.argv[1])) ipv4_configuration = { "Method": make_variant(sys.argv[2]) } if (len(sys.argv) > 3): @@ -32,6 +32,6 @@ if (len(sys.argv) > 5): ipv4_configuration["Gateway"] = make_variant(sys.argv[5]) service.SetProperty("IPv4.Configuration", ipv4_configuration) -print "New IPv4.Configuration: ", ipv4_configuration +print("New IPv4.Configuration: ", ipv4_configuration) -print +print() diff --git a/test/set-ipv6-method b/test/set-ipv6-method index eb1f1b5..5536e6f 100755 --- a/test/set-ipv6-method +++ b/test/set-ipv6-method @@ -10,7 +10,7 @@ def make_byte_variant(string): return dbus.Byte(int(string), variant_level=1) def print_usage(): - print "Usage: %s off|manual|auto [
[prefixlen] [gateway]] []" % (sys.argv[0]) + print("Usage: %s off|manual|auto [
[prefixlen] [gateway]] []" % (sys.argv[0])) if (len(sys.argv) < 3): print_usage() @@ -23,7 +23,7 @@ service = dbus.Interface(bus.get_object('net.connman', path), properties = service.GetProperties() -print "Setting method %s for %s" % (sys.argv[2], sys.argv[1]) +print("Setting method %s for %s" % (sys.argv[2], sys.argv[1])) ipv6_configuration = { "Method": make_variant(sys.argv[2])} if sys.argv[2] == "auto": @@ -38,6 +38,6 @@ else: ipv6_configuration["Gateway"] = make_variant(sys.argv[5]) service.SetProperty("IPv6.Configuration", ipv6_configuration) -print "New IPv6.Configuration: ", ipv6_configuration +print("New IPv6.Configuration: ", ipv6_configuration) -print +print() diff --git a/test/set-nameservers b/test/set-nameservers index ece69b8..584df7e 100755 --- a/test/set-nameservers +++ b/test/set-nameservers @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage: %s [nameserver*]" % (sys.argv[0]) + print("Usage: %s [nameserver*]" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -14,7 +14,7 @@ service = dbus.Interface(bus.get_object('net.connman', path), properties = service.GetProperties() -print "Setting nameserver to %s" % (sys.argv[2:]) +print("Setting nameserver to %s" % (sys.argv[2:])) service.SetProperty("Nameservers.Configuration", dbus.Array(sys.argv[2:], signature=dbus.Signature('s'))) diff --git a/test/set-proxy b/test/set-proxy index b9da7b0..f308d32 100755 --- a/test/set-proxy +++ b/test/set-proxy @@ -4,12 +4,12 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage:" - print "%s direct" % (sys.argv[0]) - print "%s manual [servers=uri1,uri2,...] [excludes=host1,host2,...]" % (sys.argv[0]) - print "%s auto url=[pac-url]" % (sys.argv[0]) - print "Example: %s service0 manual servers=proxy.example.com:8080" % sys.argv[0] - print " This would set the proxy uri and the method to manual" + print("Usage:") + print("%s direct" % (sys.argv[0])) + print("%s manual [servers=uri1,uri2,...] [excludes=host1,host2,...]" % (sys.argv[0])) + print("%s auto url=[pac-url]" % (sys.argv[0])) + print("Example: %s service0 manual servers=proxy.example.com:8080" % sys.argv[0]) + print(" This would set the proxy uri and the method to manual") sys.exit(1) bus = dbus.SystemBus() @@ -40,5 +40,5 @@ for arg in sys.argv[3:]: try: service.SetProperty("Proxy.Configuration", dbus.Dictionary(values, signature='sv')) -except dbus.exceptions.DBusException, e_msg: - print e_msg +except dbus.exceptions.DBusException as e_msg: + print(e_msg) diff --git a/test/set-timeservers b/test/set-timeservers index 19cc938..55a1984 100755 --- a/test/set-timeservers +++ b/test/set-timeservers @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage: %s [timeserver*]" % (sys.argv[0]) + print("Usage: %s [timeserver*]" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -14,7 +14,7 @@ service = dbus.Interface(bus.get_object('net.connman', path), properties = service.GetProperties() -print "Setting timeserver to %s" % (sys.argv[2:]) +print("Setting timeserver to %s" % (sys.argv[2:])) service.SetProperty("Timeservers.Configuration", dbus.Array(sys.argv[2:], signature=dbus.Signature('s'))) diff --git a/test/set-timezone b/test/set-timezone index dfc1c98..4808bd8 100755 --- a/test/set-timezone +++ b/test/set-timezone @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) != 2): - print "Usage: %s " % (sys.argv[0]) + print("Usage: %s " % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -12,10 +12,10 @@ bus = dbus.SystemBus() clock = dbus.Interface(bus.get_object('net.connman', '/'), 'net.connman.Clock') -print "Setting timezone to %s" % (sys.argv[1]) +print("Setting timezone to %s" % (sys.argv[1])) try: clock.SetProperty("Timezone", dbus.String(sys.argv[1], variant_level=1), signature=dbus.Signature('sv')) -except dbus.exceptions.DBusException, e_msg: - print e_msg +except dbus.exceptions.DBusException as e_msg: + print(e_msg) diff --git a/test/show-introspection b/test/show-introspection index 4b6450f..983b36f 100755 --- a/test/show-introspection +++ b/test/show-introspection @@ -7,7 +7,7 @@ bus = dbus.SystemBus() object = dbus.Interface(bus.get_object("net.connman", '/'), "org.freedesktop.DBus.Introspectable") -print object.Introspect() +print(object.Introspect()) manager = dbus.Interface(bus.get_object("net.connman", "/"), "net.connman.Manager") @@ -18,4 +18,4 @@ for path, properties in technologies: object = dbus.Interface(bus.get_object("net.connman", path), "org.freedesktop.DBus.Introspectable") - print object.Introspect() + print(object.Introspect()) diff --git a/test/simple-agent b/test/simple-agent index 01c82ba..282785e 100755 --- a/test/simple-agent +++ b/test/simple-agent @@ -32,8 +32,8 @@ class Agent(dbus.service.Object): response = {} if not self.identity and not self.passphrase and not self.wpspin: - print "Service credentials requested, type cancel to cancel" - args = raw_input('Answer: ') + print("Service credentials requested, type cancel to cancel") + args = input('Answer: ') for arg in args.split(): if arg.startswith("cancel"): @@ -62,9 +62,9 @@ class Agent(dbus.service.Object): response = {} if not self.username and not self.password: - print "User login requested, type cancel to cancel" - print "or browser to login through the browser by yourself." - args = raw_input('Answer: ') + print("User login requested, type cancel to cancel") + print("or browser to login through the browser by yourself.") + args = input('Answer: ') for arg in args.split(): if arg.startswith("cancel") or arg.startswith("browser"): @@ -87,7 +87,7 @@ class Agent(dbus.service.Object): response = {} if not self.name and not self.ssid: - args = raw_input('Answer ') + args = input('Answer ') for arg in args.split(): if arg.startswith("Name="): @@ -110,18 +110,18 @@ class Agent(dbus.service.Object): in_signature='oa{sv}', out_signature='a{sv}') def RequestInput(self, path, fields): - print "RequestInput (%s,%s)" % (path, fields) + print("RequestInput (%s,%s)" % (path, fields)) response = {} - if fields.has_key("Name"): + if "Name" in fields: response.update(self.input_hidden()) - if fields.has_key("Passphrase"): + if "Passphrase" in fields: response.update(self.input_passphrase()) - if fields.has_key("Username"): + if "Username" in fields: response.update(self.input_username()) - if response.has_key("Error"): + if "Error" in response: if response["Error"] == "cancel": raise Canceled("canceled") return @@ -129,7 +129,7 @@ class Agent(dbus.service.Object): raise LaunchBrowser("launch browser") return - print "returning (%s)" % (response) + print("returning (%s)" % (response)) return response @@ -137,12 +137,12 @@ class Agent(dbus.service.Object): in_signature='os', out_signature='') def RequestBrowser(self, path, url): - print "RequestBrowser (%s,%s)" % (path, url) + print("RequestBrowser (%s,%s)" % (path, url)) - print "Please login through the given url in a browser" - print "Then press enter to accept or some text to cancel" + print("Please login through the given url in a browser") + print("Then press enter to accept or some text to cancel") - args = raw_input('> ') + args = input('> ') if len(args) > 0: raise Canceled("canceled") @@ -153,8 +153,8 @@ class Agent(dbus.service.Object): in_signature='os', out_signature='') def ReportError(self, path, error): - print "ReportError %s, %s" % (path, error) - retry = raw_input("Retry service (yes/no): ") + print("ReportError %s, %s" % (path, error)) + retry = input("Retry service (yes/no): ") if (retry == "yes"): class Retry(dbus.DBusException): _dbus_error_name = "net.connman.Agent.Error.Retry" @@ -167,7 +167,7 @@ class Agent(dbus.service.Object): @dbus.service.method("net.connman.Agent", in_signature='', out_signature='') def Cancel(self): - print "Cancel" + print("Cancel") class VpnAgent(dbus.service.Object): name = None @@ -185,8 +185,8 @@ class VpnAgent(dbus.service.Object): response = {} if not self.cookie: - print "VPN credentials requested, type cancel to cancel" - args = raw_input('Answer: ') + print("VPN credentials requested, type cancel to cancel") + args = input('Answer: ') for arg in args.split(): if arg.startswith("cancel"): @@ -204,8 +204,8 @@ class VpnAgent(dbus.service.Object): response = {} if not self.username and not self.password: - print "User login requested, type cancel to cancel" - args = raw_input('Answer: ') + print("User login requested, type cancel to cancel") + args = input('Answer: ') for arg in args.split(): if arg.startswith("cancel"): @@ -228,21 +228,21 @@ class VpnAgent(dbus.service.Object): in_signature='oa{sv}', out_signature='a{sv}') def RequestInput(self, path, fields): - print "RequestInput (%s,%s)" % (path, fields) + print("RequestInput (%s,%s)" % (path, fields)) response = {} - if fields.has_key("OpenConnect.Cookie"): + if "OpenConnect.Cookie" in fields: response.update(self.input_cookie()) - if fields.has_key("Username") or fields.has_key("Password"): + if "Username" in fields or "Password" in fields: response.update(self.input_username()) - if response.has_key("Error"): + if "Error" in response: if response["Error"] == "cancel": raise Canceled("canceled") return - print "returning (%s)" % (response) + print("returning (%s)" % (response)) return response @@ -250,8 +250,8 @@ class VpnAgent(dbus.service.Object): in_signature='os', out_signature='') def ReportError(self, path, error): - print "ReportError %s, %s" % (path, error) - retry = raw_input("Retry service (yes/no): ") + print("ReportError %s, %s" % (path, error)) + retry = input("Retry service (yes/no): ") if (retry == "yes"): class Retry(dbus.DBusException): _dbus_error_name = "net.connman.vpn.Agent.Error.Retry" @@ -264,7 +264,7 @@ class VpnAgent(dbus.service.Object): @dbus.service.method("net.connman.vpn.Agent", in_signature='', out_signature='') def Cancel(self): - print "Cancel" + print("Cancel") def vpnNameOwnerChanged(proxy): if proxy: @@ -276,22 +276,22 @@ def vpnNameOwnerChanged(proxy): 'net.connman.vpn.Manager') vpn_manager.RegisterAgent(path) except: - print "vpn agent is not registered" + print("vpn agent is not registered") else: print("vpnd is disconnected from system bus") vpn_manager = None def print_usage(): - print "Usage:" - print "For hidden service:" - print "%s Name= [SSID=]" % (sys.argv[0]) - print "For EAP/WPA input:" - print "%s Identity= Passphrase= WPS=" % (sys.argv[0]) - print "For WISPr login, L2TP or PPTP input:" - print "%s Username= Password=" % (sys.argv[0]) - print "For OpenConnect input:" - print "%s Cookie=" % (sys.argv[0]) - print "Help: %s help" % (sys.argv[0]) + print("Usage:") + print("For hidden service:") + print("%s Name= [SSID=]" % (sys.argv[0])) + print("For EAP/WPA input:") + print("%s Identity= Passphrase= WPS=" % (sys.argv[0])) + print("For WISPr login, L2TP or PPTP input:") + print("%s Username= Password=" % (sys.argv[0])) + print("For OpenConnect input:") + print("%s Cookie=" % (sys.argv[0])) + print("Help: %s help" % (sys.argv[0])) sys.exit(1) if __name__ == '__main__': @@ -314,7 +314,7 @@ if __name__ == '__main__': vpn_object = VpnAgent(bus, vpn_path) except: vpn_manager = None - print "net.connman.vpn is not present" + print("net.connman.vpn is not present") if len(sys.argv) >= 2: for arg in sys.argv[1:]: @@ -342,7 +342,7 @@ if __name__ == '__main__': try: manager.RegisterAgent(path) except: - print "Cannot register connman agent." + print("Cannot register connman agent.") if vpn_manager != None: try: diff --git a/test/test-clock b/test/test-clock index e9b76fc..c086ffc 100755 --- a/test/test-clock +++ b/test/test-clock @@ -9,11 +9,11 @@ clock = dbus.Interface(bus.get_object("net.connman", "/"), properties = clock.GetProperties() -for key in properties.keys(): +for key in list(properties.keys()): if key in ["Timeservers"]: list = "" for val in properties[key]: list = list + val + " " - print "%s = [ %s]" % (key, list) + print("%s = [ %s]" % (key, list)) else: - print "%s = %s" % (key, properties[key]) + print("%s = %s" % (key, properties[key])) diff --git a/test/test-compat b/test/test-compat index cd1ca7a..c9cdbab 100755 --- a/test/test-compat +++ b/test/test-compat @@ -12,4 +12,4 @@ states = [ "unknown", "asleep", "connecting", "connected", "disconnected" ] state = manager.state() -print "System is %s" % (states[state]) +print("System is %s" % (states[state])) diff --git a/test/test-connman b/test/test-connman index d047c86..45a18d9 100755 --- a/test/test-connman +++ b/test/test-connman @@ -9,19 +9,19 @@ manager = dbus.Interface(bus.get_object("net.connman", "/"), "net.connman.Manager") if len(sys.argv) < 2: - print "Usage: %s " % (sys.argv[0]) - print "" - print " state" - print " services" - print " autoconnect [autoconnect]" - print " connect " - print " disconnect " - print " remove " - print "" - print " scan " - print " enable " - print " disable " - print " offlinemode [on|off]" + print("Usage: %s " % (sys.argv[0])) + print("") + print(" state") + print(" services") + print(" autoconnect [autoconnect]") + print(" connect ") + print(" disconnect ") + print(" remove ") + print("") + print(" scan ") + print(" enable ") + print(" disable ") + print(" offlinemode [on|off]") sys.exit(1) def print_services(services): @@ -45,25 +45,25 @@ def print_services(services): else: favorite = " " - if "Name" in properties.keys(): + if "Name" in list(properties.keys()): name = properties["Name"] else: name = "{" + properties["Type"] + "}" - print "%s%s%s %-26s { %s }" % (favorite, autoconnect, state, - name, identifier) + print("%s%s%s %-26s { %s }" % (favorite, autoconnect, state, + name, identifier)) if sys.argv[1] == "state": properties = manager.GetProperties() - print "System is %s" % (properties["State"]) + print("System is %s" % (properties["State"])) elif sys.argv[1] in ["services", "list", "show"]: print_services(manager.GetServices()) elif sys.argv[1] in ["autoconnect", "autoconn"]: if (len(sys.argv) < 3): - print "Need at least service parameter" + print("Need at least service parameter") sys.exit(1) path = "/net/connman/service/" + sys.argv[2] @@ -77,25 +77,25 @@ elif sys.argv[1] in ["autoconnect", "autoconn"]: service.SetProperty("AutoConnect", autoconnect); - print "Auto connect %s for %s" % (autoconnect, sys.argv[2]) + print("Auto connect %s for %s" % (autoconnect, sys.argv[2])) else: properties = service.GetProperties() - if "Name" in properties.keys(): + if "Name" in list(properties.keys()): name = properties["Name"] else: name = "{" + properties["Type"] + "}" - if "AutoConnect" in properties.keys(): + if "AutoConnect" in list(properties.keys()): autoconnect = properties["AutoConnect"] else: autoconnect = dbus.Boolean(0) - print "Auto connect %s for %s" % (autoconnect, name) + print("Auto connect %s for %s" % (autoconnect, name)) elif sys.argv[1] in ["connect", "conn"]: if (len(sys.argv) < 3): - print "Need at least service parameter" + print("Need at least service parameter") sys.exit(1) path = "/net/connman/service/" + sys.argv[2] @@ -105,12 +105,12 @@ elif sys.argv[1] in ["connect", "conn"]: try: service.Connect(timeout=60000) - except dbus.DBusException, error: - print "%s: %s" % (error._dbus_error_name, error.message) + except dbus.DBusException as error: + print("%s: %s" % (error._dbus_error_name, error.message)) elif sys.argv[1] in ["disconnect", "disc"]: if (len(sys.argv) < 3): - print "Need at least service parameter" + print("Need at least service parameter") sys.exit(1) path = "/net/connman/service/" + sys.argv[2] @@ -120,12 +120,12 @@ elif sys.argv[1] in ["disconnect", "disc"]: try: service.Disconnect() - except dbus.DBusException, error: - print "%s: %s" % (error._dbus_error_name, error.message) + except dbus.DBusException as error: + print("%s: %s" % (error._dbus_error_name, error.message)) elif sys.argv[1] in ["remove"]: if (len(sys.argv) < 3): - print "Need at least service parameter" + print("Need at least service parameter") sys.exit(1) path = "/net/connman/service/" + sys.argv[2] @@ -136,13 +136,13 @@ elif sys.argv[1] in ["remove"]: properties = service.GetProperties() if properties["Favorite"] == dbus.Boolean(0): - print "Only favorite services can be removed" + print("Only favorite services can be removed") sys.exit(1) try: service.Remove() - except dbus.DBusException, error: - print "%s: %s" % (error._dbus_error_name, error.message) + except dbus.DBusException as error: + print("%s: %s" % (error._dbus_error_name, error.message)) elif sys.argv[1] == "scan": if len(sys.argv) == 3: @@ -151,7 +151,7 @@ elif sys.argv[1] == "scan": "net.connman.Technology") technology.Scan() else: - print "'%s' takes two arguments" % sys.argv[1] + print("'%s' takes two arguments" % sys.argv[1]) elif sys.argv[1] == "enable": if len(sys.argv) == 3: @@ -160,7 +160,7 @@ elif sys.argv[1] == "enable": "net.connman.Technology") technology.SetProperty("Powered", True) else: - print "'%s' takes two arguments" % sys.argv[1] + print("'%s' takes two arguments" % sys.argv[1]) elif sys.argv[1] == "disable": if len(sys.argv) == 3: @@ -169,7 +169,7 @@ elif sys.argv[1] == "disable": "net.connman.Technology") technology.SetProperty("Powered", False) else: - print "'%s' takes two arguments" % sys.argv[1] + print("'%s' takes two arguments" % sys.argv[1]) elif sys.argv[1] in ["offlinemode", "flightmode"]: @@ -179,15 +179,15 @@ elif sys.argv[1] in ["offlinemode", "flightmode"]: elif sys.argv[2] == "off" or sys.argv[2] == "0" or sys.argv[2] == "no": active = dbus.Boolean(0) else: - print "Use either 'on', '1', 'yes', 'off', '0' or 'no'" + print("Use either 'on', '1', 'yes', 'off', '0' or 'no'") exit() manager.SetProperty("OfflineMode", active) elif len(sys.argv) == 2: properties = manager.GetProperties() - print "Offline mode is %s" % (properties["OfflineMode"]) + print("Offline mode is %s" % (properties["OfflineMode"])) else: - print "'%s' takes max. two arguments" % sys.argv[1] + print("'%s' takes max. two arguments" % sys.argv[1]) else: - print "Unknown command" + print("Unknown command") diff --git a/test/test-counter b/test/test-counter index ce83580..c09aabc 100755 --- a/test/test-counter +++ b/test/test-counter @@ -24,7 +24,7 @@ def make_bytes_readable(bytes): return '' def print_stats(stats): - keys = stats.keys() + keys = list(stats.keys()) keys.sort() for key in keys: @@ -36,7 +36,7 @@ def print_stats(stats): if hstr: str = "%s (%s)" % (str, hstr) - print str + print(str) class Counter(dbus.service.Object): @dbus.service.method("net.connman.Counter", @@ -48,13 +48,13 @@ class Counter(dbus.service.Object): @dbus.service.method("net.connman.Counter", in_signature='oa{sv}a{sv}', out_signature='') def Usage(self, path, home, roaming): - print "%s" % (path) + print("%s" % (path)) if len(home) > 0: - print " Home" + print(" Home") print_stats(home) if len(roaming) > 0: - print " Roaming" + print(" Roaming") print_stats(roaming) if __name__ == '__main__': diff --git a/test/test-manager b/test/test-manager index 2b4493c..2bc53ac 100755 --- a/test/test-manager +++ b/test/test-manager @@ -4,7 +4,7 @@ import dbus def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["PrefixLength"]: val += "%s" % (int(values[key])) @@ -30,23 +30,23 @@ manager = dbus.Interface(bus.get_object("net.connman", "/"), properties = manager.GetProperties() -for key in properties.keys(): +for key in list(properties.keys()): if key in ["OfflineMode", "SessionMode"]: - print "%s" % (key) + print("%s" % (key)) if properties[key] == dbus.Boolean(1): - print " true" + print(" true") else: - print " false" + print(" false") else: - print "%s" % (key) - print " %s" % (properties[key]) + print("%s" % (key)) + print(" %s" % (properties[key])) print ("Services") services = manager.GetServices() for (path, properties) in services: - print " %s" % (path) - for key in properties.keys(): + print(" %s" % (path)) + for key in list(properties.keys()): if key in ["Available", "Remember", "Default", "Favorite", "Immutable", "AutoConnect", "LoginRequired", @@ -73,14 +73,14 @@ for (path, properties) in services: else: val = str(properties[key]) - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) print ("Technologies") technologies = manager.GetTechnologies() for (path, properties) in technologies: - print " %s" % (path) - for key in properties.keys(): + print(" %s" % (path)) + for key in list(properties.keys()): if key in ["Connected", "Powered", "Tethering"]: if properties[key] == dbus.Boolean(1): @@ -90,4 +90,4 @@ for (path, properties) in technologies: else: val = properties[key] - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) diff --git a/test/test-new-supplicant b/test/test-new-supplicant index be230dc..face130 100755 --- a/test/test-new-supplicant +++ b/test/test-new-supplicant @@ -11,4 +11,4 @@ bus = dbus.SystemBus() dummy = dbus.Interface(bus.get_object(WPA_NAME, WPA_PATH), 'org.freedesktop.DBus.Introspectable') -print dummy.Introspect() +print(dummy.Introspect()) diff --git a/test/test-session b/test/test-session index 2d82fb6..e45d22b 100755 --- a/test/test-session +++ b/test/test-session @@ -21,7 +21,7 @@ def extract_list(list): def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["PrefixLength"]: val += "%s" % (int(values[key])) @@ -41,26 +41,26 @@ class Notification(dbus.service.Object): @dbus.service.method("net.connman.Notification", in_signature='', out_signature='') def Release(self): - print "Release %s" % (self._object_path) + print("Release %s" % (self._object_path)) session_name = self._object_path.split('/')[-1] self.app.release(session_name) @dbus.service.method("net.connman.Notification", in_signature='a{sv}', out_signature='') def Update(self, settings): - print "Update called at %s" % (self._object_path) + print("Update called at %s" % (self._object_path)) try: - for key in settings.keys(): + for key in list(settings.keys()): if key in ["IPv4", "IPv6"]: val = extract_values(settings[key]) elif key in ["AllowedBearers"]: val = extract_list(settings[key]) else: val = settings[key] - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) except: - print "Exception:" + print("Exception:") traceback.print_exc() class SessionApplication(dbus.service.Object): @@ -80,15 +80,15 @@ class SessionApplication(dbus.service.Object): def connman_name_owner_changed(self, proxy): try: if proxy: - print "connman appeared on D-Bus ", str(proxy) + print("connman appeared on D-Bus ", str(proxy)) bus = dbus.SystemBus() self.manager = dbus.Interface(bus.get_object("net.connman", "/"), "net.connman.Manager") else: - print "connman disappeared on D-Bus" + print("connman disappeared on D-Bus") self.manager = None - for s in self.sessions.keys(): + for s in list(self.sessions.keys()): self.sessions[s]['notify'].remove_from_connection() self.sessions[s]['notify'] = None @@ -127,18 +127,18 @@ class SessionApplication(dbus.service.Object): return value def find_session(self, session_name): - if not session_name in self.sessions.keys(): + if not session_name in list(self.sessions.keys()): return None return self.sessions[session_name] @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def CreateSession(self, session_name): - print "Create session" + print("Create session") s = self.find_session(session_name) if s and s['session'] : - print "Session %s already created-> drop reqest" % (session_name) + print("Session %s already created-> drop request" % (session_name)) return try: @@ -149,29 +149,29 @@ class SessionApplication(dbus.service.Object): s['notify_path'] = self._object_path + "/" + session_name s['notify'] = Notification(bus, self, s['notify_path']) s['notify'].add_to_connection(bus, s['notify_path']) - if not 'settings' in s.keys(): + if not 'settings' in list(s.keys()): s['settings'] = {}; s['session_path'] = self.manager.CreateSession(s['settings'], s['notify_path']) - print "notify path %s" % (s['notify_path']) - print "session path %s" % (s['session_path']) + print("notify path %s" % (s['notify_path'])) + print("session path %s" % (s['session_path'])) s['session'] = dbus.Interface(bus.get_object("net.connman", s['session_path']), "net.connman.Session") self.sessions[session_name] = s - except dbus.DBusException, e: + except dbus.DBusException as e: if e.get_dbus_name() in ['net.connman.Error.Failed']: - print e.get_dbus_message() + print(e.get_dbus_message()) return traceback.print_exc() @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def DestroySession(self, session_name): - print "Destroy session" + print("Destroy session") s = self.find_session(session_name) if s == None or s['session'] == None: - print "The session is not running -> drop request" + print("The session is not running -> drop request") return try: @@ -182,92 +182,92 @@ class SessionApplication(dbus.service.Object): @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def Connect(self, session_name): - print "Connect session" + print("Connect session") s = self.find_session(session_name) if s == None or s['session'] == None: - print "The session is not running -> drop request" + print("The session is not running -> drop request") return try: s['session'].Connect() - except dbus.DBusException, e: + except dbus.DBusException as e: if e.get_dbus_name() in ['net.connman.Error.Failed']: - print e.get_dbus_message() + print(e.get_dbus_message()) return traceback.print_exc() @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def Disconnect(self, session_name): - print "Disconnect session" + print("Disconnect session") s = self.find_session(session_name) if s == None or s['session'] == None: - print "The session is not running -> drop request" + print("The session is not running -> drop request") return try: s['session'].Disconnect() - except dbus.DBusException, e: + except dbus.DBusException as e: if e.get_dbus_name() in ['net.connman.Error.Failed']: - print e.get_dbus_message() + print(e.get_dbus_message()) return traceback.print_exc() @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def Change(self, session_name, key, value): - print "Update session settings" + print("Update session settings") s = self.find_session(session_name) if s == None or s['session'] == None: - print "The session is not running -> drop request" + print("The session is not running -> drop request") return try: val = self.type_convert(key, value) s['session'].Change(key, val) - except dbus.DBusException, e: + except dbus.DBusException as e: if e.get_dbus_name() in ['net.connman.Error.Failed']: - print e.get_dbus_message() + print(e.get_dbus_message()) return traceback.print_exc() @dbus.service.method("com.example.TestSession", in_signature='', out_signature='') def Configure(self, session_name, key, value): - print "Configure session settings" + print("Configure session settings") s = self.find_session(session_name) if s == None: s = {} s['notify_path'] = None s['notify'] = None - if not 'settings' in s.keys(): + if not 'settings' in list(s.keys()): s['settings'] = {}; s['session_path'] = None s['session'] = None self.sessions[session_name] = s if s and s['session']: - print "The session is running, use change -> drop request" + print("The session is running, use change -> drop request") return val = self.type_convert(key, value) s['settings'][key] = val def main(): if len(sys.argv) < 2: - print "Usage: %s " % (sys.argv[0]) - print "" - print " enable" - print " disable" - print " create " - print " destroy " - print " connect " - print " disconnect " - print " change " - print " configure " - print "" - print " run " + print("Usage: %s " % (sys.argv[0])) + print("") + print(" enable") + print(" disable") + print(" create ") + print(" destroy ") + print(" connect ") + print(" disconnect ") + print(" change ") + print(" configure ") + print("") + print(" run ") sys.exit(1) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) @@ -287,7 +287,7 @@ def main(): return if (len(sys.argv) < 3): - print "Need test application path" + print("Need test application path") sys.exit(1) app_path = sys.argv[2] @@ -321,20 +321,20 @@ def main(): elif sys.argv[1] == "change": if len(sys.argv) < 5: - print "Arguments missing" + print("Arguments missing") sys.exit(1) app.Change(sys.argv[3], sys.argv[4], sys.argv[5:]) elif sys.argv[1] == "configure": if len(sys.argv) < 5: - print "Arguments missing" + print("Arguments missing") sys.exit(1) app.Configure(sys.argv[3], sys.argv[4], sys.argv[5:]) else: - print "Unknown command '%s'" % sys.argv[1] + print("Unknown command '%s'" % sys.argv[1]) sys.exit(1) if __name__ == '__main__': diff --git a/test/vpn-connect b/test/vpn-connect index 0f8636d..29753dc 100755 --- a/test/vpn-connect +++ b/test/vpn-connect @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 2): - print "Usage: %s " % (sys.argv[0]) + print("Usage: %s " % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -16,7 +16,7 @@ connections = manager.GetConnections() path = "/net/connman/vpn/connection/" + sys.argv[1] -print "Attempting to connect VPN %s" % (path) +print("Attempting to connect VPN %s" % (path)) connection = dbus.Interface(bus.get_object("net.connman.vpn", path), "net.connman.vpn.Connection") diff --git a/test/vpn-disconnect b/test/vpn-disconnect index d7a49ba..dccad2c 100755 --- a/test/vpn-disconnect +++ b/test/vpn-disconnect @@ -4,7 +4,7 @@ import sys import dbus if (len(sys.argv) < 1): - print "Usage: %s " % (sys.argv[0]) + print("Usage: %s " % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -16,7 +16,7 @@ connections = manager.GetConnections() path = "/net/connman/vpn/connection/" + sys.argv[1] -print "Attempting to disconnect VPN %s" % (path) +print("Attempting to disconnect VPN %s" % (path)) connection = dbus.Interface(bus.get_object("net.connman.vpn", path), "net.connman.vpn.Connection") diff --git a/test/vpn-get b/test/vpn-get index f1f760c..c9c5a70 100755 --- a/test/vpn-get +++ b/test/vpn-get @@ -4,7 +4,7 @@ import dbus def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["Servers", "Excludes"]: val += extract_list(values[key]) @@ -34,15 +34,15 @@ for entry in manager.GetConnections(): path = entry[0] properties = entry[1] - print "[ %s ]" % (path) + print("[ %s ]" % (path)) - for key in properties.keys(): + for key in list(properties.keys()): if key in ["IPv4", "IPv6" ]: val = extract_values(properties[key]) elif key in ["Nameservers","ServerRoutes","UserRoutes"]: val = extract_list(properties[key]) else: val = str(properties[key]) - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) - print + print() diff --git a/test/vpn-property b/test/vpn-property index d05f4c7..feddb5f 100755 --- a/test/vpn-property +++ b/test/vpn-property @@ -8,7 +8,7 @@ def make_variant(string): def extract_values(values): val = "{" - for key in values.keys(): + for key in list(values.keys()): val += " " + key + "=" if key in ["Servers", "Excludes"]: val += extract_list(values[key]) @@ -32,7 +32,7 @@ def extract_list(list): argc = len(sys.argv) if (argc < 2): - print "Usage: %s [] []" % (sys.argv[0]) + print("Usage: %s [] []" % (sys.argv[0])) sys.exit(1) bus = dbus.SystemBus() @@ -44,29 +44,29 @@ connections = manager.GetConnections() path = "/net/connman/vpn/connection/" + sys.argv[1] -print "Attempting to connect VPN %s" % (path) +print("Attempting to connect VPN %s" % (path)) connection = dbus.Interface(bus.get_object("net.connman.vpn", path), "net.connman.vpn.Connection") if (argc < 3): properties = connection.GetProperties() - for key in properties.keys(): + for key in list(properties.keys()): if key in ["IPv4", "IPv6" ]: val = extract_values(properties[key]) elif key in ["Nameservers","ServerRoutes","UserRoutes"]: val = extract_list(properties[key]) else: val = str(properties[key]) - print " %s = %s" % (key, val) + print(" %s = %s" % (key, val)) elif (argc < 4): try: connection.ClearProperty(sys.argv[2]) - except dbus.DBusException, error: - print "%s: %s" % (error._dbus_error_name, error.message) + except dbus.DBusException as error: + print("%s: %s" % (error._dbus_error_name, error.message)) else: try: connection.SetProperty(sys.argv[2], sys.argv[3]) - except dbus.DBusException, error: - print "%s: %s" % (error._dbus_error_name, error.message) + except dbus.DBusException as error: + print("%s: %s" % (error._dbus_error_name, error.message)) diff --git a/tools/netlink-test.c b/tools/netlink-test.c deleted file mode 100644 index 221e349..0000000 --- a/tools/netlink-test.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * - * Connection Manager - * - * Copyright (C) 2013-2014 BMW Car IT GmbH. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "../src/shared/netlink.h" - -#define NFGEN_DATA(nlh) ((void *)((char *)(nlh) + \ - NLMSG_ALIGN(sizeof(struct nfgenmsg)))) -#define NLA_DATA(nla) ((void *)((char*)(nla) + NLA_HDRLEN)) -#define NLA_OK(nla,len) ((len) >= (int)sizeof(struct nlattr) && \ - (nla)->nla_len >= sizeof(struct nlattr) && \ - (nla)->nla_len <= (len)) -#define NLA_NEXT(nla,attrlen) ((attrlen) -= NLA_ALIGN((nla)->nla_len), \ - (struct nlattr*)(((char*)(nla)) + \ - NLA_ALIGN((nla)->nla_len))) - -static GMainLoop *mainloop; - -static void do_debug(const char *str, void *user_data) -{ - const char *prefix = user_data; - - printf("%s%s\n", prefix, str); -} - -static void getlink_callback(unsigned int error, uint16_t type, const void *data, - uint32_t len, void *user_data) -{ - const struct ifinfomsg *ifi = data; - struct rtattr *rta; - int bytes; - char ifname[IF_NAMESIZE]; - uint32_t index, flags; - - g_assert_cmpuint(error, ==, 0); - - bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg)); - - memset(ifname, 0, sizeof(ifname)); - - index = ifi->ifi_index; - flags = ifi->ifi_flags; - - for (rta = IFLA_RTA(ifi); RTA_OK(rta, bytes); - rta = RTA_NEXT(rta, bytes)) { - switch (rta->rta_type) { - case IFLA_IFNAME: - if (RTA_PAYLOAD(rta) <= IF_NAMESIZE) - strcpy(ifname, RTA_DATA(rta)); - break; - } - } - - printf("index=%d flags=0x%08x name=%s\n", index, flags, ifname); - - g_main_loop_quit(mainloop); -} - -static void test_case_1(void) -{ - struct netlink_info *netlink; - struct ifinfomsg msg; - - netlink = netlink_new(NETLINK_ROUTE); - - printf("\n"); - netlink_set_debug(netlink, do_debug, "[NETLINK] ", NULL); - - memset(&msg, 0, sizeof(msg)); - - netlink_send(netlink, RTM_GETLINK, NLM_F_DUMP, &msg, sizeof(msg), - getlink_callback, NULL, NULL); - - mainloop = g_main_loop_new(NULL, FALSE); - g_main_loop_run(mainloop); - g_main_loop_unref(mainloop); - - netlink_destroy(netlink); -} - -int main(int argc, char *argv[]) -{ - g_test_init(&argc, &argv, NULL); - - g_test_add_func("/netlink/Test case 1", test_case_1); - - return g_test_run(); -} diff --git a/tools/stats-tool.c b/tools/stats-tool.c index 5695048..105dc49 100644 --- a/tools/stats-tool.c +++ b/tools/stats-tool.c @@ -108,12 +108,10 @@ static char *option_last_file_name = NULL; static bool parse_start_ts(const char *key, const char *value, gpointer user_data, GError **error) { - GTimeVal time_val; + struct tm tm; - if (!g_time_val_from_iso8601(value, &time_val)) - return false; - - option_start_ts = time_val.tv_sec; + strptime(value, "%FT%TZ", &tm); + option_start_ts = mktime(&tm); return true; } diff --git a/vpn/connman-vpn.service.in b/vpn/connman-vpn.service.in index e98fb71..dd15bab 100644 --- a/vpn/connman-vpn.service.in +++ b/vpn/connman-vpn.service.in @@ -6,7 +6,7 @@ Type=dbus BusName=net.connman.vpn ExecStart=@sbindir@/connman-vpnd -n StandardOutput=null -CapabilityBoundingSet=CAP_KILL CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW +CapabilityBoundingSet=CAP_KILL CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID ProtectHome=read-only ProtectSystem=full diff --git a/vpn/main.c b/vpn/main.c index ee88aac..133acd2 100644 --- a/vpn/main.c +++ b/vpn/main.c @@ -44,71 +44,10 @@ #define CONFIGMAINFILE CONFIGDIR "/connman-vpn.conf" -#define DEFAULT_INPUT_REQUEST_TIMEOUT 300 * 1000 - static GMainLoop *main_loop = NULL; static unsigned int __terminated = 0; -static struct { - unsigned int timeout_inputreq; -} connman_vpn_settings = { - .timeout_inputreq = DEFAULT_INPUT_REQUEST_TIMEOUT, -}; - -static GKeyFile *load_config(const char *file) -{ - GError *err = NULL; - GKeyFile *keyfile; - - keyfile = g_key_file_new(); - - g_key_file_set_list_separator(keyfile, ','); - - if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { - if (err->code != G_FILE_ERROR_NOENT) { - connman_error("Parsing %s failed: %s", file, - err->message); - } - - g_error_free(err); - g_key_file_free(keyfile); - return NULL; - } - - return keyfile; -} - -static void parse_config(GKeyFile *config, const char *file) -{ - GError *error = NULL; - int timeout; - - if (!config) - return; - - DBG("parsing %s", file); - - timeout = g_key_file_get_integer(config, "General", - "InputRequestTimeout", &error); - if (!error && timeout >= 0) - connman_vpn_settings.timeout_inputreq = timeout * 1000; - - g_clear_error(&error); -} - -static int config_init(const char *file) -{ - GKeyFile *config; - - config = load_config(file); - parse_config(config, file); - if (config) - g_key_file_free(config); - - return 0; -} - static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, gpointer user_data) { @@ -230,7 +169,7 @@ static GOptionEntry options[] = { */ unsigned int connman_timeout_input_request(void) { - return connman_vpn_settings.timeout_inputreq; + return __vpn_settings_get_timeout_inputreq(); } int main(int argc, char *argv[]) @@ -314,9 +253,9 @@ int main(int argc, char *argv[]) __connman_dbus_init(conn); if (!option_config) - config_init(CONFIGMAINFILE); + __vpn_settings_init(CONFIGMAINFILE); else - config_init(option_config); + __vpn_settings_init(option_config); __connman_inotify_init(); __connman_agent_init(); @@ -348,6 +287,7 @@ int main(int argc, char *argv[]) __connman_inotify_cleanup(); __connman_dbus_cleanup(); __connman_log_cleanup(false); + __vpn_settings_cleanup(); dbus_connection_unref(conn); diff --git a/vpn/plugins/l2tp.c b/vpn/plugins/l2tp.c index 5d83eb8..48894aa 100644 --- a/vpn/plugins/l2tp.c +++ b/vpn/plugins/l2tp.c @@ -65,6 +65,7 @@ enum { OPT_L2G = 2, OPT_L2 = 3, OPT_PPPD = 4, + OPT_L2LNS = 5, }; struct { @@ -83,7 +84,7 @@ struct { { "L2TP.DefaultRoute", "defaultroute", OPT_L2, NULL, OPT_STRING }, { "L2TP.FlowBit", "flow bit", OPT_L2, NULL, OPT_STRING }, { "L2TP.TunnelRWS", "tunnel rws", OPT_L2, NULL, OPT_STRING }, - { "L2TP.Exclusive", "exclusive", OPT_L2, NULL, OPT_STRING }, + { "L2TP.Exclusive", "exclusive", OPT_L2LNS, NULL, OPT_STRING }, { "L2TP.Autodial", "autodial", OPT_L2, "yes", OPT_STRING }, { "L2TP.Redial", "redial", OPT_L2, "yes", OPT_STRING }, { "L2TP.RedialTimeout", "redial timeout", OPT_L2, "10", OPT_STRING }, @@ -96,7 +97,7 @@ struct { { "L2TP.ForceUserSpace", "force userspace", OPT_L2G, NULL, OPT_STRING }, { "L2TP.ListenAddr", "listen-addr", OPT_L2G, NULL, OPT_STRING }, { "L2TP.Rand Source", "rand source", OPT_L2G, NULL, OPT_STRING }, - { "L2TP.IPsecSaref", "ipsec saref", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.IPsecSaref", "ipsec saref", OPT_L2G, "no", OPT_STRING }, { "L2TP.Port", "port", OPT_L2G, NULL, OPT_STRING }, { "PPPD.EchoFailure", "lcp-echo-failure", OPT_PPPD, "0", OPT_STRING }, { "PPPD.EchoInterval", "lcp-echo-interval", OPT_PPPD, "0", OPT_STRING }, @@ -178,7 +179,8 @@ static int l2tp_notify(DBusMessage *msg, struct vpn_provider *provider) DBG("authentication failure"); vpn_provider_set_string(provider, "L2TP.User", NULL); - vpn_provider_set_string(provider, "L2TP.Password", NULL); + vpn_provider_set_string_hide_value(provider, "L2TP.Password", + NULL); return VPN_STATE_AUTH_FAILURE; } @@ -454,6 +456,9 @@ static int l2tp_write_config(struct vpn_provider *provider, l2tp_write_option(fd, "[global]", NULL); l2tp_write_fields(provider, fd, OPT_L2G); + l2tp_write_option(fd, "[lns default]", NULL); + l2tp_write_fields(provider, fd, OPT_L2LNS); + l2tp_write_option(fd, "[lac l2tp]", NULL); option = vpn_provider_get_string(provider, "Host"); @@ -491,16 +496,28 @@ struct request_input_reply { static void request_input_reply(DBusMessage *reply, void *user_data) { struct request_input_reply *l2tp_reply = user_data; + struct l2tp_private_data *data; const char *error = NULL; char *username = NULL, *password = NULL; char *key; DBusMessageIter iter, dict; + int err; DBG("provider %p", l2tp_reply->provider); - if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { - if (reply) - error = dbus_message_get_error_name(reply); + if (!reply) + goto done; + + data = l2tp_reply->user_data; + + err = vpn_agent_check_and_process_reply_error(reply, + l2tp_reply->provider, data->task, data->cb, + data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + error = dbus_message_get_error_name(reply); goto done; } @@ -593,6 +610,9 @@ static int request_input(struct vpn_provider *provider, connman_dbus_dict_open(&iter, &dict); + if (vpn_provider_get_authentication_errors(provider)) + vpn_agent_append_auth_failure(&dict, provider, NULL); + vpn_agent_append_user_info(&dict, provider, "L2TP.User"); vpn_agent_append_host_and_name(&dict, provider); @@ -633,7 +653,7 @@ static int run_connect(struct vpn_provider *provider, int l2tp_fd, pppd_fd; int err; - if (!username || !password) { + if (!username || !*username || !password || !*password) { DBG("Cannot connect username %s password %p", username, password); err = -EINVAL; @@ -704,7 +724,7 @@ static void request_input_cb(struct vpn_provider *provider, { struct l2tp_private_data *data = user_data; - if (!username || !password) + if (!username || !*username || !password || !*password) DBG("Requesting username %s or password failed, error %s", username, error); else if (error) @@ -739,7 +759,7 @@ static int l2tp_connect(struct vpn_provider *provider, DBG("user %s password %p", username, password); - if (!username || !password) { + if (!username || !*username || !password || !*password) { struct l2tp_private_data *data; data = g_try_new0(struct l2tp_private_data, 1); @@ -783,7 +803,12 @@ static int l2tp_error_code(struct vpn_provider *provider, int exit_code) static void l2tp_disconnect(struct vpn_provider *provider) { - vpn_provider_set_string(provider, "L2TP.Password", NULL); + if (!provider) + return; + + vpn_provider_set_string_hide_value(provider, "L2TP.Password", NULL); + + connman_agent_cancel(provider); } static struct vpn_driver vpn_driver = { diff --git a/vpn/plugins/libwireguard.c b/vpn/plugins/libwireguard.c new file mode 100644 index 0000000..c1f55a7 --- /dev/null +++ b/vpn/plugins/libwireguard.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2008-2012 Pablo Neira Ayuso . + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "src/shared/mnlg.h" +#include "wireguard.h" + +/* wireguard.h netlink uapi: */ + +#define WG_GENL_NAME "wireguard" +#define WG_GENL_VERSION 1 + +enum wg_cmd { + WG_CMD_GET_DEVICE, + WG_CMD_SET_DEVICE, + __WG_CMD_MAX +}; + +enum wgdevice_flag { + WGDEVICE_F_REPLACE_PEERS = 1U << 0 +}; +enum wgdevice_attribute { + WGDEVICE_A_UNSPEC, + WGDEVICE_A_IFINDEX, + WGDEVICE_A_IFNAME, + WGDEVICE_A_PRIVATE_KEY, + WGDEVICE_A_PUBLIC_KEY, + WGDEVICE_A_FLAGS, + WGDEVICE_A_LISTEN_PORT, + WGDEVICE_A_FWMARK, + WGDEVICE_A_PEERS, + __WGDEVICE_A_LAST +}; + +enum wgpeer_flag { + WGPEER_F_REMOVE_ME = 1U << 0, + WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1 +}; +enum wgpeer_attribute { + WGPEER_A_UNSPEC, + WGPEER_A_PUBLIC_KEY, + WGPEER_A_PRESHARED_KEY, + WGPEER_A_FLAGS, + WGPEER_A_ENDPOINT, + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + WGPEER_A_LAST_HANDSHAKE_TIME, + WGPEER_A_RX_BYTES, + WGPEER_A_TX_BYTES, + WGPEER_A_ALLOWEDIPS, + WGPEER_A_PROTOCOL_VERSION, + __WGPEER_A_LAST +}; + +enum wgallowedip_attribute { + WGALLOWEDIP_A_UNSPEC, + WGALLOWEDIP_A_FAMILY, + WGALLOWEDIP_A_IPADDR, + WGALLOWEDIP_A_CIDR_MASK, + __WGALLOWEDIP_A_LAST +}; + +/* wireguard-specific parts: */ + +struct inflatable_buffer { + char *buffer; + char *next; + bool good; + size_t len; + size_t pos; +}; + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer) +{ + size_t len, expand_to; + char *new_buffer; + + if (!buffer->good || !buffer->next) { + free(buffer->next); + buffer->good = false; + return 0; + } + + len = strlen(buffer->next) + 1; + + if (len == 1) { + free(buffer->next); + buffer->good = false; + return 0; + } + + if (buffer->len - buffer->pos <= len) { + expand_to = max(buffer->len * 2, buffer->len + len + 1); + new_buffer = realloc(buffer->buffer, expand_to); + if (!new_buffer) { + free(buffer->next); + buffer->good = false; + return -errno; + } + memset(&new_buffer[buffer->len], 0, expand_to - buffer->len); + buffer->buffer = new_buffer; + buffer->len = expand_to; + } + memcpy(&buffer->buffer[buffer->pos], buffer->next, len); + free(buffer->next); + buffer->good = false; + buffer->pos += len; + return 0; +} + +static int parse_linkinfo(const struct nlattr *attr, void *data) +{ + struct inflatable_buffer *buffer = data; + + if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr))) + buffer->good = true; + return MNL_CB_OK; +} + +static int parse_infomsg(const struct nlattr *attr, void *data) +{ + struct inflatable_buffer *buffer = data; + + if (mnl_attr_get_type(attr) == IFLA_LINKINFO) + return mnl_attr_parse_nested(attr, parse_linkinfo, data); + else if (mnl_attr_get_type(attr) == IFLA_IFNAME) + buffer->next = strdup(mnl_attr_get_str(attr)); + return MNL_CB_OK; +} + +static int read_devices_cb(const struct nlmsghdr *nlh, void *data) +{ + struct inflatable_buffer *buffer = data; + int ret; + + buffer->good = false; + buffer->next = NULL; + ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, data); + if (ret != MNL_CB_OK) + return ret; + ret = add_next_to_inflatable_buffer(buffer); + if (ret < 0) + return ret; + if (nlh->nlmsg_type != NLMSG_DONE) + return MNL_CB_OK + 1; + return MNL_CB_OK; +} + +static int fetch_device_names(struct inflatable_buffer *buffer) +{ + struct mnl_socket *nl = NULL; + char *rtnl_buffer = NULL; + size_t message_len; + unsigned int portid, seq; + ssize_t len; + int ret = 0; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + + ret = -ENOMEM; + rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1); + if (!rtnl_buffer) + goto cleanup; + + nl = mnl_socket_open(NETLINK_ROUTE); + if (!nl) { + ret = -errno; + goto cleanup; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + ret = -errno; + goto cleanup; + } + + seq = time(NULL); + portid = mnl_socket_get_portid(nl); + nlh = mnl_nlmsg_put_header(rtnl_buffer); + nlh->nlmsg_type = RTM_GETLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP; + nlh->nlmsg_seq = seq; + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + message_len = nlh->nlmsg_len; + + if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) { + ret = -errno; + goto cleanup; + } + +another: + if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) { + ret = -errno; + goto cleanup; + } + if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, buffer)) < 0) { + /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed + * during the dump. That's unfortunate, but is pretty common on busy + * systems that are adding and removing tunnels all the time. Rather + * than retrying, potentially indefinitely, we just work with the + * partial results. */ + if (errno != EINTR) { + ret = -errno; + goto cleanup; + } + } + if (len == MNL_CB_OK + 1) + goto another; + ret = 0; + +cleanup: + free(rtnl_buffer); + if (nl) + mnl_socket_close(nl); + return ret; +} + +static int add_del_iface(const char *ifname, bool add) +{ + struct mnl_socket *nl = NULL; + char *rtnl_buffer; + ssize_t len; + int ret; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + struct nlattr *nest; + + rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1); + if (!rtnl_buffer) { + ret = -ENOMEM; + goto cleanup; + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (!nl) { + ret = -errno; + goto cleanup; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + ret = -errno; + goto cleanup; + } + + nlh = mnl_nlmsg_put_header(rtnl_buffer); + nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0); + nlh->nlmsg_seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname); + nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); + mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME); + mnl_attr_nest_end(nlh, nest); + + if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) { + ret = -errno; + goto cleanup; + } + if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) { + ret = -errno; + goto cleanup; + } + if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) { + ret = -errno; + goto cleanup; + } + ret = 0; + +cleanup: + free(rtnl_buffer); + if (nl) + mnl_socket_close(nl); + return ret; +} + +int wg_set_device(wg_device *dev) +{ + int ret = 0; + wg_peer *peer = NULL; + wg_allowedip *allowedip = NULL; + struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest; + struct nlmsghdr *nlh; + struct mnlg_socket *nlg; + + nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); + if (!nlg) + return -errno; + +again: + nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK); + mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name); + + if (!peer) { + uint32_t flags = 0; + + if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) + mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key); + if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) + mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port); + if (dev->flags & WGDEVICE_HAS_FWMARK) + mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark); + if (dev->flags & WGDEVICE_REPLACE_PEERS) + flags |= WGDEVICE_F_REPLACE_PEERS; + if (flags) + mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags); + } + if (!dev->first_peer) + goto send; + peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL; + peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS); + for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) { + uint32_t flags = 0; + + peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0); + if (!peer_nest) + goto toobig_peers; + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key)) + goto toobig_peers; + if (peer->flags & WGPEER_REMOVE_ME) + flags |= WGPEER_F_REMOVE_ME; + if (!allowedip) { + if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS) + flags |= WGPEER_F_REPLACE_ALLOWEDIPS; + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) { + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key)) + goto toobig_peers; + } + if (peer->endpoint.addr.sa_family == AF_INET) { + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4)) + goto toobig_peers; + } else if (peer->endpoint.addr.sa_family == AF_INET6) { + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6)) + goto toobig_peers; + } + if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) { + if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval)) + goto toobig_peers; + } + } + if (flags) { + if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags)) + goto toobig_peers; + } + if (peer->first_allowedip) { + if (!allowedip) + allowedip = peer->first_allowedip; + allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS); + if (!allowedips_nest) + goto toobig_allowedips; + for (; allowedip; allowedip = allowedip->next_allowedip) { + allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0); + if (!allowedip_nest) + goto toobig_allowedips; + if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family)) + goto toobig_allowedips; + if (allowedip->family == AF_INET) { + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4)) + goto toobig_allowedips; + } else if (allowedip->family == AF_INET6) { + if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6)) + goto toobig_allowedips; + } + if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr)) + goto toobig_allowedips; + mnl_attr_nest_end(nlh, allowedip_nest); + allowedip_nest = NULL; + } + mnl_attr_nest_end(nlh, allowedips_nest); + allowedips_nest = NULL; + } + + mnl_attr_nest_end(nlh, peer_nest); + peer_nest = NULL; + } + mnl_attr_nest_end(nlh, peers_nest); + peers_nest = NULL; + goto send; +toobig_allowedips: + if (allowedip_nest) + mnl_attr_nest_cancel(nlh, allowedip_nest); + if (allowedips_nest) + mnl_attr_nest_end(nlh, allowedips_nest); + mnl_attr_nest_end(nlh, peer_nest); + mnl_attr_nest_end(nlh, peers_nest); + goto send; +toobig_peers: + if (peer_nest) + mnl_attr_nest_cancel(nlh, peer_nest); + mnl_attr_nest_end(nlh, peers_nest); + goto send; +send: + if (mnlg_socket_send(nlg, nlh) < 0) { + ret = -errno; + goto out; + } + errno = 0; + if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) { + ret = errno ? -errno : -EINVAL; + goto out; + } + if (peer) + goto again; + +out: + mnlg_socket_close(nlg); + errno = -ret; + return ret; +} + +static int parse_allowedip(const struct nlattr *attr, void *data) +{ + wg_allowedip *allowedip = data; + + switch (mnl_attr_get_type(attr)) { + case WGALLOWEDIP_A_UNSPEC: + break; + case WGALLOWEDIP_A_FAMILY: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + allowedip->family = mnl_attr_get_u16(attr); + break; + case WGALLOWEDIP_A_IPADDR: + if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4)) + memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4)); + else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6)) + memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6)); + break; + case WGALLOWEDIP_A_CIDR_MASK: + if (!mnl_attr_validate(attr, MNL_TYPE_U8)) + allowedip->cidr = mnl_attr_get_u8(attr); + break; + } + + return MNL_CB_OK; +} + +static int parse_allowedips(const struct nlattr *attr, void *data) +{ + wg_peer *peer = data; + wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip)); + int ret; + + if (!new_allowedip) + return MNL_CB_ERROR; + if (!peer->first_allowedip) + peer->first_allowedip = peer->last_allowedip = new_allowedip; + else { + peer->last_allowedip->next_allowedip = new_allowedip; + peer->last_allowedip = new_allowedip; + } + ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip); + if (!ret) + return ret; + if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) { + errno = EAFNOSUPPORT; + return MNL_CB_ERROR; + } + return MNL_CB_OK; +} + +bool wg_key_is_zero(const wg_key key) +{ + volatile uint8_t acc = 0; + unsigned int i; + + for (i = 0; i < sizeof(wg_key); ++i) { + acc |= key[i]; + __asm__ ("" : "=r" (acc) : "0" (acc)); + } + return 1 & ((acc - 1) >> 8); +} + +static int parse_peer(const struct nlattr *attr, void *data) +{ + wg_peer *peer = data; + + switch (mnl_attr_get_type(attr)) { + case WGPEER_A_UNSPEC: + break; + case WGPEER_A_PUBLIC_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) { + memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key)); + peer->flags |= WGPEER_HAS_PUBLIC_KEY; + } + break; + case WGPEER_A_PRESHARED_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) { + memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key)); + if (!wg_key_is_zero(peer->preshared_key)) + peer->flags |= WGPEER_HAS_PRESHARED_KEY; + } + break; + case WGPEER_A_ENDPOINT: { + struct sockaddr *addr; + + if (mnl_attr_get_payload_len(attr) < sizeof(*addr)) + break; + addr = mnl_attr_get_payload(attr); + if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4)) + memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4)); + else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6)) + memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6)); + break; + } + case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + peer->persistent_keepalive_interval = mnl_attr_get_u16(attr); + break; + case WGPEER_A_LAST_HANDSHAKE_TIME: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time)) + memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time)); + break; + case WGPEER_A_RX_BYTES: + if (!mnl_attr_validate(attr, MNL_TYPE_U64)) + peer->rx_bytes = mnl_attr_get_u64(attr); + break; + case WGPEER_A_TX_BYTES: + if (!mnl_attr_validate(attr, MNL_TYPE_U64)) + peer->tx_bytes = mnl_attr_get_u64(attr); + break; + case WGPEER_A_ALLOWEDIPS: + return mnl_attr_parse_nested(attr, parse_allowedips, peer); + } + + return MNL_CB_OK; +} + +static int parse_peers(const struct nlattr *attr, void *data) +{ + wg_device *device = data; + wg_peer *new_peer = calloc(1, sizeof(wg_peer)); + int ret; + + if (!new_peer) + return MNL_CB_ERROR; + if (!device->first_peer) + device->first_peer = device->last_peer = new_peer; + else { + device->last_peer->next_peer = new_peer; + device->last_peer = new_peer; + } + ret = mnl_attr_parse_nested(attr, parse_peer, new_peer); + if (!ret) + return ret; + if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) { + errno = ENXIO; + return MNL_CB_ERROR; + } + return MNL_CB_OK; +} + +static int parse_device(const struct nlattr *attr, void *data) +{ + wg_device *device = data; + + switch (mnl_attr_get_type(attr)) { + case WGDEVICE_A_UNSPEC: + break; + case WGDEVICE_A_IFINDEX: + if (!mnl_attr_validate(attr, MNL_TYPE_U32)) + device->ifindex = mnl_attr_get_u32(attr); + break; + case WGDEVICE_A_IFNAME: + if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) { + strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1); + device->name[sizeof(device->name) - 1] = '\0'; + } + break; + case WGDEVICE_A_PRIVATE_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) { + memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key)); + device->flags |= WGDEVICE_HAS_PRIVATE_KEY; + } + break; + case WGDEVICE_A_PUBLIC_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) { + memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key)); + device->flags |= WGDEVICE_HAS_PUBLIC_KEY; + } + break; + case WGDEVICE_A_LISTEN_PORT: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + device->listen_port = mnl_attr_get_u16(attr); + break; + case WGDEVICE_A_FWMARK: + if (!mnl_attr_validate(attr, MNL_TYPE_U32)) + device->fwmark = mnl_attr_get_u32(attr); + break; + case WGDEVICE_A_PEERS: + return mnl_attr_parse_nested(attr, parse_peers, device); + } + + return MNL_CB_OK; +} + +static int read_device_cb(const struct nlmsghdr *nlh, void *data) +{ + return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data); +} + +static void coalesce_peers(wg_device *device) +{ + wg_peer *old_next_peer, *peer = device->first_peer; + + while (peer && peer->next_peer) { + if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) { + peer = peer->next_peer; + continue; + } + if (!peer->first_allowedip) { + peer->first_allowedip = peer->next_peer->first_allowedip; + peer->last_allowedip = peer->next_peer->last_allowedip; + } else { + peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip; + peer->last_allowedip = peer->next_peer->last_allowedip; + } + old_next_peer = peer->next_peer; + peer->next_peer = old_next_peer->next_peer; + free(old_next_peer); + } +} + +int wg_get_device(wg_device **device, const char *device_name) +{ + int ret = 0; + struct nlmsghdr *nlh; + struct mnlg_socket *nlg; + +try_again: + *device = calloc(1, sizeof(wg_device)); + if (!*device) + return -errno; + + nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); + if (!nlg) { + wg_free_device(*device); + *device = NULL; + return -errno; + } + + nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name); + if (mnlg_socket_send(nlg, nlh) < 0) { + ret = -errno; + goto out; + } + errno = 0; + if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) { + ret = errno ? -errno : -EINVAL; + goto out; + } + coalesce_peers(*device); + +out: + if (nlg) + mnlg_socket_close(nlg); + if (ret) { + wg_free_device(*device); + if (ret == -EINTR) + goto try_again; + *device = NULL; + } + errno = -ret; + return ret; +} + +/* first\0second\0third\0forth\0last\0\0 */ +char *wg_list_device_names(void) +{ + struct inflatable_buffer buffer = { .len = MNL_SOCKET_BUFFER_SIZE }; + int ret; + + ret = -ENOMEM; + buffer.buffer = calloc(1, buffer.len); + if (!buffer.buffer) + goto err; + + ret = fetch_device_names(&buffer); +err: + errno = -ret; + if (errno) { + free(buffer.buffer); + return NULL; + } + return buffer.buffer; +} + +int wg_add_device(const char *device_name) +{ + return add_del_iface(device_name, true); +} + +int wg_del_device(const char *device_name) +{ + return add_del_iface(device_name, false); +} + +void wg_free_device(wg_device *dev) +{ + wg_peer *peer, *np; + wg_allowedip *allowedip, *na; + + if (!dev) + return; + for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) { + for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL) + free(allowedip); + free(peer); + } + free(dev); +} + +static void encode_base64(char dest[static 4], const uint8_t src[static 3]) +{ + const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 }; + unsigned int i; + + for (i = 0; i < 4; ++i) + dest[i] = input[i] + 'A' + + (((25 - input[i]) >> 8) & 6) + - (((51 - input[i]) >> 8) & 75) + - (((61 - input[i]) >> 8) & 15) + + (((62 - input[i]) >> 8) & 3); + +} + +void wg_key_to_base64(wg_key_b64_string base64, const wg_key key) +{ + unsigned int i; + + for (i = 0; i < 32 / 3; ++i) + encode_base64(&base64[i * 4], &key[i * 3]); + encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 }); + base64[sizeof(wg_key_b64_string) - 2] = '='; + base64[sizeof(wg_key_b64_string) - 1] = '\0'; +} + +static int decode_base64(const char src[static 4]) +{ + int val = 0; + unsigned int i; + + for (i = 0; i < 4; ++i) + val |= (-1 + + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64)) + + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70)) + + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) + + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) + + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64) + ) << (18 - 6 * i); + return val; +} + +int wg_key_from_base64(wg_key key, const wg_key_b64_string base64) +{ + unsigned int i; + int val; + volatile uint8_t ret = 0; + + if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') { + errno = EINVAL; + goto out; + } + + for (i = 0; i < 32 / 3; ++i) { + val = decode_base64(&base64[i * 4]); + ret |= (uint32_t)val >> 31; + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + key[i * 3 + 2] = val & 0xff; + } + val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' }); + ret |= ((uint32_t)val >> 31) | (val & 0xff); + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + errno = EINVAL & ~((ret - 1) >> 8); +out: + return -errno; +} + +typedef int64_t fe[16]; + +static __attribute__((noinline)) void memzero_explicit(void *s, size_t count) +{ + memset(s, 0, count); + __asm__ __volatile__("": :"r"(s) :"memory"); +} + +static void carry(fe o) +{ + int i; + + for (i = 0; i < 16; ++i) { + o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16); + o[i] &= 0xffff; + } +} + +static void cswap(fe p, fe q, int b) +{ + int i; + int64_t t, c = ~(b - 1); + + for (i = 0; i < 16; ++i) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + + memzero_explicit(&t, sizeof(t)); + memzero_explicit(&c, sizeof(c)); + memzero_explicit(&b, sizeof(b)); +} + +static void pack(uint8_t *o, const fe n) +{ + int i, j, b; + fe m, t; + + memcpy(t, n, sizeof(t)); + carry(t); + carry(t); + carry(t); + for (j = 0; j < 2; ++j) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; ++i) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + cswap(t, m, 1 - b); + } + for (i = 0; i < 16; ++i) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } + + memzero_explicit(m, sizeof(m)); + memzero_explicit(t, sizeof(t)); + memzero_explicit(&b, sizeof(b)); +} + +static void add(fe o, const fe a, const fe b) +{ + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] + b[i]; +} + +static void subtract(fe o, const fe a, const fe b) +{ + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] - b[i]; +} + +static void multmod(fe o, const fe a, const fe b) +{ + int i, j; + int64_t t[31] = { 0 }; + + for (i = 0; i < 16; ++i) { + for (j = 0; j < 16; ++j) + t[i + j] += a[i] * b[j]; + } + for (i = 0; i < 15; ++i) + t[i] += 38 * t[i + 16]; + memcpy(o, t, sizeof(fe)); + carry(o); + carry(o); + + memzero_explicit(t, sizeof(t)); +} + +static void invert(fe o, const fe i) +{ + fe c; + int a; + + memcpy(c, i, sizeof(c)); + for (a = 253; a >= 0; --a) { + multmod(c, c, c); + if (a != 2 && a != 4) + multmod(c, c, i); + } + memcpy(o, c, sizeof(fe)); + + memzero_explicit(c, sizeof(c)); +} + +static void clamp_key(uint8_t *z) +{ + z[31] = (z[31] & 127) | 64; + z[0] &= 248; +} + +void wg_generate_public_key(wg_key public_key, const wg_key private_key) +{ + int i, r; + uint8_t z[32]; + fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f; + + memcpy(z, private_key, sizeof(z)); + clamp_key(z); + + for (i = 254; i >= 0; --i) { + r = (z[i >> 3] >> (i & 7)) & 1; + cswap(a, b, r); + cswap(c, d, r); + add(e, a, c); + subtract(a, a, c); + add(c, b, d); + subtract(b, b, d); + multmod(d, e, e); + multmod(f, a, a); + multmod(a, c, a); + multmod(c, b, e); + add(e, a, c); + subtract(a, a, c); + multmod(b, a, a); + subtract(c, d, f); + multmod(a, c, (const fe){ 0xdb41, 1 }); + add(a, a, d); + multmod(c, c, a); + multmod(a, d, f); + multmod(d, b, (const fe){ 9 }); + multmod(b, e, e); + cswap(a, b, r); + cswap(c, d, r); + } + invert(c, c); + multmod(a, a, c); + pack(public_key, a); + + memzero_explicit(&r, sizeof(r)); + memzero_explicit(z, sizeof(z)); + memzero_explicit(a, sizeof(a)); + memzero_explicit(b, sizeof(b)); + memzero_explicit(c, sizeof(c)); + memzero_explicit(d, sizeof(d)); + memzero_explicit(e, sizeof(e)); + memzero_explicit(f, sizeof(f)); +} + +void wg_generate_private_key(wg_key private_key) +{ + wg_generate_preshared_key(private_key); + clamp_key(private_key); +} + +void wg_generate_preshared_key(wg_key preshared_key) +{ + ssize_t ret; + size_t i; + int fd; +#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) + if (!getentropy(preshared_key, sizeof(wg_key))) + return; +#endif +#if defined(__NR_getrandom) && defined(__linux__) + if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key)) + return; +#endif + fd = open("/dev/urandom", O_RDONLY); + assert(fd >= 0); + for (i = 0; i < sizeof(wg_key); i += ret) { + ret = read(fd, preshared_key + i, sizeof(wg_key) - i); + assert(ret > 0); + } + close(fd); +} diff --git a/vpn/plugins/openconnect.c b/vpn/plugins/openconnect.c index 8e74479..d600e61 100644 --- a/vpn/plugins/openconnect.c +++ b/vpn/plugins/openconnect.c @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2007-2013 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Jolla Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -47,25 +48,137 @@ #include "vpn.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) +#define OC_MAX_READBUF_LEN 128 + +enum opt_type { + OPT_STRING = 0, + OPT_BOOL = 1, +}; struct { - const char *cm_opt; - const char *oc_opt; - char has_value; + const char *cm_opt; + const char *oc_opt; + bool has_value; + bool enabled; // Use as task parameter + enum opt_type type; } oc_options[] = { - { "OpenConnect.NoCertCheck", "--no-cert-check", 0 }, + { "OpenConnect.AllowSelfSignedCert", NULL, 1, 0, OPT_BOOL}, + { "OpenConnect.AuthType", NULL, 1, 0, OPT_STRING}, + { "OpenConnect.CACert", "--cafile", 1, 1, OPT_STRING}, + { "OpenConnect.ClientCert", NULL, 1, 0, OPT_STRING}, + { "OpenConnect.DisableIPv6", "--disable-ipv6", 1, 1, OPT_BOOL}, + { "OpenConnect.PKCSClientCert", NULL, 1, 0, OPT_STRING}, + { "OpenConnect.Protocol", "--protocol", 1, 1, OPT_STRING}, + /* --no-cert-check is disabled in openconnect 8.02 */ + { "OpenConnect.NoCertCheck", "--no-cert-check", 0, 0, OPT_BOOL}, + { "OpenConnect.NoHTTPKeepalive", "--no-http-keepalive", 1, 1, OPT_BOOL}, + { "OpenConnect.NoDTLS", "--no-dtls", 1, 1, OPT_BOOL}, + { "OpenConnect.ServerCert", "--servercert", 1, 1, OPT_STRING}, + { "OpenConnect.Usergroup", "--usergroup", 1, 1, OPT_STRING}, + { "OpenConnect.UserPrivateKey", NULL, 1, 0, OPT_STRING}, + { "VPN.MTU", "--base-mtu", 1, 1, OPT_STRING}, +}; + +enum oc_connect_type { + OC_CONNECT_COOKIE = 0, + OC_CONNECT_COOKIE_WITH_USERPASS, + OC_CONNECT_USERPASS, + OC_CONNECT_PUBLICKEY, + OC_CONNECT_PKCS, }; +static const char *connect_types[] = {"cookie", "cookie_with_userpass", + "userpass", "publickey", "pkcs", NULL}; +static const char *protocols[] = { "anyconnect", "nc", "gp", NULL}; + struct oc_private_data { struct vpn_provider *provider; struct connman_task *task; char *if_name; + char *dbus_sender; vpn_provider_connect_cb_t cb; void *user_data; + int fd_in; + int out_ch_id; + int err_ch_id; + GIOChannel *out_ch; + GIOChannel *err_ch; + enum oc_connect_type connect_type; + bool interactive; }; +static bool is_valid_protocol(const char* protocol) +{ + if (!protocol || !*protocol) + return false; + + return g_strv_contains(protocols, protocol); +} + +static void oc_connect_done(struct oc_private_data *data, int err) +{ + connman_info("data %p err %d/%s", data, err, strerror(err)); + + if (data && data->cb) { + vpn_provider_connect_cb_t cb = data->cb; + void *user_data = data->user_data; + + /* Make sure we don't invoke this callback twice */ + data->cb = NULL; + data->user_data = NULL; + cb(data->provider, user_data, err); + } +} + +static void close_io_channel(struct oc_private_data *data, GIOChannel *channel) +{ + int id = 0; + + connman_info("data %p channel %p", data, channel); + + if (!data || !channel) + return; + + if (data->out_ch == channel) { + id = data->out_ch_id; + data->out_ch = NULL; + data->out_ch_id = 0; + } else if (data->err_ch == channel) { + id = data->err_ch_id; + data->err_ch = NULL; + data->err_ch_id = 0; + } else { + return; + } + + if (id) + g_source_remove(id); + + g_io_channel_shutdown(channel, FALSE, NULL); + g_io_channel_unref(channel); +} + static void free_private_data(struct oc_private_data *data) { + connman_info("data %p", data); + + if (!data || !data->provider) + return; + + connman_info("provider %p", data->provider); + + if (vpn_provider_get_plugin_data(data->provider) == data) + vpn_provider_set_plugin_data(data->provider, NULL); + + vpn_provider_unref(data->provider); + + if (data->fd_in > 0) + close(data->fd_in); + data->fd_in = -1; + close_io_channel(data, data->out_ch); + close_io_channel(data, data->err_ch); + + g_free(data->dbus_sender); g_free(data->if_name); g_free(data); } @@ -73,17 +186,51 @@ static void free_private_data(struct oc_private_data *data) static int task_append_config_data(struct vpn_provider *provider, struct connman_task *task) { - const char *option; + const char *option = NULL; int i; for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) { - if (!oc_options[i].oc_opt) + if (!oc_options[i].oc_opt || !oc_options[i].enabled) continue; - option = vpn_provider_get_string(provider, - oc_options[i].cm_opt); - if (!option) - continue; + if (oc_options[i].has_value) { + option = vpn_provider_get_string(provider, + oc_options[i].cm_opt); + if (!option) + continue; + + /* Add boolean type values only if set as true. */ + if (oc_options[i].type == OPT_BOOL) { + if (!vpn_provider_get_boolean(provider, + oc_options[i].cm_opt, + false)) + continue; + + /* No option is set for boolean type values. */ + option = NULL; + } + + /* Skip protocol if it is invalid. */ + if (!g_strcmp0(oc_options[i].cm_opt, + "OpenConnect.Protocol")) { + if (!is_valid_protocol(option)) + continue; + } + } + + /* + * Add server certificate fingerprint only when self signed + * certificates are explicitly allowed. Using --servercert as + * parameter will accept any server with matching fingerprint, + * which would disregard the setting of AllowSelfSignedCert. + */ + if (!g_strcmp0(oc_options[i].cm_opt, + "OpenConnect.ServerCert")) { + if (!vpn_provider_get_boolean(provider, + "OpenConnect.AllowSelfSignedCert", + false)) + continue; + } if (connman_task_add_argument(task, oc_options[i].oc_opt, @@ -103,6 +250,11 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider) char *netmask = NULL, *gateway = NULL; unsigned char prefix_len = 0; struct connman_ipaddress *ipaddress; + struct oc_private_data *data; + + connman_info("provider %p", provider); + + data = vpn_provider_get_plugin_data(provider); dbus_message_iter_init(msg, &iter); @@ -111,6 +263,7 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider) if (!provider) { connman_error("No provider found"); + oc_connect_done(data, ENOENT); return VPN_STATE_FAILURE; } @@ -214,110 +367,770 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(domain); connman_ipaddress_free(ipaddress); + oc_connect_done(data, 0); return VPN_STATE_CONNECT; } -static int run_connect(struct vpn_provider *provider, - struct connman_task *task, const char *if_name, - vpn_provider_connect_cb_t cb, void *user_data) +static ssize_t full_write(int fd, const void *buf, size_t len) { - const char *vpnhost, *vpncookie, *servercert, *mtu; - int fd, err = 0, len; + ssize_t byte_write; + + while (len) { + byte_write = write(fd, buf, len); + if (byte_write < 0) { + connman_error("failed to write config to openconnect: " + " %s\n", strerror(errno)); + return byte_write; + } + len -= byte_write; + buf += byte_write; + } - vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost"); - if (!vpnhost) - vpnhost = vpn_provider_get_string(provider, "Host"); - vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie"); - servercert = vpn_provider_get_string(provider, - "OpenConnect.ServerCert"); + return len; +} - if (!vpncookie || !servercert) { - err = -EINVAL; - goto done; +static ssize_t write_data(int fd, const char *data) +{ + gchar *buf; + ssize_t len; + + if (!data || !*data) + return -1; + + buf = g_strdup_printf("%s\n", data); + + len = full_write(fd, buf, strlen(buf)); + + g_free(buf); + + return len; +} + +static void oc_died(struct connman_task *task, int exit_code, void *user_data) +{ + struct oc_private_data *data = user_data; + + connman_info("task %p data %p exit_code %d user_data %p", task, data, + exit_code, user_data); + + if (!data) + return; + + if (data->provider) { + connman_agent_cancel(data->provider); + + if (task) + vpn_died(task, exit_code, data->provider); } - task_append_config_data(provider, task); + free_private_data(data); +} - connman_task_add_argument(task, "--servercert", servercert); +static gboolean io_channel_out_cb(GIOChannel *source, GIOCondition condition, + gpointer user_data) +{ + struct oc_private_data *data; + char *str; + + data = user_data; - mtu = vpn_provider_get_string(provider, "VPN.MTU"); + if (data->out_ch != source) + return G_SOURCE_REMOVE; - if (mtu) - connman_task_add_argument(task, "--mtu", (char *)mtu); + if ((condition & G_IO_IN) && + g_io_channel_read_line(source, &str, NULL, NULL, NULL) == + G_IO_STATUS_NORMAL) { - connman_task_add_argument(task, "--syslog", NULL); - connman_task_add_argument(task, "--cookie-on-stdin", NULL); + g_strchomp(str); - connman_task_add_argument(task, "--script", - SCRIPTDIR "/openconnect-script"); + /* Only cookie is printed to stdout */ + vpn_provider_set_string_hide_value(data->provider, + "OpenConnect.Cookie", str); - connman_task_add_argument(task, "--interface", if_name); + g_free(str); + } else if (condition & (G_IO_ERR | G_IO_HUP)) { + connman_info("Out channel termination"); + close_io_channel(data, source); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static bool strv_contains_prefix(const char *strv[], const char *str) +{ + int i; + + if (!strv || !str || !*str) + return false; + + for (i = 0; strv[i]; i++) { + if (g_str_has_prefix(str, strv[i])) + return true; + } + + return false; +} + +static void clear_provider_credentials(struct vpn_provider *provider) +{ + const char *keys[] = { "OpenConnect.Username", + "OpenConnect.Password", + "OpenConnect.PKCSPassword", + "OpenConnect.Cookie", + NULL + }; + int i; + + connman_info("provider %p", provider); + + for (i = 0; keys[i]; i++) { + if (!vpn_provider_get_string_immutable(provider, keys[i])) + vpn_provider_set_string_hide_value(provider, keys[i], + "-"); + } +} + +typedef void (* request_input_reply_cb_t) (DBusMessage *reply, + void *user_data); + +static int request_input_credentials(struct oc_private_data *data, + request_input_reply_cb_t cb); + + +static void request_input_pkcs_reply(DBusMessage *reply, void *user_data) +{ + struct oc_private_data *data = user_data; + const char *password = NULL; + const char *key; + DBusMessageIter iter, dict; + int err; + + connman_info("provider %p", data->provider); + + if (!reply) + goto err; + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + goto err; + } + + if (!vpn_agent_check_reply_has_dict(reply)) + goto err; + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "OpenConnect.PKCSPassword")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &password); + vpn_provider_set_string_hide_value(data->provider, key, + password); + } + + dbus_message_iter_next(&dict); + } + + if (data->connect_type != OC_CONNECT_PKCS || !password) + goto err; + + if (write_data(data->fd_in, password) != 0) { + connman_error("openconnect failed to take PKCS pass phrase on" + " stdin"); + goto err; + } + + clear_provider_credentials(data->provider); + + return; +err: + oc_connect_done(data, EACCES); +} + +static gboolean io_channel_err_cb(GIOChannel *source, GIOCondition condition, + gpointer user_data) +{ + struct oc_private_data *data; + const char *auth_failures[] = { + /* Login failed */ + "Got HTTP response: HTTP/1.1 401 Unauthorized", + "Failed to obtain WebVPN cookie", + /* Cookie not valid */ + "Got inappropriate HTTP CONNECT response: " + "HTTP/1.1 401 Unauthorized", + /* Invalid cookie */ + "VPN service unavailable", + /* Problem with certificates */ + "SSL connection failure", + "Creating SSL connection failed", + "SSL connection cancelled", + NULL + }; + const char *conn_failures[] = { + "Failed to connect to", + "Failed to open HTTPS connection to", + NULL + }; + /* Handle both PKCS#12 and PKCS#8 failures */ + const char *pkcs_failures[] = { + "Failed to decrypt PKCS#12 certificate file", + "Failed to decrypt PKCS#8 certificate file", + NULL + }; + /* Handle both PKCS#12 and PKCS#8 requests */ + const char *pkcs_requests[] = { + "Enter PKCS#12 pass phrase", + "Enter PKCS#8 pass phrase", + NULL + }; + const char *server_key_hash = " --servercert"; + char *str; + bool close = false; + int err = 0; + + data = user_data; + + if (!data) + return G_SOURCE_REMOVE; + + if (source && data->err_ch != source) + return G_SOURCE_REMOVE; + + if ((condition & G_IO_IN)) { + gsize len; + int pos; + + if (!data->interactive) { + if (g_io_channel_read_line(source, &str, &len, NULL, + NULL) != G_IO_STATUS_NORMAL) + err = EIO; + else + str[len - 1] = '\0'; + } else { + GIOStatus status; + str = g_try_new0(char, OC_MAX_READBUF_LEN); + if (!str) + return G_SOURCE_REMOVE; + + for (pos = 0; pos < OC_MAX_READBUF_LEN - 1 ; ++pos) { + status = g_io_channel_read_chars(source, + str+pos, 1, &len, NULL); + + if (status == G_IO_STATUS_EOF) { + break; + } else if (status != G_IO_STATUS_NORMAL) { + err = EIO; + break; + } + + /* Ignore control chars and digits at start */ + if (!pos && (g_ascii_iscntrl(str[pos]) || + g_ascii_isdigit(str[pos]))) + --pos; + + /* Read zero length or no more to read */ + if (!len || g_io_channel_get_buffer_condition( + source) != G_IO_IN || + str[pos] == '\n') + break; + } + + /* + * When self signed certificates are allowed and server + * SHA1 fingerprint is printed to stderr there is a + * newline char at the end of SHA1 fingerprint. + */ + if (str[pos] == '\n') + str[pos] = '\0'; + } + + connman_info("openconnect: %s", str); + + if (err || !str || !*str) { + connman_info("error reading from openconnect"); + } else if (g_str_has_prefix(str, server_key_hash)) { + const char *fingerprint; + int position; + bool allow_self_signed; + + allow_self_signed = vpn_provider_get_boolean( + data->provider, + "OpenConnect.AllowSelfSignedCert", + false); + + if (allow_self_signed) { + position = strlen(server_key_hash) + 1; + fingerprint = g_strstrip(str + position); + + connman_info("Set server key hash: \"%s\"", + fingerprint); + + vpn_provider_set_string(data->provider, + "OpenConnect.ServerCert", + fingerprint); + + /* + * OpenConnect waits for "yes" or "no" as + * response to certificate acceptance request. + */ + if (write_data(data->fd_in, "yes") != 0) + connman_error("openconnect: cannot " + "write answer to certificate " + "accept request"); + + } else { + connman_warn("Self signed certificate is not " + " allowed"); + + /* + * Close IO channel to avoid deadlock as an + * answer is expected for the certificate + * accept request. + */ + close = true; + err = ECONNREFUSED; + } + } else if (strv_contains_prefix(pkcs_failures, str)) { + connman_warn("PKCS failure: %s", str); + close = true; + err = EACCES; + } else if (strv_contains_prefix(pkcs_requests, str)) { + connman_info("PKCS file pass phrase request: %s", str); + err = request_input_credentials(data, + request_input_pkcs_reply); + + if (err != -EINPROGRESS) { + err = EACCES; + close = true; + } else { + err = 0; + } + } else if (strv_contains_prefix(auth_failures, str)) { + connman_warn("authentication failed: %s", str); + err = EACCES; + } else if (strv_contains_prefix(conn_failures, str)) { + connman_warn("connection failed: %s", str); + err = ECONNREFUSED; + } + + g_free(str); + } else if (condition & (G_IO_ERR | G_IO_HUP)) { + connman_info("Err channel termination"); + close = true; + } + + if (err) { + switch (err) { + case EACCES: + clear_provider_credentials(data->provider); + break; + case ECONNREFUSED: + /* + * This will trigger VPN_PROVIDER_ERROR_CONNECT_FAILED + * in vpn-provider.c:connect_cb(). + */ + default: + break; + } + + oc_connect_done(data, err); + } + + if (close) { + close_io_channel(data, source); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static int run_connect(struct oc_private_data *data) +{ + struct vpn_provider *provider; + struct connman_task *task; + const char *vpnhost; + const char *vpncookie = NULL; + const char *username; + const char *password = NULL; + const char *certificate = NULL; + const char *private_key; + const char *setting_str; + bool setting; + bool use_stdout = false; + int fd_out = -1; + int fd_err; + int err = 0; + + if (!data) + return -EINVAL; + + provider = data->provider; + task = data->task; + + connman_info("provider %p task %p", provider, task); + + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + vpncookie = vpn_provider_get_string(provider, + "OpenConnect.Cookie"); + if (!vpncookie || !g_strcmp0(vpncookie, "-")) { + err = -EACCES; + goto done; + } + + connman_task_add_argument(task, "--cookie-on-stdin", NULL); + break; + case OC_CONNECT_COOKIE_WITH_USERPASS: + vpncookie = vpn_provider_get_string(provider, + "OpenConnect.Cookie"); + /* No cookie set yet, username and password used first */ + if (!vpncookie || !g_strcmp0(vpncookie, "-")) { + username = vpn_provider_get_string(provider, + "OpenConnect.Username"); + password = vpn_provider_get_string(provider, + "OpenConnect.Password"); + if (!username || !password || + !g_strcmp0(username, "-") || + !g_strcmp0(password, "-")) { + err = -EACCES; + goto done; + } + + connman_task_add_argument(task, "--cookieonly", NULL); + connman_task_add_argument(task, "--user", username); + connman_task_add_argument(task, "--passwd-on-stdin", + NULL); + + /* Use stdout only when cookie is to be read. */ + use_stdout = true; + } else { + connman_task_add_argument(task, "--cookie-on-stdin", + NULL); + } + + break; + case OC_CONNECT_USERPASS: + username = vpn_provider_get_string(provider, + "OpenConnect.Username"); + password = vpn_provider_get_string(provider, + "OpenConnect.Password"); + if (!username || !password || !g_strcmp0(username, "-") || + !g_strcmp0(password, "-")) { + err = -EACCES; + goto done; + } + + connman_task_add_argument(task, "--user", username); + connman_task_add_argument(task, "--passwd-on-stdin", NULL); + break; + case OC_CONNECT_PUBLICKEY: + certificate = vpn_provider_get_string(provider, + "OpenConnect.ClientCert"); + private_key = vpn_provider_get_string(provider, + "OpenConnect.UserPrivateKey"); + + if (!certificate || !private_key) { + err = -EACCES; + goto done; + } + + connman_task_add_argument(task, "--certificate", certificate); + connman_task_add_argument(task, "--sslkey", private_key); + break; + case OC_CONNECT_PKCS: + certificate = vpn_provider_get_string(provider, + "OpenConnect.PKCSClientCert"); + if (!certificate) { + err = -EACCES; + goto done; + } + + connman_task_add_argument(task, "--certificate", certificate); + + password = vpn_provider_get_string(data->provider, + "OpenConnect.PKCSPassword"); + /* Add password only if it is has been set */ + if (!password || !g_strcmp0(password, "-")) + break; + + connman_task_add_argument(task, "--passwd-on-stdin", NULL); + break; + } + + vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost"); + if (!vpnhost || !*vpnhost) + vpnhost = vpn_provider_get_string(provider, "Host"); + + task_append_config_data(provider, task); + + /* + * To clarify complex situation, if cookie is expected to be printed + * to stdout all other output must go to syslog. But with PKCS all + * output must be caught in order to get message about file decryption + * error. For this reason, the mode has to be interactive as well. + */ + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + /* fall through */ + case OC_CONNECT_COOKIE_WITH_USERPASS: + /* fall through */ + case OC_CONNECT_USERPASS: + /* fall through */ + case OC_CONNECT_PUBLICKEY: + connman_task_add_argument(task, "--syslog", NULL); + + setting = vpn_provider_get_boolean(provider, + "OpenConnect.AllowSelfSignedCert", + false); + setting_str = vpn_provider_get_string(provider, + "OpenConnect.ServerCert"); + + /* + * Run in interactive mode if self signed certificates are + * allowed and there is no set server SHA1 fingerprint. + */ + if (setting_str || !setting) + connman_task_add_argument(task, "--non-inter", NULL); + else + data->interactive = true; + break; + case OC_CONNECT_PKCS: + data->interactive = true; + break; + } + + connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script"); + + connman_task_add_argument(task, "--interface", data->if_name); connman_task_add_argument(task, (char *)vpnhost, NULL); - err = connman_task_run(task, vpn_died, provider, - &fd, NULL, NULL); + err = connman_task_run(task, oc_died, data, &data->fd_in, use_stdout ? + &fd_out : NULL, &fd_err); if (err < 0) { - connman_error("openconnect failed to start"); err = -EIO; goto done; } - len = strlen(vpncookie); - if (write(fd, vpncookie, len) != (ssize_t)len || - write(fd, "\n", 1) != 1) { - connman_error("openconnect failed to take cookie on stdin"); - err = -EIO; + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + if (write_data(data->fd_in, vpncookie) != 0) { + connman_error("openconnect failed to take cookie on " + "stdin"); + err = -EIO; + } + + break; + case OC_CONNECT_USERPASS: + if (write_data(data->fd_in, password) != 0) { + connman_error("openconnect failed to take password on " + "stdin"); + err = -EIO; + } + + break; + case OC_CONNECT_COOKIE_WITH_USERPASS: + if (!vpncookie || !g_strcmp0(vpncookie, "-")) { + if (write_data(data->fd_in, password) != 0) { + connman_error("openconnect failed to take " + "password on stdin"); + err = -EIO; + } + } else { + if (write_data(data->fd_in, vpncookie) != 0) { + connman_error("openconnect failed to take " + "cookie on stdin"); + err = -EIO; + } + } + + break; + case OC_CONNECT_PUBLICKEY: + break; + case OC_CONNECT_PKCS: + if (!password || !g_strcmp0(password, "-")) + break; + + if (write_data(data->fd_in, password) != 0) { + connman_error("openconnect failed to take PKCS " + "pass phrase on stdin"); + err = -EIO; + } + + break; + } + + if (err) { + if (fd_out > 0) + close(fd_out); + + if (fd_err > 0) + close(fd_err); + goto done; } + err = -EINPROGRESS; + + if (use_stdout) { + data->out_ch = g_io_channel_unix_new(fd_out); + + /* Use ASCII encoding only */ + if (g_io_channel_set_encoding(data->out_ch, NULL, NULL) != + G_IO_STATUS_NORMAL) { + close_io_channel(data, data->out_ch); + err = -EIO; + } else { + data->out_ch_id = g_io_add_watch(data->out_ch, + G_IO_IN | G_IO_ERR | G_IO_HUP, + (GIOFunc)io_channel_out_cb, + data); + } + } + + data->err_ch = g_io_channel_unix_new(fd_err); + + /* Use ASCII encoding only */ + if (g_io_channel_set_encoding(data->err_ch, NULL, NULL) != + G_IO_STATUS_NORMAL) { + close_io_channel(data, data->err_ch); + err = -EIO; + } else { + data->err_ch_id = g_io_add_watch(data->err_ch, + G_IO_IN | G_IO_ERR | G_IO_HUP, + (GIOFunc)io_channel_err_cb, data); + } + done: - if (cb) - cb(provider, user_data, err); + clear_provider_credentials(data->provider); return err; } -static void request_input_append_informational(DBusMessageIter *iter, - void *user_data) +static void request_input_append(DBusMessageIter *iter, + const char *str_type, const char *str, void *user_data) { - const char *str; + const char *string; - str = "string"; - connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, &str); + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str_type); + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); - str = "informational"; - connman_dbus_dict_append_basic(iter, "Requirement", DBUS_TYPE_STRING, - &str); + if (!user_data) + return; + + string = user_data; + connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, + &string); +} - str = user_data; - connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str); +static void request_input_append_informational(DBusMessageIter *iter, + void *user_data) +{ + request_input_append(iter, "string", "informational", user_data); } static void request_input_append_mandatory(DBusMessageIter *iter, void *user_data) { - char *str = "string"; + request_input_append(iter, "string", "mandatory", user_data); +} - connman_dbus_dict_append_basic(iter, "Type", - DBUS_TYPE_STRING, &str); - str = "mandatory"; - connman_dbus_dict_append_basic(iter, "Requirement", - DBUS_TYPE_STRING, &str); +static void request_input_append_optional(DBusMessageIter *iter, + void *user_data) +{ + request_input_append(iter, "string", "optional", user_data); +} + +static void request_input_append_password(DBusMessageIter *iter, + void *user_data) +{ + request_input_append(iter, "password", "mandatory", user_data); +} + +static void request_input_append_to_dict(struct vpn_provider *provider, + DBusMessageIter *dict, + connman_dbus_append_cb_t function_cb, const char *key) +{ + const char *str; + bool immutable = false; + + if (!provider || !dict || !function_cb || !key) + return; + + str = vpn_provider_get_string(provider, key); + /* Ignore empty informational content */ + if (!str && function_cb == request_input_append_informational) + return; + + /* If value is "-", it is cleared by VPN agent */ + if (!g_strcmp0(str, "-")) + str = NULL; + + if (str) + immutable = vpn_provider_get_string_immutable(provider, key); + + if (immutable) { + /* Hide immutable password types */ + if (function_cb == request_input_append_password) + str = "********"; + + /* Send immutable as informational */ + function_cb = request_input_append_informational; + } + + connman_dbus_dict_append_dict(dict, key, function_cb, + str ? (void *)str : NULL); } -static void request_input_cookie_reply(DBusMessage *reply, void *user_data) +static void request_input_credentials_reply(DBusMessage *reply, void *user_data) { struct oc_private_data *data = user_data; - char *cookie = NULL, *servercert = NULL, *vpnhost = NULL; - char *key; + const char *cookie = NULL; + const char *servercert = NULL; + const char *vpnhost = NULL; + const char *username = NULL; + const char *password = NULL; + const char *pkcspassword = NULL; + const char *key; DBusMessageIter iter, dict; + int err; - DBG("provider %p", data->provider); + connman_info("provider %p", data->provider); - if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) + if (!reply) goto err; + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + goto out; + } + if (!vpn_agent_check_reply_has_dict(reply)) goto err; @@ -344,7 +1157,6 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data) dbus_message_iter_get_basic(&value, &cookie); vpn_provider_set_string_hide_value(data->provider, key, cookie); - } else if (g_str_equal(key, "OpenConnect.ServerCert")) { dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) @@ -369,43 +1181,103 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data) break; dbus_message_iter_get_basic(&value, &vpnhost); vpn_provider_set_string(data->provider, key, vpnhost); + } else if (g_str_equal(key, "Username")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &username); + vpn_provider_set_string_hide_value(data->provider, + "OpenConnect.Username", username); + } else if (g_str_equal(key, "Password")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &password); + vpn_provider_set_string_hide_value(data->provider, + "OpenConnect.Password", password); + } else if (g_str_equal(key, "OpenConnect.PKCSPassword")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &pkcspassword); + vpn_provider_set_string_hide_value(data->provider, key, + pkcspassword); } dbus_message_iter_next(&dict); } - if (!cookie || !servercert || !vpnhost) - goto err; - - run_connect(data->provider, data->task, data->if_name, data->cb, - data->user_data); + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + if (!cookie) + goto err; + + break; + case OC_CONNECT_USERPASS: + /* fall through */ + case OC_CONNECT_COOKIE_WITH_USERPASS: + if (!username || !password) + goto err; + + break; + case OC_CONNECT_PUBLICKEY: + break; // This should not be reached. + case OC_CONNECT_PKCS: + if (!pkcspassword) + goto err; + + break; + } - free_private_data(data); + err = run_connect(data); + if (err != -EINPROGRESS) + goto err; return; err: - vpn_provider_indicate_error(data->provider, - VPN_PROVIDER_ERROR_AUTH_FAILED); + oc_connect_done(data, EACCES); +out: free_private_data(data); } -static int request_cookie_input(struct vpn_provider *provider, - struct oc_private_data *data, - const char *dbus_sender) +static int request_input_credentials(struct oc_private_data *data, + request_input_reply_cb_t cb) { DBusMessage *message; - const char *path, *agent_sender, *agent_path; + const char *path; + const char *agent_sender; + const char *agent_path; + const char *username; DBusMessageIter iter; DBusMessageIter dict; - const char *str; int err; void *agent; - agent = connman_agent_get_info(dbus_sender, &agent_sender, - &agent_path); - if (!provider || !agent || !agent_path) + if (!data || !cb) + return -ESRCH; + + connman_info("provider %p", data->provider); + + agent = connman_agent_get_info(data->dbus_sender, + &agent_sender, &agent_path); + if (!data->provider || !agent || !agent_path) return -ESRCH; message = dbus_message_new_method_call(agent_sender, agent_path, @@ -416,120 +1288,235 @@ static int request_cookie_input(struct vpn_provider *provider, dbus_message_iter_init_append(message, &iter); - path = vpn_provider_get_path(provider); - dbus_message_iter_append_basic(&iter, - DBUS_TYPE_OBJECT_PATH, &path); + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); connman_dbus_dict_open(&iter, &dict); - str = vpn_provider_get_string(provider, "OpenConnect.CACert"); - if (str) - connman_dbus_dict_append_dict(&dict, "OpenConnect.CACert", + request_input_append_to_dict(data->provider, &dict, request_input_append_informational, - (void *)str); - - str = vpn_provider_get_string(provider, "OpenConnect.ClientCert"); - if (str) - connman_dbus_dict_append_dict(&dict, "OpenConnect.ClientCert", + "OpenConnect.CACert"); + + /* + * For backwards compatibility add OpenConnect.ServerCert and + * OpenConnect.VPNHost as madnatory only in the default authentication + * mode. Otherwise. add the fields as informational. These should be + * set in provider settings and not to be queried with every connection + * attempt. + */ + request_input_append_to_dict(data->provider, &dict, + data->connect_type == OC_CONNECT_COOKIE ? + request_input_append_optional : request_input_append_informational, - (void *)str); - - connman_dbus_dict_append_dict(&dict, "OpenConnect.ServerCert", - request_input_append_mandatory, NULL); + "OpenConnect.ServerCert"); - connman_dbus_dict_append_dict(&dict, "OpenConnect.VPNHost", - request_input_append_mandatory, NULL); + request_input_append_to_dict(data->provider, &dict, + data->connect_type == OC_CONNECT_COOKIE ? + request_input_append_optional : + request_input_append_informational, + "OpenConnect.VPNHost"); + + if (vpn_provider_get_authentication_errors(data->provider)) + vpn_agent_append_auth_failure(&dict, data->provider, NULL); + + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + request_input_append_to_dict(data->provider, &dict, + request_input_append_mandatory, + "OpenConnect.Cookie"); + break; + /* + * The authentication is done with username and password to get the + * cookie for connection. + */ + case OC_CONNECT_COOKIE_WITH_USERPASS: + /* fallthrough */ + case OC_CONNECT_USERPASS: + username = vpn_provider_get_string(data->provider, + "OpenConnect.Username"); + vpn_agent_append_user_info(&dict, data->provider, username); + break; + case OC_CONNECT_PUBLICKEY: + return -EINVAL; + case OC_CONNECT_PKCS: + request_input_append_to_dict(data->provider, &dict, + request_input_append_informational, + "OpenConnect.PKCSClientCert"); - connman_dbus_dict_append_dict(&dict, "OpenConnect.Cookie", - request_input_append_mandatory, NULL); + request_input_append_to_dict(data->provider, &dict, + request_input_append_password, + "OpenConnect.PKCSPassword"); + break; + } - vpn_agent_append_host_and_name(&dict, provider); + vpn_agent_append_host_and_name(&dict, data->provider); connman_dbus_dict_close(&iter, &dict); - err = connman_agent_queue_message(provider, message, - connman_timeout_input_request(), - request_input_cookie_reply, data, agent); + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), cb, data, agent); - if (err < 0 && err != -EBUSY) { - DBG("error %d sending agent request", err); - dbus_message_unref(message); + dbus_message_unref(message); + if (err < 0 && err != -EBUSY) { + connman_error("cannot send agent request, error: %d", err); return err; } - dbus_message_unref(message); - return -EINPROGRESS; } +static enum oc_connect_type get_authentication_type( + struct vpn_provider *provider) +{ + const char *auth; + enum oc_connect_type type; + + auth = vpn_provider_get_string(provider, "OpenConnect.AuthType"); + if (!auth) + goto out; + + for (type = 0; connect_types[type]; type++) { + if (!g_strcmp0(auth, connect_types[type])) { + connman_info("auth type %d/%s", type, + connect_types[type]); + return type; + } + } + +out: + /* Default to cookie */ + return OC_CONNECT_COOKIE; +} + static int oc_connect(struct vpn_provider *provider, struct connman_task *task, const char *if_name, vpn_provider_connect_cb_t cb, const char *dbus_sender, void *user_data) { - const char *vpnhost, *vpncookie, *servercert; + struct oc_private_data *data; + const char *vpncookie; + const char *certificate; + const char *username; + const char *password; + const char *private_key; int err; - vpnhost = vpn_provider_get_string(provider, "Host"); - if (!vpnhost) { - connman_error("Host not set; cannot enable VPN"); - return -EINVAL; - } + connman_info("provider %p task %p", provider, task); + + data = g_try_new0(struct oc_private_data, 1); + if (!data) + return -ENOMEM; - vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie"); - servercert = vpn_provider_get_string(provider, - "OpenConnect.ServerCert"); - if (!vpncookie || !servercert) { - struct oc_private_data *data; + vpn_provider_set_plugin_data(provider, data); + data->provider = vpn_provider_ref(provider); + data->task = task; + data->if_name = g_strdup(if_name); + data->dbus_sender = g_strdup(dbus_sender); + data->cb = cb; + data->user_data = user_data; + data->connect_type = get_authentication_type(provider); + + switch (data->connect_type) { + case OC_CONNECT_COOKIE: + vpncookie = vpn_provider_get_string(provider, + "OpenConnect.Cookie"); + if (!vpncookie || !g_strcmp0(vpncookie, "-")) + goto request_input; + + break; + case OC_CONNECT_USERPASS: + username = vpn_provider_get_string(provider, + "OpenConnect.Username"); + password = vpn_provider_get_string(provider, + "OpenConnect.Password"); + if (!username || !password || !g_strcmp0(username, "-") || + !g_strcmp0(password, "-")) + goto request_input; + + break; + case OC_CONNECT_COOKIE_WITH_USERPASS: + vpncookie = vpn_provider_get_string(provider, + "OpenConnect.Cookie"); + /* Username and password must be set if cookie is missing */ + if (!vpncookie) { + username = vpn_provider_get_string(provider, + "OpenConnect.Username"); + password = vpn_provider_get_string(provider, + "OpenConnect.Password"); + + if (!username || !password || + !g_strcmp0(username, "-") || + !g_strcmp0(password, "-")) + goto request_input; + } else if (!g_strcmp0(vpncookie, "-")) { + goto request_input; + } - data = g_try_new0(struct oc_private_data, 1); - if (!data) - return -ENOMEM; + break; + case OC_CONNECT_PUBLICKEY: + certificate = vpn_provider_get_string(provider, + "OpenConnect.ClientCert"); + private_key = vpn_provider_get_string(provider, + "OpenConnect.UserPrivateKey"); - data->provider = provider; - data->task = task; - data->if_name = g_strdup(if_name); - data->cb = cb; - data->user_data = user_data; + if (!certificate || !private_key) { + connman_warn("missing certificate and/or private key"); + oc_connect_done(data, EACCES); + free_private_data(data); + return -EACCES; + } - err = request_cookie_input(provider, data, dbus_sender); - if (err != -EINPROGRESS) { - vpn_provider_indicate_error(data->provider, - VPN_PROVIDER_ERROR_LOGIN_FAILED); + break; + case OC_CONNECT_PKCS: + certificate = vpn_provider_get_string(provider, + "OpenConnect.PKCSClientCert"); + if (!certificate) { + connman_warn("missing PKCS certificate"); + oc_connect_done(data, EACCES); free_private_data(data); + return -EACCES; } - return err; + + break; } - return run_connect(provider, task, if_name, cb, user_data); + return run_connect(data); + +request_input: + err = request_input_credentials(data, request_input_credentials_reply); + if (err != -EINPROGRESS) { + oc_connect_done(data, err); + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_LOGIN_FAILED); + free_private_data(data); + } + + return err; +} + +static void oc_disconnect(struct vpn_provider *provider) +{ + connman_info("provider %p", provider); + + if (!provider) + return; + + /* + * OpenConnect may be disconnect by timeout in connmand before running + * the openconnect process. In such case it is important to cancel the + * agent request to avoid having multiple ones visible. + */ + connman_agent_cancel(provider); } static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile) { - const char *setting, *option; + const char *save_group; + const char *option; int i; - setting = vpn_provider_get_string(provider, - "OpenConnect.ServerCert"); - if (setting) - g_key_file_set_string(keyfile, - vpn_provider_get_save_group(provider), - "OpenConnect.ServerCert", setting); - - setting = vpn_provider_get_string(provider, - "OpenConnect.CACert"); - if (setting) - g_key_file_set_string(keyfile, - vpn_provider_get_save_group(provider), - "OpenConnect.CACert", setting); - - setting = vpn_provider_get_string(provider, - "VPN.MTU"); - if (setting) - g_key_file_set_string(keyfile, - vpn_provider_get_save_group(provider), - "VPN.MTU", setting); + save_group = vpn_provider_get_save_group(provider); for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) { if (strncmp(oc_options[i].cm_opt, "OpenConnect.", 12) == 0) { @@ -538,8 +1525,7 @@ static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile) if (!option) continue; - g_key_file_set_string(keyfile, - vpn_provider_get_save_group(provider), + g_key_file_set_string(keyfile, save_group, oc_options[i].cm_opt, option); } } @@ -549,13 +1535,22 @@ static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile) static int oc_error_code(struct vpn_provider *provider, int exit_code) { + connman_info("%d", exit_code); + + /* OpenConnect process return values are ambiguous in definition + * https://github.com/openconnect/openconnect/blob/master/main.c#L1693 + * and it is safer not to rely on them. Login error cannot be + * differentiated from connection errors, e.g., when self signed + * certificate is rejected by user setting. + */ switch (exit_code) { - case 1: case 2: - vpn_provider_set_string_hide_value(provider, - "OpenConnect.Cookie", NULL); + /* Cookie has failed */ + clear_provider_credentials(provider); return VPN_PROVIDER_ERROR_LOGIN_FAILED; + case 1: + /* fall through */ default: return VPN_PROVIDER_ERROR_UNKNOWN; } @@ -593,6 +1588,7 @@ static int oc_route_env_parse(struct vpn_provider *provider, const char *key, static struct vpn_driver vpn_driver = { .notify = oc_notify, .connect = oc_connect, + .disconnect = oc_disconnect, .error_code = oc_error_code, .save = oc_save, .route_env_parse = oc_route_env_parse, diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c index f38c0c3..bc0303c 100644 --- a/vpn/plugins/openvpn.c +++ b/vpn/plugins/openvpn.c @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2010-2014 BMW Car IT GmbH. + * Copyright (C) 2016-2019 Jolla Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -30,6 +31,9 @@ #include #include #include +#include +#include +#include #include @@ -39,10 +43,15 @@ #include #include #include +#include +#include +#include #include "../vpn-provider.h" +#include "../vpn-agent.h" #include "vpn.h" +#include "../vpn.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) @@ -57,7 +66,7 @@ struct { { "OpenVPN.CACert", "--ca", 1 }, { "OpenVPN.Cert", "--cert", 1 }, { "OpenVPN.Key", "--key", 1 }, - { "OpenVPN.MTU", "--mtu", 1 }, + { "OpenVPN.MTU", "--tun-mtu", 1 }, { "OpenVPN.NSCertType", "--ns-cert-type", 1 }, { "OpenVPN.Proto", "--proto", 1 }, { "OpenVPN.Port", "--port", 1 }, @@ -67,6 +76,7 @@ struct { { "OpenVPN.TLSRemote", "--tls-remote", 1 }, { "OpenVPN.TLSAuth", NULL, 1 }, { "OpenVPN.TLSAuthDir", NULL, 1 }, + { "OpenVPN.TLSCipher", "--tls-cipher", 1}, { "OpenVPN.Cipher", "--cipher", 1 }, { "OpenVPN.Auth", "--auth", 1 }, { "OpenVPN.CompLZO", "--comp-lzo", 0 }, @@ -76,6 +86,50 @@ struct { { "OpenVPN.Verb", "--verb", 1 }, }; +struct ov_private_data { + struct vpn_provider *provider; + struct connman_task *task; + char *dbus_sender; + char *if_name; + vpn_provider_connect_cb_t cb; + void *user_data; + char *mgmt_path; + guint mgmt_timer_id; + guint mgmt_event_id; + GIOChannel *mgmt_channel; + int connect_attempts; + int failed_attempts_privatekey; +}; + +static void ov_connect_done(struct ov_private_data *data, int err) +{ + if (data && data->cb) { + vpn_provider_connect_cb_t cb = data->cb; + void *user_data = data->user_data; + + /* Make sure we don't invoke this callback twice */ + data->cb = NULL; + data->user_data = NULL; + cb(data->provider, user_data, err); + } + + if (!err) + data->failed_attempts_privatekey = 0; +} + +static void free_private_data(struct ov_private_data *data) +{ + if (vpn_provider_get_plugin_data(data->provider) == data) + vpn_provider_set_plugin_data(data->provider, NULL); + + ov_connect_done(data, EIO); + vpn_provider_unref(data->provider); + g_free(data->dbus_sender); + g_free(data->if_name); + g_free(data->mgmt_path); + g_free(data); +} + struct nameserver_entry { int id; char *nameserver; @@ -162,6 +216,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) char *address = NULL, *gateway = NULL, *peer = NULL, *netmask = NULL; struct connman_ipaddress *ipaddress; GSList *nameserver_list = NULL; + struct ov_private_data *data = vpn_provider_get_plugin_data(provider); dbus_message_iter_init(msg, &iter); @@ -173,8 +228,12 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) return VPN_STATE_FAILURE; } - if (strcmp(reason, "up")) + DBG("%p %s", vpn_provider_get_name(provider), reason); + + if (strcmp(reason, "up")) { + ov_connect_done(data, EIO); return VPN_STATE_DISCONNECT; + } dbus_message_iter_recurse(&iter, &dict); @@ -266,6 +325,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(netmask); connman_ipaddress_free(ipaddress); + ov_connect_done(data, 0); return VPN_STATE_CONNECT; } @@ -304,73 +364,74 @@ static int task_append_config_data(struct vpn_provider *provider, if (!option) continue; + /* + * If the AuthUserPass option is "-", provide the input + * via management interface + */ + if (!strcmp(ov_options[i].cm_opt, "OpenVPN.AuthUserPass") && + !strcmp(option, "-")) + option = NULL; + if (connman_task_add_argument(task, ov_options[i].ov_opt, - ov_options[i].has_value ? option : NULL) < 0) { + ov_options[i].has_value ? option : NULL) < 0) return -EIO; - } + } return 0; } -static gboolean can_read_data(GIOChannel *chan, - GIOCondition cond, gpointer data) +static void close_management_interface(struct ov_private_data *data) { - void (*cbf)(const char *format, ...) = data; - gchar *str; - gsize size; + if (data->mgmt_path) { + if (unlink(data->mgmt_path) && errno != ENOENT) + connman_warn("Unable to unlink management socket %s: " + "%d", data->mgmt_path, errno); + + g_free(data->mgmt_path); + data->mgmt_path = NULL; + } - if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) - return FALSE; + if (data->mgmt_timer_id != 0) { + g_source_remove(data->mgmt_timer_id); + data->mgmt_timer_id = 0; + } - g_io_channel_read_line(chan, &str, &size, NULL, NULL); - cbf(str); - g_free(str); + if (data->mgmt_event_id) { + g_source_remove(data->mgmt_event_id); + data->mgmt_event_id = 0; + } - return TRUE; + if (data->mgmt_channel) { + g_io_channel_shutdown(data->mgmt_channel, FALSE, NULL); + g_io_channel_unref(data->mgmt_channel); + data->mgmt_channel = NULL; + } } -static int setup_log_read(int stdout_fd, int stderr_fd) +static void ov_died(struct connman_task *task, int exit_code, void *user_data) { - GIOChannel *chan; - int watch; + struct ov_private_data *data = user_data; - chan = g_io_channel_unix_new(stdout_fd); - g_io_channel_set_close_on_unref(chan, TRUE); - watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, - can_read_data, connman_debug); - g_io_channel_unref(chan); + /* Cancel any pending agent requests */ + connman_agent_cancel(data->provider); - if (watch == 0) - return -EIO; + close_management_interface(data); - chan = g_io_channel_unix_new(stderr_fd); - g_io_channel_set_close_on_unref(chan, TRUE); - watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, - can_read_data, connman_error); - g_io_channel_unref(chan); + vpn_died(task, exit_code, data->provider); - return watch == 0? -EIO : 0; + free_private_data(data); } -static int ov_connect(struct vpn_provider *provider, - struct connman_task *task, const char *if_name, - vpn_provider_connect_cb_t cb, const char *dbus_sender, - void *user_data) +static int run_connect(struct ov_private_data *data, + vpn_provider_connect_cb_t cb, void *user_data) { + struct vpn_provider *provider = data->provider; + struct connman_task *task = data->task; const char *option; - int stdout_fd, stderr_fd; int err = 0; - option = vpn_provider_get_string(provider, "Host"); - if (!option) { - connman_error("Host not set; cannot enable VPN"); - return -EINVAL; - } - - task_append_config_data(provider, task); - option = vpn_provider_get_string(provider, "OpenVPN.ConfigFile"); if (!option) { /* @@ -390,6 +451,17 @@ static int ov_connect(struct vpn_provider *provider, connman_task_add_argument(task, "--client", NULL); } + if (data->mgmt_path) { + connman_task_add_argument(task, "--management", NULL); + connman_task_add_argument(task, data->mgmt_path, NULL); + connman_task_add_argument(task, "unix", NULL); + connman_task_add_argument(task, "--management-query-passwords", + NULL); + connman_task_add_argument(task, "--auth-retry", "interact"); + } + + connman_task_add_argument(task, "--syslog", NULL); + connman_task_add_argument(task, "--script-security", "2"); connman_task_add_argument(task, "--up", @@ -408,7 +480,7 @@ static int ov_connect(struct vpn_provider *provider, connman_task_add_argument(task, "CONNMAN_PATH", connman_task_get_path(task)); - connman_task_add_argument(task, "--dev", if_name); + connman_task_add_argument(task, "--dev", data->if_name); option = vpn_provider_get_string(provider, "OpenVPN.DeviceType"); if (option) { connman_task_add_argument(task, "--dev-type", option); @@ -429,25 +501,634 @@ static int ov_connect(struct vpn_provider *provider, * moment. The problem is that when OpenVPN decides to switch * from CONNECTED state to RECONNECTING and then to RESOLVE, * it is not possible to do a DNS lookup. The DNS server is - * not accessable through the tunnel anymore and so we end up + * not accessible through the tunnel anymore and so we end up * trying to resolve the OpenVPN servers address. */ connman_task_add_argument(task, "--ping-restart", "0"); - err = connman_task_run(task, vpn_died, provider, - NULL, &stdout_fd, &stderr_fd); + /* + * Disable connetion retrying when OpenVPN is connected over TCP. + * With TCP OpenVPN attempts to handle reconnection silently without + * reporting the error back when establishing a connection or + * reconnecting as succesful one. The latter causes trouble if the + * retries are not limited to 1 (no retry) as the interface is up and + * connman regards it as the default route and network ceases to work, + * including DNS. + */ + option = vpn_provider_get_string(provider, "OpenVPN.Proto"); + if (option && g_str_has_prefix(option, "tcp")) + connman_task_add_argument(task, "--connect-retry-max", "1"); + + err = connman_task_run(task, ov_died, data, NULL, NULL, NULL); if (err < 0) { + data->cb = NULL; + data->user_data = NULL; connman_error("openvpn failed to start"); - err = -EIO; - goto done; + return -EIO; + } else { + /* This lets the caller know that the actual result of + * the operation will be reported to the callback */ + return -EINPROGRESS; + } +} + +static void ov_quote_credential(GString *line, const char *cred) +{ + if (!line) + return; + + g_string_append_c(line, '"'); + + while (*cred != '\0') { + + switch (*cred) { + case ' ': + case '"': + case '\\': + g_string_append_c(line, '\\'); + break; + default: + break; + } + + g_string_append_c(line, *cred++); + } + + g_string_append_c(line, '"'); +} + +static void ov_return_credentials(struct ov_private_data *data, + const char *username, const char *password) +{ + GString *reply_string; + gchar *reply; + gsize len; + + reply_string = g_string_new(NULL); + + g_string_append(reply_string, "username \"Auth\" "); + ov_quote_credential(reply_string, username); + g_string_append_c(reply_string, '\n'); + + g_string_append(reply_string, "password \"Auth\" "); + ov_quote_credential(reply_string, password); + g_string_append_c(reply_string, '\n'); + + len = reply_string->len; + reply = g_string_free(reply_string, FALSE); + + g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL); + g_io_channel_flush(data->mgmt_channel, NULL); + + memset(reply, 0, len); + g_free(reply); +} + +static void ov_return_private_key_password(struct ov_private_data *data, + const char *privatekeypass) +{ + GString *reply_string; + gchar *reply; + gsize len; + + reply_string = g_string_new(NULL); + + g_string_append(reply_string, "password \"Private Key\" "); + ov_quote_credential(reply_string, privatekeypass); + g_string_append_c(reply_string, '\n'); + + len = reply_string->len; + reply = g_string_free(reply_string, FALSE); + + g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL); + g_io_channel_flush(data->mgmt_channel, NULL); + + memset(reply, 0, len); + g_free(reply); +} + +static void request_input_append_informational(DBusMessageIter *iter, + void *user_data) +{ + char *str = "string"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "informational"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_append_mandatory(DBusMessageIter *iter, + void *user_data) +{ + char *str = "string"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_append_password(DBusMessageIter *iter, + void *user_data) +{ + char *str = "password"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_credentials_reply(DBusMessage *reply, + void *user_data) +{ + struct ov_private_data *data = user_data; + char *username = NULL; + char *password = NULL; + char *key; + DBusMessageIter iter, dict; + DBusError error; + int err = 0; + + connman_info("provider %p", data->provider); + + /* + * When connmand calls disconnect because of connection timeout no + * reply is received. + */ + if (!reply) { + err = ENOENT; + goto err; + } + + dbus_error_init(&error); + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + return; + } + + if (!vpn_agent_check_reply_has_dict(reply)) { + err = ENOENT; + goto err; + } + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "OpenVPN.Password")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &password); + vpn_provider_set_string_hide_value(data->provider, + key, password); + + } else if (g_str_equal(key, "OpenVPN.Username")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &username); + vpn_provider_set_string_hide_value(data->provider, + key, username); + } + + dbus_message_iter_next(&dict); + } + + if (!password || !username) { + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + err = EACCES; + goto err; + } + + ov_return_credentials(data, username, password); + + return; + +err: + ov_connect_done(data, err); +} + +static int request_credentials_input(struct ov_private_data *data) +{ + DBusMessage *message; + const char *path, *agent_sender, *agent_path; + DBusMessageIter iter; + DBusMessageIter dict; + int err; + void *agent; + + agent = connman_agent_get_info(data->dbus_sender, &agent_sender, + &agent_path); + if (!agent || !agent_path) + return -ESRCH; + + message = dbus_message_new_method_call(agent_sender, agent_path, + VPN_AGENT_INTERFACE, + "RequestInput"); + if (!message) + return -ENOMEM; + + dbus_message_iter_init_append(message, &iter); + + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, + DBUS_TYPE_OBJECT_PATH, &path); + + connman_dbus_dict_open(&iter, &dict); + + if (vpn_provider_get_authentication_errors(data->provider)) + vpn_agent_append_auth_failure(&dict, data->provider, NULL); + + /* Request temporary properties to pass on to openvpn */ + connman_dbus_dict_append_dict(&dict, "OpenVPN.Username", + request_input_append_mandatory, NULL); + + connman_dbus_dict_append_dict(&dict, "OpenVPN.Password", + request_input_append_password, NULL); + + vpn_agent_append_host_and_name(&dict, data->provider); + + connman_dbus_dict_close(&iter, &dict); + + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), + request_input_credentials_reply, data, agent); + + if (err < 0 && err != -EBUSY) { + connman_error("error %d sending agent request", err); + dbus_message_unref(message); + + return err; + } + + dbus_message_unref(message); + + return -EINPROGRESS; +} + +static void request_input_private_key_reply(DBusMessage *reply, + void *user_data) +{ + struct ov_private_data *data = user_data; + const char *privatekeypass = NULL; + const char *key; + DBusMessageIter iter, dict; + DBusError error; + int err = 0; + + connman_info("provider %p", data->provider); + + /* + * When connmand calls disconnect because of connection timeout no + * reply is received. + */ + if (!reply) { + err = ENOENT; + goto err; + } + + dbus_error_init(&error); + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + return; + } + + if (!vpn_agent_check_reply_has_dict(reply)) { + err = ENOENT; + goto err; + } + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "OpenVPN.PrivateKeyPassword")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &privatekeypass); + vpn_provider_set_string_hide_value(data->provider, + key, privatekeypass); + + } + + dbus_message_iter_next(&dict); + } + + if (!privatekeypass) { + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + + err = EACCES; + goto err; + } + + ov_return_private_key_password(data, privatekeypass); + + return; + +err: + ov_connect_done(data, err); +} + +static int request_private_key_input(struct ov_private_data *data) +{ + DBusMessage *message; + const char *path, *agent_sender, *agent_path; + const char *privatekeypass; + DBusMessageIter iter; + DBusMessageIter dict; + int err; + void *agent; + + /* + * First check if this is the second attempt to get the key within + * this connection. In such case there has been invalid Private Key + * Password and it must be reset, and queried from user. + */ + if (data->failed_attempts_privatekey) { + vpn_provider_set_string_hide_value(data->provider, + "OpenVPN.PrivateKeyPassword", NULL); + } else { + /* If the encrypted Private key password is kept in memory and + * use it first. If authentication fails this is cleared, + * likewise it is when connman-vpnd is restarted. + */ + privatekeypass = vpn_provider_get_string(data->provider, + "OpenVPN.PrivateKeyPassword"); + if (privatekeypass) { + ov_return_private_key_password(data, privatekeypass); + goto out; + } + } + + agent = connman_agent_get_info(data->dbus_sender, &agent_sender, + &agent_path); + if (!agent || !agent_path) + return -ESRCH; + + message = dbus_message_new_method_call(agent_sender, agent_path, + VPN_AGENT_INTERFACE, + "RequestInput"); + if (!message) + return -ENOMEM; + + dbus_message_iter_init_append(message, &iter); + + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, + DBUS_TYPE_OBJECT_PATH, &path); + + connman_dbus_dict_open(&iter, &dict); + + connman_dbus_dict_append_dict(&dict, "OpenVPN.PrivateKeyPassword", + request_input_append_password, NULL); + + vpn_agent_append_host_and_name(&dict, data->provider); + + /* Do not allow to store or retrieve the encrypted Private Key pass */ + vpn_agent_append_allow_credential_storage(&dict, false); + vpn_agent_append_allow_credential_retrieval(&dict, false); + + /* + * Indicate to keep credentials, the enc Private Key password should + * not affect the credential storing. + */ + vpn_agent_append_keep_credentials(&dict, true); + + connman_dbus_dict_append_dict(&dict, "Enter Private Key password", + request_input_append_informational, NULL); + + connman_dbus_dict_close(&iter, &dict); + + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), + request_input_private_key_reply, data, agent); + + if (err < 0 && err != -EBUSY) { + connman_error("error %d sending agent request", err); + dbus_message_unref(message); + + return err; + } + + dbus_message_unref(message); + +out: + return -EINPROGRESS; +} + +static gboolean ov_management_handle_input(GIOChannel *source, + GIOCondition condition, gpointer user_data) +{ + struct ov_private_data *data = user_data; + char *str = NULL; + int err = 0; + bool close = false; + + if (condition & G_IO_IN) { + /* + * Just return if line is not read and str is not allocated. + * Condition check handles closing of the channel later. + */ + if (g_io_channel_read_line(source, &str, NULL, NULL, NULL) != + G_IO_STATUS_NORMAL) + return true; + + str[strlen(str) - 1] = '\0'; + connman_warn("openvpn request %s", str); + + if (g_str_has_prefix(str, ">PASSWORD:Need 'Auth'")) { + /* + * Request credentials from the user + */ + err = request_credentials_input(data); + if (err != -EINPROGRESS) + close = true; + } else if (g_str_has_prefix(str, + ">PASSWORD:Need 'Private Key'")) { + err = request_private_key_input(data); + if (err != -EINPROGRESS) + close = true; + } else if (g_str_has_prefix(str, + ">PASSWORD:Verification Failed: 'Auth'")) { + /* + * Add error only, state change indication causes + * signal to be sent, which is not desired when + * OpenVPN is in interactive mode. + */ + vpn_provider_add_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + /* + * According to the OpenVPN manual about management interface + * https://openvpn.net/community-resources/management-interface/ + * this should be received but it does not seem to be reported + * when decrypting private key fails. This requires following + * patch for OpenVPN (at least <= 2.4.5) in order to work: + * https://git.sailfishos.org/mer-core/openvpn/blob/ + * 4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey- + * passphrase-handling.diff + */ + } else if (g_str_has_prefix(str, ">PASSWORD:Verification " + "Failed: 'Private Key'")) { + data->failed_attempts_privatekey++; + } + + g_free(str); + } else if (condition & (G_IO_ERR | G_IO_HUP)) { + connman_warn("Management channel termination"); + close = true; } - err = setup_log_read(stdout_fd, stderr_fd); -done: - if (cb) - cb(provider, user_data, err); + if (close) + close_management_interface(data); + + return true; +} + +static int ov_management_connect_timer_cb(gpointer user_data) +{ + struct ov_private_data *data = user_data; + + if (!data->mgmt_channel) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + struct sockaddr_un remote; + int err; + + memset(&remote, 0, sizeof(remote)); + remote.sun_family = AF_UNIX; + g_strlcpy(remote.sun_path, data->mgmt_path, + sizeof(remote.sun_path)); + + err = connect(fd, (struct sockaddr *)&remote, + sizeof(remote)); + if (err == 0) { + data->mgmt_channel = g_io_channel_unix_new(fd); + data->mgmt_event_id = + g_io_add_watch(data->mgmt_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, + ov_management_handle_input, + data); + + connman_warn("Connected management socket"); + data->mgmt_timer_id = 0; + return G_SOURCE_REMOVE; + } + close(fd); + } + } + + data->connect_attempts++; + if (data->connect_attempts > 30) { + connman_warn("Unable to connect management socket"); + data->mgmt_timer_id = 0; + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static int ov_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name, + vpn_provider_connect_cb_t cb, const char *dbus_sender, + void *user_data) +{ + const char *tmpdir; + struct ov_private_data *data; + + data = g_try_new0(struct ov_private_data, 1); + if (!data) + return -ENOMEM; + + vpn_provider_set_plugin_data(provider, data); + data->provider = vpn_provider_ref(provider); + data->task = task; + data->dbus_sender = g_strdup(dbus_sender); + data->if_name = g_strdup(if_name); + data->cb = cb; + data->user_data = user_data; + + /* + * We need to use the management interface to provide + * the user credentials and password for decrypting private key. + */ + + /* Use env TMPDIR for creating management socket, fall back to /tmp */ + tmpdir = getenv("TMPDIR"); + if (!tmpdir || !*tmpdir) + tmpdir = "/tmp"; + + /* Set up the path for the management interface */ + data->mgmt_path = g_strconcat(tmpdir, "/connman-vpn-management-", + vpn_provider_get_ident(provider), NULL); + if (unlink(data->mgmt_path) != 0 && errno != ENOENT) { + connman_warn("Unable to unlink management socket %s: %d", + data->mgmt_path, errno); + } + + data->mgmt_timer_id = g_timeout_add(200, + ov_management_connect_timer_cb, data); + + task_append_config_data(provider, task); + + return run_connect(data, cb, user_data); +} + +static void ov_disconnect(struct vpn_provider *provider) +{ + if (!provider) + return; + + connman_agent_cancel(provider); - return err; + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT); } static int ov_device_flags(struct vpn_provider *provider) @@ -464,14 +1145,16 @@ static int ov_device_flags(struct vpn_provider *provider) } if (!g_str_equal(option, "tun")) { - connman_warn("bad OpenVPN.DeviceType value, falling back to tun"); + connman_warn("bad OpenVPN.DeviceType value " + "falling back to tun"); } return IFF_TUN; } static int ov_route_env_parse(struct vpn_provider *provider, const char *key, - int *family, unsigned long *idx, enum vpn_provider_route_type *type) + int *family, unsigned long *idx, + enum vpn_provider_route_type *type) { char *end; const char *start; @@ -497,6 +1180,7 @@ static int ov_route_env_parse(struct vpn_provider *provider, const char *key, static struct vpn_driver vpn_driver = { .notify = ov_notify, .connect = ov_connect, + .disconnect = ov_disconnect, .save = ov_save, .device_flags = ov_device_flags, .route_env_parse = ov_route_env_parse, diff --git a/vpn/plugins/pptp.c b/vpn/plugins/pptp.c index 3dc93b0..5fc861e 100644 --- a/vpn/plugins/pptp.c +++ b/vpn/plugins/pptp.c @@ -137,7 +137,8 @@ static int pptp_notify(DBusMessage *msg, struct vpn_provider *provider) DBG("authentication failure"); vpn_provider_set_string(provider, "PPTP.User", NULL); - vpn_provider_set_string(provider, "PPTP.Password", NULL); + vpn_provider_set_string_hide_value(provider, "PPTP.Password", + NULL); return VPN_STATE_AUTH_FAILURE; } @@ -282,16 +283,28 @@ struct request_input_reply { static void request_input_reply(DBusMessage *reply, void *user_data) { struct request_input_reply *pptp_reply = user_data; + struct pptp_private_data *data; const char *error = NULL; char *username = NULL, *password = NULL; char *key; DBusMessageIter iter, dict; + int err; DBG("provider %p", pptp_reply->provider); - if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { - if (reply) - error = dbus_message_get_error_name(reply); + if (!reply) + goto done; + + data = pptp_reply->user_data; + + err = vpn_agent_check_and_process_reply_error(reply, + pptp_reply->provider, data->task, data->cb, + data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + error = dbus_message_get_error_name(reply); goto done; } @@ -384,6 +397,9 @@ static int request_input(struct vpn_provider *provider, connman_dbus_dict_open(&iter, &dict); + if (vpn_provider_get_authentication_errors(provider)) + vpn_agent_append_auth_failure(&dict, provider, NULL); + vpn_agent_append_user_info(&dict, provider, "PPTP.User"); vpn_agent_append_host_and_name(&dict, provider); @@ -424,13 +440,6 @@ static int run_connect(struct vpn_provider *provider, char *str; int err, i; - host = vpn_provider_get_string(provider, "Host"); - if (!host) { - connman_error("Host not set; cannot enable VPN"); - err = -EINVAL; - goto done; - } - if (!username || !password) { DBG("Cannot connect username %s password %p", username, password); @@ -440,6 +449,7 @@ static int run_connect(struct vpn_provider *provider, DBG("username %s password %p", username, password); + host = vpn_provider_get_string(provider, "Host"); str = g_strdup_printf("%s %s --nolaunchpppd --loglevel 2", PPTP, host); if (!str) { @@ -594,7 +604,12 @@ static int pptp_error_code(struct vpn_provider *provider, int exit_code) static void pptp_disconnect(struct vpn_provider *provider) { - vpn_provider_set_string(provider, "PPTP.Password", NULL); + if (!provider) + return; + + vpn_provider_set_string_hide_value(provider, "PPTP.Password", NULL); + + connman_agent_cancel(provider); } static struct vpn_driver vpn_driver = { diff --git a/vpn/plugins/vpn.c b/vpn/plugins/vpn.c index acede74..e04670c 100644 --- a/vpn/plugins/vpn.c +++ b/vpn/plugins/vpn.c @@ -33,6 +33,9 @@ #include #include #include +#include +#include +#include #include @@ -47,6 +50,7 @@ #include "../vpn-provider.h" #include "vpn.h" +#include "../vpn.h" struct vpn_data { struct vpn_provider *provider; @@ -85,8 +89,10 @@ static int stop_vpn(struct vpn_provider *provider) vpn_driver_data = g_hash_table_lookup(driver_hash, name); if (vpn_driver_data && vpn_driver_data->vpn_driver && - vpn_driver_data->vpn_driver->flags == VPN_FLAG_NO_TUN) + vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN) { + vpn_driver_data->vpn_driver->disconnect(data->provider); return 0; + } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = data->tun_flags | IFF_NO_PI; @@ -284,7 +290,7 @@ static DBusMessage *vpn_notify(struct connman_task *task, * We need to remove first the old address, just * replacing the old address will not work as expected * because the old address will linger in the interface - * and not disapper so the clearing is needed here. + * and not disappear so the clearing is needed here. * * Also the state must change, otherwise the routes * will not be set properly. @@ -415,12 +421,155 @@ exist_err: return ret; } +static gboolean is_numeric(const char *str) +{ + gint i; + + if(!str || !(*str)) + return false; + + for(i = 0; str[i] ; i++) { + if(!g_ascii_isdigit(str[i])) + return false; + } + + return true; +} + +static gint get_gid(const char *group_name) +{ + gint gid = -1; + struct group *grp; + + if(!group_name || !(*group_name)) + return gid; + + if (is_numeric(group_name)) { + gid_t group_id = (gid_t)g_ascii_strtoull(group_name, NULL, 10); + grp = getgrgid(group_id); + } else { + grp = getgrnam(group_name); + } + + if (grp) + gid = grp->gr_gid; + + return gid; +} + +static gint get_uid(const char *user_name) +{ + gint uid = -1; + struct passwd *pw; + + if(!user_name || !(*user_name)) + return uid; + + if (is_numeric(user_name)) { + uid_t user_id = (uid_t)g_ascii_strtoull(user_name, NULL, 10); + pw = getpwuid(user_id); + } else { + pw = getpwnam(user_name); + } + + if (pw) + uid = pw->pw_uid; + + return uid; +} + +static gint get_supplementary_gids(gchar **groups, gid_t **gid_list) +{ + gint group_count = 0; + gid_t *list = NULL; + int i; + + if (groups) { + for(i = 0; groups[i]; i++) { + group_count++; + + list = (gid_t*)g_try_realloc(list, + sizeof(gid_t) * group_count); + + if (!list) { + DBG("cannot allocate supplementary group list"); + break; + } + + list[i] = get_gid(groups[i]); + } + } + + *gid_list = list; + + return group_count; +} + +static void vpn_task_setup(gpointer user_data) +{ + struct vpn_plugin_data *data; + gint uid; + gint gid; + gid_t *gid_list = NULL; + size_t gid_list_size; + const gchar *user; + const gchar *group; + gchar **suppl_groups; + + data = user_data; + user = vpn_settings_get_binary_user(data); + group = vpn_settings_get_binary_group(data); + suppl_groups = vpn_settings_get_binary_supplementary_groups(data); + + uid = get_uid(user); + gid = get_gid(group); + gid_list_size = get_supplementary_gids(suppl_groups, &gid_list); + + DBG("vpn_task_setup uid:%d gid:%d supplementary group list size:%zu", + uid, gid, gid_list_size); + + + /* Change group if proper group name was set, requires CAP_SETGID.*/ + if (gid > 0 && setgid(gid)) + connman_error("error setting gid %d %s", gid, strerror(errno)); + + /* Set the supplementary groups if list exists, requires CAP_SETGID. */ + if (gid_list_size && gid_list && setgroups(gid_list_size, gid_list)) + connman_error("error setting gid list %s", strerror(errno)); + + /* Change user for the task if set, requires CAP_SETUID */ + if (uid > 0 && setuid(uid)) + connman_error("error setting uid %d %s", uid, strerror(errno)); +} + + +static gboolean update_provider_state(gpointer data) +{ + struct vpn_provider *provider = data; + struct vpn_data *vpn_data; + int index; + + DBG(""); + + vpn_data = vpn_provider_get_data(provider); + + index = vpn_provider_get_index(provider); + DBG("index to watch %d", index); + vpn_provider_ref(provider); + vpn_data->watch = vpn_rtnl_add_newlink_watch(index, + vpn_newlink, provider); + connman_inet_ifup(index); + + return FALSE; +} + static int vpn_connect(struct vpn_provider *provider, vpn_provider_connect_cb_t cb, const char *dbus_sender, void *user_data) { struct vpn_data *data = vpn_provider_get_data(provider); struct vpn_driver_data *vpn_driver_data; + struct vpn_plugin_data *vpn_plugin_data; const char *name; int ret = 0, tun_flags = IFF_TUN; enum vpn_state state = VPN_STATE_UNKNOWN; @@ -469,7 +618,7 @@ static int vpn_connect(struct vpn_provider *provider, goto exist_err; } - if (vpn_driver_data->vpn_driver->flags != VPN_FLAG_NO_TUN) { + if (!(vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN)) { if (vpn_driver_data->vpn_driver->device_flags) { tun_flags = vpn_driver_data->vpn_driver->device_flags(provider); } @@ -478,7 +627,30 @@ static int vpn_connect(struct vpn_provider *provider, goto exist_err; } - data->task = connman_task_create(vpn_driver_data->program); + + if (vpn_driver_data && vpn_driver_data->vpn_driver && + vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_DAEMON) { + + ret = vpn_driver_data->vpn_driver->connect(provider, + NULL, NULL, NULL, NULL, NULL); + if (ret) { + stop_vpn(provider); + goto exist_err; + } + + DBG("%s started with dev %s", + vpn_driver_data->provider_driver.name, data->if_name); + + data->state = VPN_STATE_CONNECT; + + g_timeout_add(1, update_provider_state, provider); + return -EINPROGRESS; + } + + vpn_plugin_data = + vpn_settings_get_vpn_plugin_config(vpn_driver_data->name); + data->task = connman_task_create(vpn_driver_data->program, + vpn_task_setup, vpn_plugin_data); if (!data->task) { ret = -ENOMEM; @@ -553,7 +725,15 @@ static int vpn_disconnect(struct vpn_provider *provider) } data->state = VPN_STATE_DISCONNECT; - connman_task_stop(data->task); + + if (!vpn_driver_data->vpn_driver->disconnect) { + DBG("Driver has no disconnect() implementation, set provider " + "state to disconnect."); + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT); + } + + if (data->task) + connman_task_stop(data->task); return 0; } @@ -577,7 +757,8 @@ static int vpn_remove(struct vpn_provider *provider) data->watch = 0; } - connman_task_stop(data->task); + if (data->task) + connman_task_stop(data->task); g_usleep(G_USEC_PER_SEC); stop_vpn(provider); @@ -643,6 +824,9 @@ int vpn_register(const char *name, struct vpn_driver *vpn_driver, data->name = name; data->program = program; + if (vpn_settings_parse_vpn_plugin_config(data->name) != 0) + DBG("No configuration provided for VPN plugin %s", data->name); + data->vpn_driver = vpn_driver; data->provider_driver.name = name; @@ -681,6 +865,7 @@ void vpn_unregister(const char *name) return; vpn_provider_driver_unregister(&data->provider_driver); + vpn_settings_delete_vpn_plugin_config(name); g_hash_table_remove(driver_hash, name); diff --git a/vpn/plugins/vpn.h b/vpn/plugins/vpn.h index 265fd82..71e04f6 100644 --- a/vpn/plugins/vpn.h +++ b/vpn/plugins/vpn.h @@ -28,7 +28,8 @@ extern "C" { #endif -#define VPN_FLAG_NO_TUN 1 +#define VPN_FLAG_NO_TUN 1 +#define VPN_FLAG_NO_DAEMON 2 enum vpn_state { VPN_STATE_UNKNOWN = 0, diff --git a/vpn/plugins/vpnc.c b/vpn/plugins/vpnc.c index af9dbe7..8350fc3 100644 --- a/vpn/plugins/vpnc.c +++ b/vpn/plugins/vpnc.c @@ -39,15 +39,18 @@ #include #include #include +#include +#include +#include #include "../vpn-provider.h" +#include "../vpn-agent.h" #include "vpn.h" +#include "../vpn.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) -static DBusConnection *connection; - enum { OPT_STRING = 1, OPT_BOOLEAN = 2, @@ -83,25 +86,104 @@ struct { true }, }; +struct vc_private_data { + struct vpn_provider *provider; + struct connman_task *task; + char *if_name; + vpn_provider_connect_cb_t cb; + void *user_data; + int err_ch_id; + GIOChannel *err_ch; +}; + +static void vc_connect_done(struct vc_private_data *data, int err) +{ + DBG("data %p err %d", data, err); + + if (data && data->cb) { + vpn_provider_connect_cb_t cb = data->cb; + void *user_data = data->user_data; + + /* Make sure we don't invoke this callback twice */ + data->cb = NULL; + data->user_data = NULL; + cb(data->provider, user_data, err); + } +} + +static void close_io_channel(struct vc_private_data *data, GIOChannel *channel) +{ + if (!data || !channel) + return; + + if (data->err_ch == channel) { + DBG("closing stderr"); + + if (data->err_ch_id) { + g_source_remove(data->err_ch_id); + data->err_ch_id = 0; + } + + if (!data->err_ch) + return; + + g_io_channel_shutdown(data->err_ch, FALSE, NULL); + g_io_channel_unref(data->err_ch); + + data->err_ch = NULL; + } +} + +static void free_private_data(struct vc_private_data *data) +{ + DBG("data %p", data); + + if (!data || !data->provider) + return; + + DBG("provider %p", data->provider); + + if (vpn_provider_get_plugin_data(data->provider) == data) + vpn_provider_set_plugin_data(data->provider, NULL); + + vpn_provider_unref(data->provider); + + g_free(data->if_name); + g_free(data); +} + static int vc_notify(DBusMessage *msg, struct vpn_provider *provider) { DBusMessageIter iter, dict; char *address = NULL, *netmask = NULL, *gateway = NULL; struct connman_ipaddress *ipaddress; const char *reason, *key, *value; + struct vc_private_data *data; + int type; + + data = vpn_provider_get_plugin_data(provider); dbus_message_iter_init(msg, &iter); + type = dbus_message_iter_get_arg_type(&iter); + if (type != DBUS_TYPE_STRING) { + DBG("invalid D-Bus arg type %d", type); + return VPN_STATE_FAILURE; + } + dbus_message_iter_get_basic(&iter, &reason); dbus_message_iter_next(&iter); if (!provider) { connman_error("No provider found"); + vc_connect_done(data, ENOENT); return VPN_STATE_FAILURE; } - if (strcmp(reason, "connect")) + if (g_strcmp0(reason, "connect")) { + vc_connect_done(data, EIO); return VPN_STATE_DISCONNECT; + } dbus_message_iter_recurse(&iter, &dict); @@ -109,8 +191,18 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider) DBusMessageIter entry; dbus_message_iter_recurse(&dict, &entry); + + type = dbus_message_iter_get_arg_type(&entry); + if (type != DBUS_TYPE_STRING) + continue; + dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); + + type = dbus_message_iter_get_arg_type(&entry); + if (type != DBUS_TYPE_STRING) + continue; + dbus_message_iter_get_basic(&entry, &value); DBG("%s = %s", key, value); @@ -143,7 +235,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(address); g_free(netmask); g_free(gateway); - + vc_connect_done(data, EIO); return VPN_STATE_FAILURE; } @@ -155,6 +247,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(gateway); connman_ipaddress_free(ipaddress); + vc_connect_done(data, 0); return VPN_STATE_CONNECT; } @@ -263,27 +356,109 @@ static int vc_save(struct vpn_provider *provider, GKeyFile *keyfile) return 0; } -static int vc_connect(struct vpn_provider *provider, - struct connman_task *task, const char *if_name, - vpn_provider_connect_cb_t cb, const char *dbus_sender, - void *user_data) +static void vc_died(struct connman_task *task, int exit_code, void *user_data) { - const char *option; - int err = 0, fd; + struct vc_private_data *data = user_data; - option = vpn_provider_get_string(provider, "Host"); - if (!option) { - connman_error("Host not set; cannot enable VPN"); - err = -EINVAL; - goto done; + DBG("task %p data %p exit_code %d user_data %p", task, data, exit_code, + user_data); + + if (!data) + return; + + if (data->provider) { + connman_agent_cancel(data->provider); + + if (task) + vpn_died(task, exit_code, data->provider); } - option = vpn_provider_get_string(provider, "VPNC.IPSec.ID"); - if (!option) { - connman_error("Group not set; cannot enable VPN"); - err = -EINVAL; - goto done; + + free_private_data(data); +} + +static gboolean io_channel_cb(GIOChannel *source, GIOCondition condition, + gpointer user_data) +{ + struct vc_private_data *data; + const char *auth_failures[] = { + VPNC ": hash comparison failed", + VPNC ": authentication unsuccessful", + VPNC ": expected xauth packet; rejected", + NULL + }; + const char *conn_failures[] = { + VPNC ": unknown host", + VPNC ": no response from target", + VPNC ": receiving packet: No route to host", + NULL + }; + char *str; + int i; + + data = user_data; + + if ((condition & G_IO_IN) && + g_io_channel_read_line(source, &str, NULL, NULL, NULL) == + G_IO_STATUS_NORMAL) { + str[strlen(str) - 1] = '\0'; + + for (i = 0; auth_failures[i]; i++) { + if (g_str_has_prefix(str, auth_failures[i])) { + DBG("authentication failed: %s", str); + + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + } + } + + for (i = 0; conn_failures[i]; i++) { + if (g_str_has_prefix(str, conn_failures[i])) { + DBG("connection failed: %s", str); + + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_CONNECT_FAILED); + } + } + + g_free(str); + } else if (condition & (G_IO_ERR | G_IO_HUP)) { + DBG("Channel termination"); + close_io_channel(data, source); + return G_SOURCE_REMOVE; } + return G_SOURCE_CONTINUE; +} + +static int run_connect(struct vc_private_data *data) +{ + struct vpn_provider *provider; + struct connman_task *task; + const char *credentials[] = {"VPNC.IPSec.Secret", "VPNC.Xauth.Username", + "VPNC.Xauth.Password", NULL}; + const char *if_name; + const char *option; + int err; + int fd_in; + int fd_err; + int i; + + provider = data->provider; + task = data->task; + if_name = data->if_name; + + DBG("provider %p task %p interface %s user_data %p", provider, task, + if_name, data->user_data); + + /* + * Change to use C locale, options should be in ASCII according to + * documentation. To be on the safe side, set both LANG and LC_ALL. + * This is required especially when the VPNC processe is ran using an + * user other than root. + */ + connman_task_add_variable(task,"LANG", "C"); + connman_task_add_variable(task,"LC_ALL", "C"); + connman_task_add_argument(task, "--non-inter", NULL); connman_task_add_argument(task, "--no-detach", NULL); @@ -298,8 +473,7 @@ static int vc_connect(struct vpn_provider *provider, connman_task_add_argument(task, "--ifmode", "tun"); } - connman_task_add_argument(task, "--script", - SCRIPTDIR "/openconnect-script"); + connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script"); option = vpn_provider_get_string(provider, "VPNC.Debug"); if (option) @@ -307,25 +481,354 @@ static int vc_connect(struct vpn_provider *provider, connman_task_add_argument(task, "-", NULL); - err = connman_task_run(task, vpn_died, provider, - &fd, NULL, NULL); + err = connman_task_run(data->task, vc_died, data, &fd_in, NULL, + &fd_err); if (err < 0) { connman_error("vpnc failed to start"); err = -EIO; goto done; } - err = vc_write_config_data(provider, fd); + err = vc_write_config_data(provider, fd_in); + + if (err) { + DBG("config write error %s", strerror(err)); + goto done; + } + + err = -EINPROGRESS; - close(fd); + data->err_ch = g_io_channel_unix_new(fd_err); + data->err_ch_id = g_io_add_watch(data->err_ch, + G_IO_IN | G_IO_ERR | G_IO_HUP, + (GIOFunc)io_channel_cb, data); done: - if (cb) - cb(provider, user_data, err); + close(fd_in); + + /* + * Clear out credentials if they are non-immutable. If this is called + * directly from vc_connect() all credentials are read from config and + * are set as immutable, so no change is done. In case a VPN agent is + * used these values should be reset to "-" in order to retrieve them + * from VPN agent next time VPN connection is established. This supports + * then partially defined credentials in .config and some can be + * retrieved using an agent. + */ + for (i = 0; credentials[i]; i++) { + const char *key = credentials[i]; + if (!vpn_provider_get_string_immutable(provider, key)) + vpn_provider_set_string(provider, key, "-"); + } return err; } +static void request_input_append_mandatory(DBusMessageIter *iter, + void *user_data) +{ + char *str = "string"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); + + if (!user_data) + return; + + str = user_data; + connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str); +} + +static void request_input_append_password(DBusMessageIter *iter, + void *user_data) +{ + char *str = "password"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); + + if (!user_data) + return; + + str = user_data; + connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str); +} + +static void request_input_append_informational(DBusMessageIter *iter, + void *user_data) +{ + char *str = "password"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "informational"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); + + if (!user_data) + return; + + str = user_data; + connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str); +} + +static void request_input_append_to_dict(struct vpn_provider *provider, + DBusMessageIter *dict, + connman_dbus_append_cb_t function_cb, const char *key) +{ + const char *str; + bool immutable = false; + + if (!provider || !dict || !function_cb || !key) + return; + + str = vpn_provider_get_string(provider, key); + + /* If value is "-", it is cleared by VPN agent */ + if (!g_strcmp0(str, "-")) + str = NULL; + + if (str) + immutable = vpn_provider_get_string_immutable(provider, key); + + if (immutable) { + /* Hide immutable password types */ + if (function_cb == request_input_append_password) + str = "********"; + + /* Send immutable as informational */ + function_cb = request_input_append_informational; + } + + connman_dbus_dict_append_dict(dict, key, function_cb, (void *)str); +} + +static void request_input_credentials_reply(DBusMessage *reply, void *user_data) +{ + struct vc_private_data *data = user_data; + char *secret = NULL, *username = NULL, *password = NULL; + const char *key; + DBusMessageIter iter, dict; + int err; + + DBG("provider %p", data->provider); + + if (!reply) + goto err; + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + return; + } + + if (!vpn_agent_check_reply_has_dict(reply)) + goto err; + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "VPNC.IPSec.Secret")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &secret); + vpn_provider_set_string_hide_value(data->provider, + key, secret); + + } else if (g_str_equal(key, "VPNC.Xauth.Username")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &username); + vpn_provider_set_string(data->provider, key, username); + + } else if (g_str_equal(key, "VPNC.Xauth.Password")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &password); + vpn_provider_set_string_hide_value(data->provider, key, + password); + } + + dbus_message_iter_next(&dict); + } + + if (!secret || !username || !password) + goto err; + + err = run_connect(data); + if (err != -EINPROGRESS) + goto err; + + return; + +err: + vc_connect_done(data, EACCES); +} + +static int request_input_credentials(struct vc_private_data *data, + const char* dbus_sender) +{ + DBusMessage *message; + const char *path, *agent_sender, *agent_path; + DBusMessageIter iter; + DBusMessageIter dict; + int err; + void *agent; + + if (!data || !data->provider) + return -ENOENT; + + DBG("data %p provider %p sender %s", data, data->provider, dbus_sender); + + agent = connman_agent_get_info(dbus_sender, &agent_sender, &agent_path); + if (!agent || !agent_path) + return -ESRCH; + + message = dbus_message_new_method_call(agent_sender, agent_path, + VPN_AGENT_INTERFACE, + "RequestInput"); + if (!message) + return -ENOMEM; + + dbus_message_iter_init_append(message, &iter); + + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + connman_dbus_dict_open(&iter, &dict); + + if (vpn_provider_get_authentication_errors(data->provider)) + vpn_agent_append_auth_failure(&dict, data->provider, NULL); + + request_input_append_to_dict(data->provider, &dict, + request_input_append_password, + "VPNC.IPSec.Secret"); + request_input_append_to_dict(data->provider, &dict, + request_input_append_mandatory, + "VPNC.Xauth.Username"); + request_input_append_to_dict(data->provider, &dict, + request_input_append_password, + "VPNC.Xauth.Password"); + + vpn_agent_append_host_and_name(&dict, data->provider); + + connman_dbus_dict_close(&iter, &dict); + + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), + request_input_credentials_reply, data, agent); + + dbus_message_unref(message); + + if (err < 0 && err != -EBUSY) { + DBG("error %d sending agent request", err); + return err; + } + + return -EINPROGRESS; +} + +static int vc_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name, + vpn_provider_connect_cb_t cb, const char *dbus_sender, + void *user_data) +{ + struct vc_private_data *data; + const char *option; + bool username_set = false; + bool password_set = false; + bool ipsec_secret_set = false; + int err; + + DBG("provider %p if_name %s user_data %p", provider, if_name, user_data); + + option = vpn_provider_get_string(provider, "VPNC.IPSec.ID"); + if (!option) { + connman_error("Group not set; cannot enable VPN"); + return -EINVAL; + } + + option = vpn_provider_get_string(provider, "VPNC.IPSec.Secret"); + if (option && *option && g_strcmp0(option, "-")) + ipsec_secret_set = true; + + option = vpn_provider_get_string(provider, "VPNC.Xauth.Username"); + if (option && *option && g_strcmp0(option, "-")) + username_set = true; + + option = vpn_provider_get_string(provider, "VPNC.Xauth.Password"); + if (option && *option && g_strcmp0(option, "-")) + password_set = true; + + data = g_try_new0(struct vc_private_data, 1); + if (!data) + return -ENOMEM; + + vpn_provider_set_plugin_data(provider, data); + data->provider = vpn_provider_ref(provider); + data->task = task; + data->if_name = g_strdup(if_name); + data->cb = cb; + data->user_data = user_data; + + if (!ipsec_secret_set || !username_set || !password_set) { + err = request_input_credentials(data, dbus_sender); + if (err != -EINPROGRESS) { + vc_connect_done(data, ECONNABORTED); + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_LOGIN_FAILED); + free_private_data(data); + } + + return err; + } + + return run_connect(data); +} + +static void vc_disconnect(struct vpn_provider *provider) +{ + if (!provider) + return; + + connman_agent_cancel(provider); +} + static int vc_error_code(struct vpn_provider *provider, int exit_code) { switch (exit_code) { @@ -361,6 +864,7 @@ static int vc_device_flags(struct vpn_provider *provider) static struct vpn_driver vpn_driver = { .notify = vc_notify, .connect = vc_connect, + .disconnect = vc_disconnect, .error_code = vc_error_code, .save = vc_save, .device_flags = vc_device_flags, @@ -368,16 +872,12 @@ static struct vpn_driver vpn_driver = { static int vpnc_init(void) { - connection = connman_dbus_get_connection(); - return vpn_register("vpnc", &vpn_driver, VPNC); } static void vpnc_exit(void) { vpn_unregister("vpnc"); - - dbus_connection_unref(connection); } CONNMAN_PLUGIN_DEFINE(vpnc, "vpnc plugin", VERSION, diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c new file mode 100644 index 0000000..de2dbda --- /dev/null +++ b/vpn/plugins/wireguard.c @@ -0,0 +1,404 @@ +/* + * ConnMan VPN daemon + * + * Copyright (C) 2019 Daniel Wagner. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../vpn-provider.h" +#include "../vpn.h" + +#include "vpn.h" +#include "wireguard.h" + +static int parse_key(const char *str, wg_key key) +{ + unsigned char *buf; + size_t len; + + buf = g_base64_decode(str, &len); + + if (len != 32) { + g_free(buf); + return -EINVAL; + } + + memcpy(key, buf, 32); + + g_free(buf); + return 0; +} + +static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer) +{ + struct wg_allowedip *curaip, *allowedip; + char buf[INET6_ADDRSTRLEN]; + char **tokens, **toks; + char *send; + int i; + + curaip = NULL; + tokens = g_strsplit(allowed_ips, ", ", -1); + for (i = 0; tokens[i]; i++) { + toks = g_strsplit(tokens[i], "/", -1); + if (g_strv_length(toks) != 2) { + DBG("Ignore AllowedIPs value %s", tokens[i]); + g_strfreev(toks); + continue; + } + + allowedip = g_malloc0(sizeof(*allowedip)); + + if (inet_pton(AF_INET, toks[0], buf) == 1) { + allowedip->family = AF_INET; + memcpy(&allowedip->ip4, buf, sizeof(allowedip->ip4)); + } else if (inet_pton(AF_INET6, toks[0], buf) == 1) { + allowedip->family = AF_INET6; + memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6)); + } else { + DBG("Ignore AllowedIPs value %s", tokens[i]); + g_free(allowedip); + g_strfreev(toks); + continue; + } + + allowedip->cidr = g_ascii_strtoull(toks[1], &send, 10); + + if (!curaip) + peer->first_allowedip = allowedip; + else + curaip->next_allowedip = allowedip; + + curaip = allowedip; + } + + peer->last_allowedip = curaip; + g_strfreev(tokens); + + return 0; +} + +static int parse_endpoint(const char *host, const char *port, wg_peer *peer) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sk; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if (getaddrinfo(host, port, &hints, &result) < 0) { + DBG("Failed to resolve host address"); + return -EINVAL; + } + + for (rp = result; rp; rp = rp->ai_next) { + sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sk < 0) + continue; + if (connect(sk, rp->ai_addr, rp->ai_addrlen) != -1) { + /* success */ + close(sk); + break; + } + + close(sk); + } + + if (!rp) { + freeaddrinfo(result); + return -EINVAL; + } + + memcpy(&peer->endpoint.addr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(result); + + return 0; +} + +static int parse_address(const char *address, const char *gateway, + struct connman_ipaddress **ipaddress) +{ + char buf[INET6_ADDRSTRLEN]; + unsigned char prefixlen; + char **tokens; + char *end, *netmask; + int err; + + tokens = g_strsplit(address, "/", -1); + if (g_strv_length(tokens) != 2) { + g_strfreev(tokens); + return -EINVAL; + } + + prefixlen = g_ascii_strtoull(tokens[1], &end, 10); + + if (inet_pton(AF_INET, tokens[0], buf) == 1) { + netmask = g_strdup_printf("%d.%d.%d.%d", + ((0xffffffff << (32 - prefixlen)) >> 24) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 16) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 8) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 0) & 0xff); + + *ipaddress = connman_ipaddress_alloc(AF_INET); + err = connman_ipaddress_set_ipv4(*ipaddress, tokens[0], + netmask, gateway); + g_free(netmask); + } else if (inet_pton(AF_INET6, tokens[0], buf) == 1) { + *ipaddress = connman_ipaddress_alloc(AF_INET6); + err = connman_ipaddress_set_ipv6(*ipaddress, tokens[0], + prefixlen, gateway); + } else { + DBG("Invalid Wireguard.Address value"); + err = -EINVAL; + } + + g_strfreev(tokens); + if (err) + connman_ipaddress_free(*ipaddress); + + return err; +} + +struct ifname_data { + char *ifname; + bool found; +}; + +static void ifname_check_cb(int index, void *user_data) +{ + struct ifname_data *data = (struct ifname_data *)user_data; + char *ifname; + + ifname = connman_inet_ifname(index); + + if (!g_strcmp0(ifname, data->ifname)) + data->found = true; +} + +static char *get_ifname(void) +{ + struct ifname_data data; + int i; + + for (i = 0; i < 256; i++) { + data.ifname = g_strdup_printf("wg%d", i); + data.found = false; + __vpn_ipconfig_foreach(ifname_check_cb, &data); + + if (!data.found) + return data.ifname; + + g_free(data.ifname); + } + + return NULL; +} + +struct wireguard_info { + struct wg_device device; + struct wg_peer peer; +}; + +static int wg_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name, + vpn_provider_connect_cb_t cb, + const char *dbus_sender, void *user_data) +{ + struct connman_ipaddress *ipaddress = NULL; + struct wireguard_info *info; + const char *option, *gateway; + char *ifname; + int err = -EINVAL; + + info = g_malloc0(sizeof(struct wireguard_info)); + info->peer.flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS; + info->device.flags = WGDEVICE_HAS_PRIVATE_KEY; + info->device.first_peer = &info->peer; + info->device.last_peer = &info->peer; + + vpn_provider_set_plugin_data(provider, info); + + option = vpn_provider_get_string(provider, "WireGuard.ListenPort"); + if (option) { + char *end; + info->device.listen_port = g_ascii_strtoull(option, &end, 10); + info->device.flags |= WGDEVICE_HAS_LISTEN_PORT; + } + + option = vpn_provider_get_string(provider, "WireGuard.DNS"); + if (option) { + err = vpn_provider_set_nameservers(provider, option); + if (err) + goto done; + } + + option = vpn_provider_get_string(provider, "WireGuard.PrivateKey"); + if (!option) { + DBG("WireGuard.PrivateKey is missing"); + goto done; + } + err = parse_key(option, info->device.private_key); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.PublicKey"); + if (!option) { + DBG("WireGuard.PublicKey is missing"); + goto done; + } + err = parse_key(option, info->peer.public_key); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.PresharedKey"); + if (option) { + info->peer.flags |= WGPEER_HAS_PRESHARED_KEY; + err = parse_key(option, info->peer.preshared_key); + if (err) + goto done; + } + + option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs"); + if (!option) { + DBG("WireGuard.AllowedIPs is missing"); + goto done; + } + err = parse_allowed_ips(option, &info->peer); + if (err) + goto done; + + option = vpn_provider_get_string(provider, + "WireGuard.PersistentKeepalive"); + if (option) { + char *end; + info->peer.persistent_keepalive_interval = + g_ascii_strtoull(option, &end, 10); + info->peer.flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL; + } + + option = vpn_provider_get_string(provider, "WireGuard.EndpointPort"); + if (!option) + option = "51820"; + + gateway = vpn_provider_get_string(provider, "Host"); + err = parse_endpoint(gateway, option, &info->peer); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.Address"); + if (!option) { + DBG("Missing WireGuard.Address configuration"); + goto done; + } + err = parse_address(option, gateway, &ipaddress); + if (err) + goto done; + + ifname = get_ifname(); + if (!ifname) { + DBG("Failed to find an usable device name"); + err = -ENOENT; + goto done; + } + stpncpy(info->device.name, ifname, sizeof(info->device.name)); + g_free(ifname); + + err = wg_add_device(info->device.name); + if (err) { + DBG("Failed to creating WireGuard device %s", info->device.name); + goto done; + } + + err = wg_set_device(&info->device); + if (err) { + DBG("Failed to configure WireGuard device %s", info->device.name); + wg_del_device(info->device.name); + } + + vpn_set_ifname(provider, info->device.name); + if (ipaddress) + vpn_provider_set_ipaddress(provider, ipaddress); + +done: + if (cb) + cb(provider, user_data, err); + + connman_ipaddress_free(ipaddress); + + return err; +} + +static void wg_disconnect(struct vpn_provider *provider) +{ + struct wireguard_info *info; + + info = vpn_provider_get_plugin_data(provider); + if (!info) + return; + vpn_provider_set_plugin_data(provider, NULL); + + wg_del_device(info->device.name); + + g_free(info); +} + +static struct vpn_driver vpn_driver = { + .flags = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON, + .connect = wg_connect, + .disconnect = wg_disconnect, +}; + +static int wg_init(void) +{ + return vpn_register("wireguard", &vpn_driver, NULL); +} + +static void wg_exit(void) +{ + vpn_unregister("wireguard"); +} + +CONNMAN_PLUGIN_DEFINE(wireguard, "WireGuard VPN plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, wg_init, wg_exit) diff --git a/vpn/plugins/wireguard.h b/vpn/plugins/wireguard.h new file mode 100644 index 0000000..e7a1bbf --- /dev/null +++ b/vpn/plugins/wireguard.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#ifndef WIREGUARD_H +#define WIREGUARD_H + +#include +#include +#include +#include +#include +#include + +typedef uint8_t wg_key[32]; +typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1]; + +/* Cross platform __kernel_timespec */ +struct timespec64 { + int64_t tv_sec; + int64_t tv_nsec; +}; + +typedef struct wg_allowedip { + uint16_t family; + union { + struct in_addr ip4; + struct in6_addr ip6; + }; + uint8_t cidr; + struct wg_allowedip *next_allowedip; +} wg_allowedip; + +enum wg_peer_flags { + WGPEER_REMOVE_ME = 1U << 0, + WGPEER_REPLACE_ALLOWEDIPS = 1U << 1, + WGPEER_HAS_PUBLIC_KEY = 1U << 2, + WGPEER_HAS_PRESHARED_KEY = 1U << 3, + WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4 +}; + +typedef struct wg_peer { + enum wg_peer_flags flags; + + wg_key public_key; + wg_key preshared_key; + + union { + struct sockaddr addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + } endpoint; + + struct timespec64 last_handshake_time; + uint64_t rx_bytes, tx_bytes; + uint16_t persistent_keepalive_interval; + + struct wg_allowedip *first_allowedip, *last_allowedip; + struct wg_peer *next_peer; +} wg_peer; + +enum wg_device_flags { + WGDEVICE_REPLACE_PEERS = 1U << 0, + WGDEVICE_HAS_PRIVATE_KEY = 1U << 1, + WGDEVICE_HAS_PUBLIC_KEY = 1U << 2, + WGDEVICE_HAS_LISTEN_PORT = 1U << 3, + WGDEVICE_HAS_FWMARK = 1U << 4 +}; + +typedef struct wg_device { + char name[IFNAMSIZ]; + uint32_t ifindex; + + enum wg_device_flags flags; + + wg_key public_key; + wg_key private_key; + + uint32_t fwmark; + uint16_t listen_port; + + struct wg_peer *first_peer, *last_peer; +} wg_device; + +#define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1) +#define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer) +#define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip) + +int wg_set_device(wg_device *dev); +int wg_get_device(wg_device **dev, const char *device_name); +int wg_add_device(const char *device_name); +int wg_del_device(const char *device_name); +void wg_free_device(wg_device *dev); +char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */ +void wg_key_to_base64(wg_key_b64_string base64, const wg_key key); +int wg_key_from_base64(wg_key key, const wg_key_b64_string base64); +bool wg_key_is_zero(const wg_key key); +void wg_generate_public_key(wg_key public_key, const wg_key private_key); +void wg_generate_private_key(wg_key private_key); +void wg_generate_preshared_key(wg_key preshared_key); + +#endif diff --git a/vpn/vpn-agent.c b/vpn/vpn-agent.c index b0b582b..ab6fea5 100644 --- a/vpn/vpn-agent.c +++ b/vpn/vpn-agent.c @@ -3,6 +3,7 @@ * Connection Manager * * Copyright (C) 2012 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Jolla Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -30,6 +31,8 @@ #include #include #include +#include +#include #include #include "vpn-agent.h" @@ -102,6 +105,7 @@ void vpn_agent_append_host_and_name(DBusMessageIter *iter, struct user_info_data { struct vpn_provider *provider; const char *username_str; + const char *type_str; }; static void request_input_append_user_info(DBusMessageIter *iter, @@ -109,10 +113,10 @@ static void request_input_append_user_info(DBusMessageIter *iter, { struct user_info_data *data = user_data; struct vpn_provider *provider = data->provider; - const char *str = "string"; + const char *str = NULL; connman_dbus_dict_append_basic(iter, "Type", - DBUS_TYPE_STRING, &str); + DBUS_TYPE_STRING, &data->type_str); str = "mandatory"; connman_dbus_dict_append_basic(iter, "Requirement", DBUS_TYPE_STRING, &str); @@ -134,12 +138,150 @@ void vpn_agent_append_user_info(DBusMessageIter *iter, .username_str = username_str }; + data.type_str = "string"; connman_dbus_dict_append_dict(iter, "Username", request_input_append_user_info, &data); data.username_str = NULL; + data.type_str = "password"; connman_dbus_dict_append_dict(iter, "Password", request_input_append_user_info, &data); } + +static void request_input_append_flag(DBusMessageIter *iter, + void *user_data) +{ + dbus_bool_t data = (dbus_bool_t)GPOINTER_TO_INT(user_data); + const char *str = NULL; + + str = "boolean"; + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + + str = "control"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); + + connman_dbus_dict_append_basic(iter, "Value", + DBUS_TYPE_BOOLEAN, &data); +} + +void vpn_agent_append_allow_credential_storage(DBusMessageIter *iter, + bool allow) +{ + connman_dbus_dict_append_dict(iter, "AllowStoreCredentials", + request_input_append_flag, + GINT_TO_POINTER(allow)); +} + +void vpn_agent_append_allow_credential_retrieval(DBusMessageIter *iter, + bool allow) +{ + connman_dbus_dict_append_dict(iter, "AllowRetrieveCredentials", + request_input_append_flag, + GINT_TO_POINTER(allow)); +} + +void vpn_agent_append_keep_credentials(DBusMessageIter *iter, bool allow) +{ + connman_dbus_dict_append_dict(iter, "KeepCredentials", + request_input_append_flag, + GINT_TO_POINTER(allow)); +} + +struct failure_data { + struct vpn_provider *provider; + const char* type_str; + const char *key; + const char* str; +}; + +static void request_input_append_failure(DBusMessageIter *iter, + void *user_data) +{ + struct failure_data *data; + const char *str; + + data = user_data; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &data->type_str); + str = "informational"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); + + str = data->str; + + /* Try to get information from provider about error */ + if (!str) + str = vpn_provider_get_string(data->provider, data->key); + + if (str) + connman_dbus_dict_append_basic(iter, "Value", + DBUS_TYPE_STRING, &str); +} + +void vpn_agent_append_auth_failure(DBusMessageIter *iter, + struct vpn_provider *provider, + const char* information) +{ + struct failure_data data; + unsigned int value; + + /* Skip if there are no auth errors */ + value = vpn_provider_get_authentication_errors(provider); + if (!value) + return; + + data.provider = provider; + data.type_str = "string"; + data.key = "VpnAgent.AuthFailure"; + data.str = information; + + connman_dbus_dict_append_dict(iter, data.key, + request_input_append_failure, &data); +} + +int vpn_agent_check_and_process_reply_error(DBusMessage *reply, + struct vpn_provider *provider, + struct connman_task *task, + vpn_provider_connect_cb_t cb, void *user_data) +{ + DBusError error; + int err; + + if (!reply || !provider) + return EINVAL; + + dbus_error_init(&error); + + if (!dbus_set_error_from_message(&error, reply)) + return 0; + + if (!g_strcmp0(error.name, VPN_AGENT_INTERFACE ".Error.Canceled")) + err = ECANCELED; + else if (!g_strcmp0(error.name, "org.freedesktop.DBus.Error.Timeout")) + err = ETIMEDOUT; + else if (!g_strcmp0(error.name, "org.freedesktop.DBus.Error.NoReply")) + err = ENOMSG; + else + err = EACCES; + + dbus_error_free(&error); + + if (cb) + cb(provider, user_data, err); + + if (task) + connman_task_stop(task); + + /* + * VPN agent dialog cancel, timeout, broken connection should set the + * VPN back to idle state + */ + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + + return err; +} diff --git a/vpn/vpn-agent.h b/vpn/vpn-agent.h index c7328d7..dc79766 100644 --- a/vpn/vpn-agent.h +++ b/vpn/vpn-agent.h @@ -38,6 +38,18 @@ bool vpn_agent_check_reply_has_dict(DBusMessage *reply); void vpn_agent_append_user_info(DBusMessageIter *iter, struct vpn_provider *provider, const char *username_str); +void vpn_agent_append_allow_credential_storage(DBusMessageIter *iter, + bool allow); +void vpn_agent_append_allow_credential_retrieval(DBusMessageIter *iter, + bool allow); +void vpn_agent_append_keep_credentials(DBusMessageIter *iter, bool allow); +void vpn_agent_append_auth_failure(DBusMessageIter *iter, + struct vpn_provider *provider, + const char *information); +int vpn_agent_check_and_process_reply_error(DBusMessage *reply, + struct vpn_provider *provider, + struct connman_task *task, + vpn_provider_connect_cb_t cb, void *user_data); #ifdef __cplusplus } diff --git a/vpn/vpn-config.c b/vpn/vpn-config.c index c88a99a..f56e51e 100644 --- a/vpn/vpn-config.c +++ b/vpn/vpn-config.c @@ -261,9 +261,6 @@ static int load_provider(GKeyFile *keyfile, const char *group, config_provider->config_entry = g_strdup_printf("provider_%s", config_provider->ident); - g_hash_table_insert(config->provider_table, - config_provider->ident, config_provider); - err = __vpn_provider_create_from_config( config_provider->setting_strings, config_provider->config_ident, @@ -274,6 +271,10 @@ static int load_provider(GKeyFile *keyfile, const char *group, goto err; } + g_hash_table_insert(config->provider_table, config_provider->ident, + config_provider); + + connman_info("Added provider configuration %s", config_provider->ident); return 0; diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c index dd54ac0..5ce9328 100644 --- a/vpn/vpn-provider.c +++ b/vpn/vpn-provider.c @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Jolla Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -87,10 +88,14 @@ struct vpn_provider { bool immutable; struct connman_ipaddress *prev_ipv4_addr; struct connman_ipaddress *prev_ipv6_addr; + void *plugin_data; + unsigned int auth_error_counter; + unsigned int conn_error_counter; }; static void append_properties(DBusMessageIter *iter, struct vpn_provider *provider); +static int vpn_provider_save(struct vpn_provider *provider); static void free_route(gpointer data) { @@ -190,6 +195,39 @@ static int provider_routes_changed(struct vpn_provider *provider) return 0; } +/* + * Sort vpn_route struct based on (similarly to the route key in hash table): + * 1) IP protocol number + * 2) Network addresses + * 3) Netmask addresses + * 4) Gateway addresses + */ +static gint compare_route(gconstpointer a, gconstpointer b) +{ + const struct vpn_route *route_a = a; + const struct vpn_route *route_b = b; + int difference; + + /* If IP families differ, prefer IPv6 over IPv4 */ + if (route_a->family != route_b->family) { + if (route_a->family < route_b->family) + return -1; + + if (route_a->family > route_b->family) + return 1; + } + + /* If networks differ, return */ + if ((difference = g_strcmp0(route_a->network, route_b->network))) + return difference; + + /* If netmasks differ, return. */ + if ((difference = g_strcmp0(route_a->netmask, route_b->netmask))) + return difference; + + return g_strcmp0(route_a->gateway, route_b->gateway); +} + static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) { DBusMessageIter dict, value, entry; @@ -258,9 +296,11 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) } else { switch (family) { case '4': + case 4: family = AF_INET; break; case '6': + case 6: family = AF_INET6; break; default: @@ -274,7 +314,7 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) route->netmask = g_strdup(netmask); route->gateway = g_strdup(gateway); - routes = g_slist_prepend(routes, route); + routes = g_slist_insert_sorted(routes, route, compare_route); return routes; } @@ -404,6 +444,189 @@ static DBusMessage *get_properties(DBusConnection *conn, return reply; } +/* True when lists are equal, false otherwise */ +static bool compare_network_lists(GSList *a, GSList *b) +{ + struct vpn_route *route_a, *route_b; + GSList *iter_a, *iter_b; + + if (!a && !b) + return true; + + /* + * If either of lists is NULL or the lists are of different size, the + * lists are not equal. + */ + if ((!a || !b) || (g_slist_length(a) != g_slist_length(b))) + return false; + + /* Routes are in sorted list so items can be compared in order. */ + for (iter_a = a, iter_b = b; iter_a && iter_b; + iter_a = iter_a->next, iter_b = iter_b->next) { + + route_a = iter_a->data; + route_b = iter_b->data; + + if (compare_route(route_a, route_b)) + return false; + } + + return true; +} + +static int set_provider_property(struct vpn_provider *provider, + const char *name, DBusMessageIter *value, int type) +{ + int err = 0; + + DBG("provider %p", provider); + + if (!provider || !name || !value) + return -EINVAL; + + if (g_str_equal(name, "UserRoutes")) { + GSList *networks; + + if (type != DBUS_TYPE_ARRAY) + return -EINVAL; + + networks = get_user_networks(value); + + if (compare_network_lists(provider->user_networks, networks)) { + g_slist_free_full(networks, free_route); + return -EALREADY; + } + + del_routes(provider); + provider->user_networks = networks; + set_user_networks(provider, provider->user_networks); + + if (!handle_routes) + send_routes(provider, provider->user_routes, + "UserRoutes"); + } else { + const char *str; + + if (type != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(value, &str); + + DBG("property %s value %s", name, str); + + /* Empty string clears the value, similar to ClearProperty. */ + err = vpn_provider_set_string(provider, name, + *str ? str : NULL); + } + + return err; +} + +static GString *append_to_gstring(GString *str, const char *value) +{ + if (!str) + return g_string_new(value); + + g_string_append_printf(str, ",%s", value); + + return str; +} + +static DBusMessage *set_properties(DBusMessageIter *iter, DBusMessage *msg, + void *data) +{ + struct vpn_provider *provider = data; + DBusMessageIter dict; + const char *key; + bool change = false; + GString *invalid = NULL; + GString *denied = NULL; + int type; + int err; + + for (dbus_message_iter_recurse(iter, &dict); + dbus_message_iter_get_arg_type(&dict) == + DBUS_TYPE_DICT_ENTRY; + dbus_message_iter_next(&dict)) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + /* + * Ignore invalid types in order to process all values in the + * dict. If there is an invalid type in between the dict there + * may already be changes on some values and breaking out here + * would have the provider in an inconsistent state, leaving + * the rest, potentially correct property values untouched. + */ + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + continue; + + dbus_message_iter_get_basic(&entry, &key); + + DBG("key %s", key); + + dbus_message_iter_next(&entry); + /* Ignore and report back all non variant types. */ + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) { + invalid = append_to_gstring(invalid, key); + continue; + } + + dbus_message_iter_recurse(&entry, &value); + + type = dbus_message_iter_get_arg_type(&value); + /* Ignore and report back all invalid property types */ + if (type != DBUS_TYPE_STRING && type != DBUS_TYPE_ARRAY) { + invalid = append_to_gstring(invalid, key); + continue; + } + + err = set_provider_property(provider, key, &value, type); + switch (err) { + case 0: + change = true; + break; + case -EINVAL: + invalid = append_to_gstring(invalid, key); + break; + case -EPERM: + denied = append_to_gstring(denied, key); + break; + } + } + + if (change) + vpn_provider_save(provider); + + if (invalid || denied) { + DBusMessage *error; + char *invalid_str = g_string_free(invalid, FALSE); + char *denied_str = g_string_free(denied, FALSE); + + /* + * If there are both invalid and denied properties report + * back invalid arguments. Add also the failed properties to + * the error message. + */ + error = g_dbus_create_error(msg, (invalid ? + CONNMAN_ERROR_INTERFACE ".InvalidProperty" : + CONNMAN_ERROR_INTERFACE ".PermissionDenied"), + "%s %s%s%s", (invalid ? "Invalid properties" : + "Permission denied"), + (invalid ? invalid_str : ""), + (invalid && denied ? "," : ""), + (denied ? denied_str : "")); + + g_free(invalid_str); + g_free(denied_str); + + return error; + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -411,6 +634,7 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, DBusMessageIter iter, value; const char *name; int type; + int err; DBG("conn %p", conn); @@ -432,28 +656,20 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, dbus_message_iter_recurse(&iter, &value); type = dbus_message_iter_get_arg_type(&value); + if (type == DBUS_TYPE_ARRAY && g_str_equal(name, "Properties")) + return set_properties(&value, msg, data); - if (g_str_equal(name, "UserRoutes")) { - GSList *networks; - - if (type != DBUS_TYPE_ARRAY) - return __connman_error_invalid_arguments(msg); - - networks = get_user_networks(&value); - if (networks) { - del_routes(provider); - provider->user_networks = networks; - set_user_networks(provider, provider->user_networks); - - if (!handle_routes) - send_routes(provider, provider->user_routes, - "UserRoutes"); - } - } else { - const char *str; - - dbus_message_iter_get_basic(&value, &str); - vpn_provider_set_string(provider, name, str); + err = set_provider_property(provider, name, &value, type); + switch (err) { + case 0: + vpn_provider_save(provider); + break; + case -EALREADY: + break; + case -EINVAL: + return __connman_error_invalid_property(msg); + default: + return __connman_error_failed(msg, -err); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); @@ -464,6 +680,8 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, { struct vpn_provider *provider = data; const char *name; + bool change = false; + int err; DBG("conn %p", conn); @@ -474,16 +692,38 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_INVALID); if (g_str_equal(name, "UserRoutes")) { + /* + * If either user_routes or user_networks has any entries + * there is a change that is to be written to settings file. + */ + if (g_hash_table_size(provider->user_routes) || + provider->user_networks) + change = true; + del_routes(provider); if (!handle_routes) send_routes(provider, provider->user_routes, name); } else if (vpn_provider_get_string(provider, name)) { - vpn_provider_set_string(provider, name, NULL); + err = vpn_provider_set_string(provider, name, NULL); + switch (err) { + case 0: + change = true; + /* fall through */ + case -EALREADY: + break; + case -EINVAL: + return __connman_error_invalid_property(msg); + default: + return __connman_error_failed(msg, -err); + } } else { return __connman_error_invalid_property(msg); } + if (change) + vpn_provider_save(provider); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } @@ -496,7 +736,7 @@ static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg, DBG("conn %p provider %p", conn, provider); err = __vpn_provider_connect(provider, msg); - if (err < 0) + if (err < 0 && err != -EINPROGRESS) return __connman_error_failed(msg, -err); return NULL; @@ -855,6 +1095,14 @@ static gchar **create_network_list(GSList *networks, gsize *count) return result; } +static void reset_error_counters(struct vpn_provider *provider) +{ + if (!provider) + return; + + provider->auth_error_counter = provider->conn_error_counter = 0; +} + static int vpn_provider_save(struct vpn_provider *provider) { GKeyFile *keyfile; @@ -862,6 +1110,11 @@ static int vpn_provider_save(struct vpn_provider *provider) DBG("provider %p immutable %s", provider, provider->immutable ? "yes" : "no"); + reset_error_counters(provider); + + if (provider->state == VPN_PROVIDER_STATE_FAILURE) + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + if (provider->immutable) { /* * Do not save providers that are provisioned via .config @@ -1085,20 +1338,111 @@ static void connect_cb(struct vpn_provider *provider, void *user_data, if (reply) g_dbus_send_message(connection, reply); - vpn_provider_indicate_error(provider, + switch (error) { + case EACCES: + vpn_provider_indicate_error(provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + break; + case ENOENT: + /* + * No reply, disconnect called by connmand because of + * connection timeout. + */ + break; + case ENOMSG: + /* fall through */ + case ETIMEDOUT: + /* No reply or timed out -> cancel the agent request */ + connman_agent_cancel(provider); + vpn_provider_indicate_error(provider, + VPN_PROVIDER_ERROR_UNKNOWN); + break; + case ECANCELED: + /* fall through */ + case ECONNABORTED: + /* + * This can be called in other situations than when + * VPN agent error checker is called. In such case + * react to both ECONNABORTED and ECANCELED as if the + * connection was called to terminate and do full + * disconnect -> idle cycle when being connected or + * ready. Setting the state also using the driver + * callback (vpn_set_state()) ensures that the driver is + * being disconnected as well and eventually the vpn + * process gets killed and vpn_died() is called to make + * the provider back to idle state. + */ + if (provider->state == VPN_PROVIDER_STATE_CONNECT || + provider->state == + VPN_PROVIDER_STATE_READY) { + if (provider->driver->set_state) + provider->driver->set_state(provider, + VPN_PROVIDER_STATE_DISCONNECT); + + vpn_provider_set_state(provider, + VPN_PROVIDER_STATE_DISCONNECT); + } + break; + default: + vpn_provider_indicate_error(provider, VPN_PROVIDER_ERROR_CONNECT_FAILED); - vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE); - } else + vpn_provider_set_state(provider, + VPN_PROVIDER_STATE_FAILURE); + } + } else { + reset_error_counters(provider); g_dbus_send_reply(connection, pending, DBUS_TYPE_INVALID); + } dbus_message_unref(pending); } int __vpn_provider_connect(struct vpn_provider *provider, DBusMessage *msg) { + DBusMessage *reply; int err; - DBG("provider %p", provider); + DBG("provider %p state %d", provider, provider->state); + + switch (provider->state) { + /* + * When previous connection has failed change state to idle and let + * the connmand to process this information as well. Return -EINPROGRESS + * to indicate that transition is in progress and next connection + * attempt will continue as normal. + */ + case VPN_PROVIDER_STATE_FAILURE: + if (provider->driver && provider->driver->set_state) + provider->driver->set_state(provider, + VPN_PROVIDER_STATE_IDLE); + + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + /* fall through */ + /* + * If re-using a provider and it is being disconnected let it finish + * the disconnect process in order to let vpn.c:vpn_died() to get + * processed and everything cleaned up. Otherwise the reference + * counters are not decreased properly causing the previous interface + * being left up and its routes will remain in routing table. Return + * -EINPROGRESS to indicate that transition is in progress. + */ + case VPN_PROVIDER_STATE_DISCONNECT: + /* + * Failure transition or disconnecting does not yield a + * message to be sent. Send in progress message to avoid + * D-Bus LimitsExceeded error message. + */ + reply = __connman_error_in_progress(msg); + if (reply) + g_dbus_send_message(connection, reply); + + return -EINPROGRESS; + case VPN_PROVIDER_STATE_UNKNOWN: + case VPN_PROVIDER_STATE_IDLE: + case VPN_PROVIDER_STATE_CONNECT: + case VPN_PROVIDER_STATE_READY: + break; + } if (provider->driver && provider->driver->connect) { const char *dbus_sender = dbus_message_get_sender(msg); @@ -1573,6 +1917,22 @@ int vpn_provider_set_state(struct vpn_provider *provider, return -EINVAL; } +void vpn_provider_add_error(struct vpn_provider *provider, + enum vpn_provider_error error) +{ + switch (error) { + case VPN_PROVIDER_ERROR_UNKNOWN: + break; + case VPN_PROVIDER_ERROR_CONNECT_FAILED: + ++provider->conn_error_counter; + break; + case VPN_PROVIDER_ERROR_LOGIN_FAILED: + case VPN_PROVIDER_ERROR_AUTH_FAILED: + ++provider->auth_error_counter; + break; + } +} + int vpn_provider_indicate_error(struct vpn_provider *provider, enum vpn_provider_error error) { @@ -1581,16 +1941,7 @@ int vpn_provider_indicate_error(struct vpn_provider *provider, vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE); - switch (error) { - case VPN_PROVIDER_ERROR_UNKNOWN: - case VPN_PROVIDER_ERROR_CONNECT_FAILED: - break; - - case VPN_PROVIDER_ERROR_LOGIN_FAILED: - case VPN_PROVIDER_ERROR_AUTH_FAILED: - vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); - break; - } + vpn_provider_add_error(provider, error); if (provider->driver && provider->driver->set_state) provider->driver->set_state(provider, provider->state); @@ -1714,6 +2065,13 @@ static struct vpn_provider *vpn_provider_get(const char *identifier) return provider; } +static void vpn_provider_put(const char *identifier) +{ + configuration_count_del(); + + g_hash_table_remove(provider_hash, identifier); +} + static void provider_dbus_ident(char *ident) { int i, len = strlen(ident); @@ -2078,8 +2436,6 @@ int __vpn_provider_create_from_config(GHashTable *settings, provider->config_file = g_strdup(config_ident); provider->config_entry = g_strdup(config_entry); - provider_register(provider); - provider_resolv_host_addr(provider); } @@ -2114,6 +2470,7 @@ int __vpn_provider_create_from_config(GHashTable *settings, return 0; fail: + vpn_provider_put(ident); g_free(ident); g_slist_free_full(networks, free_route); @@ -2161,7 +2518,7 @@ DBusMessage *__vpn_provider_get_connections(DBusMessage *msg) return reply; } -const char *__vpn_provider_get_ident(struct vpn_provider *provider) +const char *vpn_provider_get_ident(struct vpn_provider *provider) { if (!provider) return NULL; @@ -2178,35 +2535,54 @@ static int set_string(struct vpn_provider *provider, hide_value ? "" : value); if (g_str_equal(key, "Type")) { + if (!g_strcmp0(provider->type, value)) + return -EALREADY; + g_free(provider->type); provider->type = g_ascii_strdown(value, -1); send_value(provider->path, "Type", provider->type); } else if (g_str_equal(key, "Name")) { + if (!g_strcmp0(provider->name, value)) + return -EALREADY; + g_free(provider->name); provider->name = g_strdup(value); send_value(provider->path, "Name", provider->name); } else if (g_str_equal(key, "Host")) { + if (!g_strcmp0(provider->host, value)) + return -EALREADY; + g_free(provider->host); provider->host = g_strdup(value); send_value(provider->path, "Host", provider->host); } else if (g_str_equal(key, "VPN.Domain") || g_str_equal(key, "Domain")) { + if (!g_strcmp0(provider->domain, value)) + return -EALREADY; + g_free(provider->domain); provider->domain = g_strdup(value); send_value(provider->path, "Domain", provider->domain); } else { struct vpn_setting *setting; + bool replace = true; setting = g_hash_table_lookup(provider->setting_strings, key); - if (setting && !immutable && - setting->immutable) { - DBG("Trying to set immutable variable %s", key); - return -EPERM; - } + if (setting) { + if (!immutable && setting->immutable) { + DBG("Trying to set immutable variable %s", key); + return -EPERM; + } else if (!g_strcmp0(setting->value, value)) { + return -EALREADY; + } - setting = g_try_new0(struct vpn_setting, 1); - if (!setting) - return -ENOMEM; + g_free(setting->value); + replace = false; + } else { + setting = g_try_new0(struct vpn_setting, 1); + if (!setting) + return -ENOMEM; + } setting->value = g_strdup(value); setting->hide_value = hide_value; @@ -2217,8 +2593,9 @@ static int set_string(struct vpn_provider *provider, if (!hide_value) send_value(provider->path, key, setting->value); - g_hash_table_replace(provider->setting_strings, - g_strdup(key), setting); + if (replace) + g_hash_table_replace(provider->setting_strings, + g_strdup(key), setting); } return 0; @@ -2272,6 +2649,52 @@ const char *vpn_provider_get_string(struct vpn_provider *provider, return setting->value; } +bool vpn_provider_get_boolean(struct vpn_provider *provider, const char *key, + bool default_value) +{ + struct vpn_setting *setting; + + connman_info("provider %p key %s", provider, key); + + setting = g_hash_table_lookup(provider->setting_strings, key); + if (!setting || !setting->value) + return default_value; + + if (!g_strcmp0(setting->value, "true")) + return true; + + if (!g_strcmp0(setting->value, "false")) + return false; + + return default_value; +} + +bool vpn_provider_get_string_immutable(struct vpn_provider *provider, + const char *key) +{ + struct vpn_setting *setting; + + /* These values can be changed if the provider is not immutable */ + if (g_str_equal(key, "Type")) { + return provider->immutable; + } else if (g_str_equal(key, "Name")) { + return provider->immutable; + } else if (g_str_equal(key, "Host")) { + return provider->immutable; + } else if (g_str_equal(key, "HostIP")) { + return provider->immutable; + } else if (g_str_equal(key, "VPN.Domain") || + g_str_equal(key, "Domain")) { + return provider->immutable; + } + + setting = g_hash_table_lookup(provider->setting_strings, key); + if (!setting) + return true; /* Not found, regard as immutable - no changes */ + + return setting->immutable; +} + bool __vpn_provider_check_routes(struct vpn_provider *provider) { if (!provider) @@ -2298,6 +2721,16 @@ void vpn_provider_set_data(struct vpn_provider *provider, void *data) provider->driver_data = data; } +void *vpn_provider_get_plugin_data(struct vpn_provider *provider) +{ + return provider->plugin_data; +} + +void vpn_provider_set_plugin_data(struct vpn_provider *provider, void *data) +{ + provider->plugin_data = data; +} + void vpn_provider_set_index(struct vpn_provider *provider, int index) { DBG("index %d provider %p", index, provider); @@ -2306,7 +2739,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index) provider->ipconfig_ipv4 = __vpn_ipconfig_create(index, AF_INET); if (!provider->ipconfig_ipv4) { - DBG("Couldnt create ipconfig for IPv4"); + DBG("Couldn't create ipconfig for IPv4"); goto done; } } @@ -2317,7 +2750,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index) provider->ipconfig_ipv6 = __vpn_ipconfig_create(index, AF_INET6); if (!provider->ipconfig_ipv6) { - DBG("Couldnt create ipconfig for IPv6"); + DBG("Couldn't create ipconfig for IPv6"); goto done; } } @@ -2419,7 +2852,7 @@ int vpn_provider_set_nameservers(struct vpn_provider *provider, if (!nameservers) return 0; - provider->nameservers = g_strsplit(nameservers, " ", 0); + provider->nameservers = g_strsplit_set(nameservers, ", ", 0); return 0; } @@ -2545,7 +2978,6 @@ void vpn_provider_driver_unregister(struct vpn_provider_driver *driver) struct vpn_provider *provider = value; if (provider && provider->driver && - provider->driver->type == driver->type && g_strcmp0(provider->driver->name, driver->name) == 0) { provider->driver = NULL; @@ -2568,6 +3000,18 @@ const char *vpn_provider_get_path(struct vpn_provider *provider) return provider->path; } +unsigned int vpn_provider_get_authentication_errors( + struct vpn_provider *provider) +{ + return provider->auth_error_counter; +} + +unsigned int vpn_provider_get_connection_errors( + struct vpn_provider *provider) +{ + return provider->conn_error_counter; +} + void vpn_provider_change_address(struct vpn_provider *provider) { switch (provider->family) { diff --git a/vpn/vpn-provider.h b/vpn/vpn-provider.h index 96452c1..0275d51 100644 --- a/vpn/vpn-provider.h +++ b/vpn/vpn-provider.h @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2007-2013 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Jolla Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -36,11 +37,6 @@ extern "C" { * @short_description: Functions for handling providers */ -enum vpn_provider_type { - VPN_PROVIDER_TYPE_UNKNOWN = 0, - VPN_PROVIDER_TYPE_VPN = 1, -}; - enum vpn_provider_state { VPN_PROVIDER_STATE_UNKNOWN = 0, VPN_PROVIDER_STATE_IDLE = 1, @@ -85,10 +81,16 @@ int vpn_provider_set_string_hide_value(struct vpn_provider *provider, const char *key, const char *value); const char *vpn_provider_get_string(struct vpn_provider *provider, const char *key); +bool vpn_provider_get_string_immutable(struct vpn_provider *provider, + const char *key); +bool vpn_provider_get_boolean(struct vpn_provider *provider, const char *key, + bool default_value); int vpn_provider_set_state(struct vpn_provider *provider, enum vpn_provider_state state); +void vpn_provider_add_error(struct vpn_provider *provider, + enum vpn_provider_error error); int vpn_provider_indicate_error(struct vpn_provider *provider, enum vpn_provider_error error); @@ -97,6 +99,8 @@ int vpn_provider_get_index(struct vpn_provider *provider); void vpn_provider_set_data(struct vpn_provider *provider, void *data); void *vpn_provider_get_data(struct vpn_provider *provider); +void vpn_provider_set_plugin_data(struct vpn_provider *provider, void *data); +void *vpn_provider_get_plugin_data(struct vpn_provider *provider); int vpn_provider_set_ipaddress(struct vpn_provider *provider, struct connman_ipaddress *ipaddress); int vpn_provider_set_pac(struct vpn_provider *provider, @@ -114,6 +118,12 @@ const char *vpn_provider_get_save_group(struct vpn_provider *provider); const char *vpn_provider_get_name(struct vpn_provider *provider); const char *vpn_provider_get_host(struct vpn_provider *provider); const char *vpn_provider_get_path(struct vpn_provider *provider); + +unsigned int vpn_provider_get_authentication_errors( + struct vpn_provider *provider); +unsigned int vpn_provider_get_connection_errors( + struct vpn_provider *provider); + void vpn_provider_change_address(struct vpn_provider *provider); void vpn_provider_clear_address(struct vpn_provider *provider, int family); @@ -131,7 +141,6 @@ typedef void (* vpn_provider_password_cb_t) (struct vpn_provider *provider, struct vpn_provider_driver { const char *name; - enum vpn_provider_type type; int (*probe) (struct vpn_provider *provider); int (*remove) (struct vpn_provider *provider); int (*connect) (struct vpn_provider *provider, diff --git a/vpn/vpn-settings.c b/vpn/vpn-settings.c new file mode 100644 index 0000000..0eca2bc --- /dev/null +++ b/vpn/vpn-settings.c @@ -0,0 +1,253 @@ +/* + * ConnMan VPN daemon settings + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * Copyright (C) 2018-2019 Jolla Ltd. All rights reserved. + * Contact: jussi.laakkonen@jolla.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "vpn.h" + +#define DEFAULT_INPUT_REQUEST_TIMEOUT 300 * 1000 +#define PLUGIN_CONFIGDIR CONFIGDIR "/vpn-plugin" +#define VPN_GROUP "DACPrivileges" + +static struct { + unsigned int timeout_inputreq; + char *binary_user; + char *binary_group; + char **binary_supplementary_groups; +} connman_vpn_settings = { + .timeout_inputreq = DEFAULT_INPUT_REQUEST_TIMEOUT, + .binary_user = NULL, + .binary_group = NULL, + .binary_supplementary_groups = NULL, +}; + +struct vpn_plugin_data { + char *binary_user; + char *binary_group; + char **binary_supplementary_groups; +}; + +GHashTable *plugin_hash = NULL; + +const char *vpn_settings_get_binary_user(struct vpn_plugin_data *data) +{ + if (data && data->binary_user) + return data->binary_user; + + return connman_vpn_settings.binary_user; +} + +const char *vpn_settings_get_binary_group(struct vpn_plugin_data *data) +{ + if (data && data->binary_group) + return data->binary_group; + + return connman_vpn_settings.binary_group; +} + +char **vpn_settings_get_binary_supplementary_groups(struct vpn_plugin_data *data) +{ + if (data && data->binary_supplementary_groups) + return data->binary_supplementary_groups; + + return connman_vpn_settings.binary_supplementary_groups; +} + +unsigned int __vpn_settings_get_timeout_inputreq() +{ + return connman_vpn_settings.timeout_inputreq; +} + +static char *get_string(GKeyFile *config, const char *group, const char *key) +{ + char *str = g_key_file_get_string(config, group, key, NULL); + return str ? g_strstrip(str) : NULL; +} + +static char **get_string_list(GKeyFile *config, const char *group, + const char *key) +{ + gsize len = 0; + char **str = g_key_file_get_string_list(config, group, key, &len, NULL); + + if (str) { + guint i = 0; + + for (i = 0; i < len ; i++) { + str[i] = g_strstrip(str[i]); + } + } + + return str; +} + +static void parse_config(GKeyFile *config, const char *file) +{ + const char *group = "General"; + GError *error = NULL; + int timeout; + + if (!config) + return; + + DBG("parsing %s", file); + + timeout = g_key_file_get_integer(config, group, + "InputRequestTimeout", &error); + if (!error && timeout >= 0) + connman_vpn_settings.timeout_inputreq = timeout * 1000; + + g_clear_error(&error); + + connman_vpn_settings.binary_user = get_string(config, VPN_GROUP, + "User"); + connman_vpn_settings.binary_group = get_string(config, VPN_GROUP, + "Group"); + connman_vpn_settings.binary_supplementary_groups = get_string_list( + config, VPN_GROUP, + "SupplementaryGroups"); +} + +struct vpn_plugin_data *vpn_settings_get_vpn_plugin_config(const char *name) +{ + struct vpn_plugin_data *data = NULL; + + if (plugin_hash) + data = g_hash_table_lookup(plugin_hash, name); + + return data; +} + +static void vpn_plugin_data_free(gpointer data) +{ + struct vpn_plugin_data *plugin_data = (struct vpn_plugin_data*)data; + + g_free(plugin_data->binary_user); + g_free(plugin_data->binary_group); + g_strfreev(plugin_data->binary_supplementary_groups); + + g_free(data); +} + +int vpn_settings_parse_vpn_plugin_config(const char *name) +{ + struct vpn_plugin_data *data; + gchar *file; + gchar *ext = ".conf"; + GKeyFile *config; + gint err = 0; + + if (!name || !*name) + return -EINVAL; + + if (vpn_settings_get_vpn_plugin_config(name)) + return -EALREADY; + + file = g_strconcat(PLUGIN_CONFIGDIR, "/", name, ext, NULL); + + config = __vpn_settings_load_config(file); + + if (!config) { + err = -ENOENT; + DBG("Cannot load config %s for %s", file, name); + goto out; + } + + data = g_try_new0(struct vpn_plugin_data, 1); + + data->binary_user = get_string(config, VPN_GROUP, "User"); + data->binary_group = get_string(config, VPN_GROUP, "Group"); + data->binary_supplementary_groups = get_string_list(config, VPN_GROUP, + "SupplementaryGroups"); + + DBG("Loaded settings for %s: %s - %s", + name, data->binary_user, data->binary_group); + + if (!plugin_hash) + plugin_hash = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, vpn_plugin_data_free); + + g_hash_table_replace(plugin_hash, g_strdup(name), data); + + g_key_file_unref(config); + +out: + g_free(file); + return err; +} + +void vpn_settings_delete_vpn_plugin_config(const char *name) +{ + if (plugin_hash && name) + g_hash_table_remove(plugin_hash, name); +} + +GKeyFile *__vpn_settings_load_config(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + if (err->code != G_FILE_ERROR_NOENT) { + connman_error("Parsing %s failed: %s", file, + err->message); + } + + g_error_free(err); + g_key_file_unref(keyfile); + return NULL; + } + + return keyfile; +} + +int __vpn_settings_init(const char *file) +{ + GKeyFile *config; + + config = __vpn_settings_load_config(file); + parse_config(config, file); + if (config) + g_key_file_unref(config); + + return 0; +} + +void __vpn_settings_cleanup() +{ + g_free(connman_vpn_settings.binary_user); + g_free(connman_vpn_settings.binary_group); + g_strfreev(connman_vpn_settings.binary_supplementary_groups); + + if (plugin_hash) { + g_hash_table_destroy(plugin_hash); + plugin_hash = NULL; + } +} diff --git a/vpn/vpn.h b/vpn/vpn.h index 8bf86bd..45cf46d 100644 --- a/vpn/vpn.h +++ b/vpn/vpn.h @@ -85,7 +85,7 @@ int __vpn_provider_create_from_config(GHashTable *settings, int __vpn_provider_set_string_immutable(struct vpn_provider *provider, const char *key, const char *value); DBusMessage *__vpn_provider_get_connections(DBusMessage *msg); -const char * __vpn_provider_get_ident(struct vpn_provider *provider); +const char *vpn_provider_get_ident(struct vpn_provider *provider); struct vpn_provider *__vpn_provider_lookup(const char *identifier); int __vpn_provider_indicate_state(struct vpn_provider *provider, enum vpn_provider_state state); @@ -116,3 +116,19 @@ char *__vpn_config_get_string(GKeyFile *key_file, const char *group_name, const char *key, GError **error); char **__vpn_config_get_string_list(GKeyFile *key_file, const char *group_name, const char *key, gsize *length, GError **error); + +int __vpn_settings_init(const char *file); +void __vpn_settings_cleanup(void); +GKeyFile *__vpn_settings_load_config(const char *file); +unsigned int __vpn_settings_get_timeout_inputreq(void); + +struct vpn_plugin_data; + +int vpn_settings_parse_vpn_plugin_config(const char* plugin_name); +void vpn_settings_delete_vpn_plugin_config(const char *name); +struct vpn_plugin_data* vpn_settings_get_vpn_plugin_config(const char *name); + +const char * vpn_settings_get_binary_user(struct vpn_plugin_data *data); +const char * vpn_settings_get_binary_group(struct vpn_plugin_data *data); +char ** vpn_settings_get_binary_supplementary_groups( + struct vpn_plugin_data *data); -- 2.7.4