Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP1:GA
NetworkManager
nm-probe-radius-server-cert.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File nm-probe-radius-server-cert.patch of Package NetworkManager
From 17b0c482034e11f350c5470505cbb23f25582c96 Mon Sep 17 00:00:00 2001 From: Gary Ching-Pang Lin <chingpang@gmail.com> Date: Wed, 18 Jul 2012 12:14:56 +0800 Subject: [PATCH 1/4] libnm-util: allow the server hash to be the CA certificate --- libnm-util/libnm-util.ver | 1 + libnm-util/nm-setting-8021x.c | 54 +++++++++++++++++- libnm-util/nm-setting-8021x.h | 6 +- src/settings/plugins/ifnet/connection_parser.c | 76 +++++++++++++++++++++----- src/settings/plugins/keyfile/reader.c | 7 +++ src/settings/plugins/keyfile/writer.c | 14 ++++- 6 files changed, 139 insertions(+), 19 deletions(-) diff --git a/libnm-util/libnm-util.ver b/libnm-util/libnm-util.ver index 04f2a91..1ac871c 100644 --- a/libnm-util/libnm-util.ver +++ b/libnm-util/libnm-util.ver @@ -118,6 +118,7 @@ global: nm_setting_802_1x_get_anonymous_identity; nm_setting_802_1x_get_ca_cert_blob; nm_setting_802_1x_get_ca_cert_path; + nm_setting_802_1x_get_ca_cert_hash; nm_setting_802_1x_get_ca_cert_scheme; nm_setting_802_1x_get_ca_path; nm_setting_802_1x_get_client_cert_blob; diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index 0a9a210..8e2dcdc 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -63,6 +63,7 @@ **/ #define SCHEME_PATH "file://" +#define SCHEME_HASH "hash://server/sha256/" /** * nm_setting_802_1x_error_quark: @@ -392,6 +393,9 @@ get_cert_scheme (GByteArray *array) if ( (array->len > strlen (SCHEME_PATH)) && !memcmp (array->data, SCHEME_PATH, strlen (SCHEME_PATH))) return NM_SETTING_802_1X_CK_SCHEME_PATH; + else if ( (array->len > strlen (SCHEME_HASH)) + && !memcmp (array->data, SCHEME_HASH, strlen (SCHEME_HASH))) + return NM_SETTING_802_1X_CK_SCHEME_HASH; return NM_SETTING_802_1X_CK_SCHEME_BLOB; } @@ -402,7 +406,8 @@ get_cert_scheme (GByteArray *array) * * Returns the scheme used to store the CA certificate. If the returned scheme * is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use nm_setting_802_1x_get_ca_cert_blob(); - * if %NM_SETTING_802_1X_CK_SCHEME_PATH, use nm_setting_802_1x_get_ca_cert_path(). + * if %NM_SETTING_802_1X_CK_SCHEME_PATH, use nm_setting_802_1x_get_ca_cert_path(); + * if %NM_SETTING_802_1X_CK_SCHEME_HASH, use nm_setting_802_1x_get_ca_cert_hash(). * * Returns: scheme used to store the CA certificate (blob or path) **/ @@ -466,6 +471,32 @@ nm_setting_802_1x_get_ca_cert_path (NMSetting8021x *setting) return (const char *) (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert->data + strlen (SCHEME_PATH)); } +/** + * nm_setting_802_1x_get_ca_cert_hash: + * @setting: the #NMSetting8021x + * + * Returns the CA certificate path if the CA certificate is stored using the + * %NM_SETTING_802_1X_CK_SCHEME_HASH scheme. Not all EAP methods use a + * CA certificate (LEAP for example), and those that can take advantage of the + * CA certificate allow it to be unset. Note that lack of a CA certificate + * reduces security by allowing man-in-the-middle attacks, because the identity + * of the network cannot be confirmed by the client. + * + * Returns: hash of the RADIUS server + **/ +const char * +nm_setting_802_1x_get_ca_cert_hash (NMSetting8021x *setting) +{ + NMSetting8021xCKScheme scheme; + + g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NULL); + + scheme = nm_setting_802_1x_get_ca_cert_scheme (setting); + g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_HASH, NULL); + + return (const char *) (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert->data); +} + static GByteArray * path_to_scheme_value (const char *path) { @@ -517,7 +548,8 @@ nm_setting_802_1x_set_ca_cert (NMSetting8021x *self, if (cert_path) { g_return_val_if_fail (g_utf8_validate (cert_path, -1, NULL), FALSE); g_return_val_if_fail ( scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB - || scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, + || scheme == NM_SETTING_802_1X_CK_SCHEME_PATH + || scheme == NM_SETTING_802_1X_CK_SCHEME_HASH, FALSE); } @@ -535,6 +567,17 @@ nm_setting_802_1x_set_ca_cert (NMSetting8021x *self, if (!cert_path) return TRUE; + if (scheme == NM_SETTING_802_1X_CK_SCHEME_HASH) { + int length = strlen (cert_path); + if ( length == (strlen (SCHEME_HASH) + 64) + && !g_str_has_prefix (cert_path, SCHEME_HASH)) + return FALSE; + data = g_byte_array_sized_new (length + 1); + g_byte_array_append (data, (guint8 *) cert_path, length + 1); + priv->ca_cert = data; + return TRUE; + } + data = crypto_load_and_verify_certificate (cert_path, &format, error); if (data) { /* wpa_supplicant can only use raw x509 CA certs */ @@ -2426,6 +2469,13 @@ verify_cert (GByteArray *array, const char *prop_name, GError **error) return TRUE; } break; + case NM_SETTING_802_1X_CK_SCHEME_HASH: + /* For hash-based schemes, verify that the has is zero-terminated */ + if (array->data[array->len - 1] == '\0') { + if (g_str_has_prefix ((char *)array->data, SCHEME_HASH)) + return TRUE; + } + break; default: break; } diff --git a/libnm-util/nm-setting-8021x.h b/libnm-util/nm-setting-8021x.h index 8381fed..6f9ecf1 100644 --- a/libnm-util/nm-setting-8021x.h +++ b/libnm-util/nm-setting-8021x.h @@ -57,6 +57,8 @@ typedef enum { /*< underscore_name=nm_setting_802_1x_ck_format >*/ * item data * @NM_SETTING_802_1X_CK_SCHEME_PATH: certificate or key is stored as a path * to a file containing the certificate or key data + * @NM_SETTING_802_1X_CK_SCHEME_HASH: certificate or key is stored as a path + * of the CA server hash * * #NMSetting8021xCKScheme values indicate how a certificate or private key is * stored in the setting properties, either as a blob of the item's data, or as @@ -65,7 +67,8 @@ typedef enum { /*< underscore_name=nm_setting_802_1x_ck_format >*/ typedef enum { /*< underscore_name=nm_setting_802_1x_ck_scheme >*/ NM_SETTING_802_1X_CK_SCHEME_UNKNOWN = 0, NM_SETTING_802_1X_CK_SCHEME_BLOB, - NM_SETTING_802_1X_CK_SCHEME_PATH + NM_SETTING_802_1X_CK_SCHEME_PATH, + NM_SETTING_802_1X_CK_SCHEME_HASH } NMSetting8021xCKScheme; @@ -185,6 +188,7 @@ const char * nm_setting_802_1x_get_phase2_ca_path (NMSetting8 NMSetting8021xCKScheme nm_setting_802_1x_get_ca_cert_scheme (NMSetting8021x *setting); const GByteArray * nm_setting_802_1x_get_ca_cert_blob (NMSetting8021x *setting); const char * nm_setting_802_1x_get_ca_cert_path (NMSetting8021x *setting); +const char * nm_setting_802_1x_get_ca_cert_hash (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_ca_cert (NMSetting8021x *setting, const char *cert_path, NMSetting8021xCKScheme scheme, diff --git a/src/settings/plugins/ifnet/connection_parser.c b/src/settings/plugins/ifnet/connection_parser.c index c85b5c4..8372806 100644 --- a/src/settings/plugins/ifnet/connection_parser.c +++ b/src/settings/plugins/ifnet/connection_parser.c @@ -44,6 +44,8 @@ #include "connection_parser.h" #include "nm-ifnet-connection.h" +#define SCHEME_HASH "hash://server/sha256/" + static void update_connection_id (NMConnection *connection, const char *conn_name) { @@ -206,11 +208,18 @@ eap_tls_reader (const char *eap_method, NULL, error)) goto done; } else { - if (!nm_setting_802_1x_set_ca_cert (s_8021x, - ca_cert, - NM_SETTING_802_1X_CK_SCHEME_PATH, - NULL, error)) - goto done; + if (g_str_has_prefix (ca_cert, SCHEME_HASH)) + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_HASH, + NULL, error)) + goto done; + else + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, error)) + goto done; } } else { PLUGIN_WARN (IFNET_PLUGIN_NAME, @@ -317,11 +326,18 @@ eap_peap_reader (const char *eap_method, ca_cert = get_cert (ssid, "ca_cert", basepath); if (ca_cert) { - if (!nm_setting_802_1x_set_ca_cert (s_8021x, - ca_cert, - NM_SETTING_802_1X_CK_SCHEME_PATH, - NULL, error)) - goto done; + if (g_str_has_prefix (ca_cert, SCHEME_HASH)) + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_HASH, + NULL, error)) + goto done; + else + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, error)) + goto done; } else { PLUGIN_WARN (IFNET_PLUGIN_NAME, " warning: missing " "IEEE_8021X_CA_CERT for EAP method '%s'; this is" @@ -420,11 +436,18 @@ eap_ttls_reader (const char *eap_method, /* ca cert */ ca_cert = get_cert (ssid, "ca_cert", basepath); if (ca_cert) { - if (!nm_setting_802_1x_set_ca_cert (s_8021x, - ca_cert, - NM_SETTING_802_1X_CK_SCHEME_PATH, - NULL, error)) - goto done; + if (g_str_has_prefix (ca_cert, SCHEME_HASH)) + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_HASH, + NULL, error)) + goto done; + else + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, error)) + goto done; } else { PLUGIN_WARN (IFNET_PLUGIN_NAME, " warning: missing " "IEEE_8021X_CA_CERT for EAP method '%s'; this is" @@ -1774,12 +1797,14 @@ error: typedef NMSetting8021xCKScheme (*SchemeFunc) (NMSetting8021x * setting); typedef const char *(*PathFunc) (NMSetting8021x * setting); +typedef const char *(*HashFunc) (NMSetting8021x * setting); typedef const GByteArray *(*BlobFunc) (NMSetting8021x * setting); typedef struct ObjectType { const char *setting_key; SchemeFunc scheme_func; PathFunc path_func; + HashFunc hash_func; BlobFunc blob_func; const char *conn_name_key; const char *suffix; @@ -1789,6 +1814,7 @@ static const ObjectType ca_type = { NM_SETTING_802_1X_CA_CERT, nm_setting_802_1x_get_ca_cert_scheme, nm_setting_802_1x_get_ca_cert_path, + nm_setting_802_1x_get_ca_cert_hash, nm_setting_802_1x_get_ca_cert_blob, "ca_cert", "ca-cert.der" @@ -1798,6 +1824,7 @@ static const ObjectType phase2_ca_type = { NM_SETTING_802_1X_PHASE2_CA_CERT, nm_setting_802_1x_get_phase2_ca_cert_scheme, nm_setting_802_1x_get_phase2_ca_cert_path, + NULL, nm_setting_802_1x_get_phase2_ca_cert_blob, "ca_cert2", "inner-ca-cert.der" @@ -1807,6 +1834,7 @@ static const ObjectType client_type = { NM_SETTING_802_1X_CLIENT_CERT, nm_setting_802_1x_get_client_cert_scheme, nm_setting_802_1x_get_client_cert_path, + NULL, nm_setting_802_1x_get_client_cert_blob, "client_cert", "client-cert.der" @@ -1816,6 +1844,7 @@ static const ObjectType phase2_client_type = { NM_SETTING_802_1X_PHASE2_CLIENT_CERT, nm_setting_802_1x_get_phase2_client_cert_scheme, nm_setting_802_1x_get_phase2_client_cert_path, + NULL, nm_setting_802_1x_get_phase2_client_cert_blob, "client_cert2", "inner-client-cert.der" @@ -1825,6 +1854,7 @@ static const ObjectType pk_type = { NM_SETTING_802_1X_PRIVATE_KEY, nm_setting_802_1x_get_private_key_scheme, nm_setting_802_1x_get_private_key_path, + NULL, nm_setting_802_1x_get_private_key_blob, "private_key", "private-key.pem" @@ -1834,6 +1864,7 @@ static const ObjectType phase2_pk_type = { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, nm_setting_802_1x_get_phase2_private_key_scheme, nm_setting_802_1x_get_phase2_private_key_path, + NULL, nm_setting_802_1x_get_phase2_private_key_blob, "private_key2", "inner-private-key.pem" @@ -1843,6 +1874,7 @@ static const ObjectType p12_type = { NM_SETTING_802_1X_PRIVATE_KEY, nm_setting_802_1x_get_private_key_scheme, nm_setting_802_1x_get_private_key_path, + NULL, nm_setting_802_1x_get_private_key_blob, "private_key", "private-key.p12" @@ -1852,6 +1884,7 @@ static const ObjectType phase2_p12_type = { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, nm_setting_802_1x_get_phase2_private_key_scheme, nm_setting_802_1x_get_phase2_private_key_path, + NULL, nm_setting_802_1x_get_phase2_private_key_blob, "private_key2", "inner-private-key.p12" @@ -1866,6 +1899,7 @@ write_object (NMSetting8021x *s_8021x, { NMSetting8021xCKScheme scheme; const char *path = NULL; + const char *hash = NULL; const GByteArray *blob = NULL; g_return_val_if_fail (conn_name != NULL, FALSE); @@ -1884,6 +1918,9 @@ write_object (NMSetting8021x *s_8021x, case NM_SETTING_802_1X_CK_SCHEME_PATH: path = (*(objtype->path_func)) (s_8021x); break; + case NM_SETTING_802_1X_CK_SCHEME_HASH: + hash = (*(objtype->hash_func)) (s_8021x); + break; default: break; } @@ -1898,6 +1935,15 @@ write_object (NMSetting8021x *s_8021x, return TRUE; } + /* If the object hash was specified, prefer that over any raw cert data that + * may have been sent. + */ + if (hash) { + wpa_set_data (conn_name, (gchar *) objtype->conn_name_key, + (gchar *) hash); + return TRUE; + } + /* does not support writing encryption data now */ if (blob) { PLUGIN_WARN (IFNET_PLUGIN_NAME, diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index ec4661e..9df23eb 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -703,6 +703,7 @@ get_cert_path (const char *keyfile_path, GByteArray *cert_path) } #define SCHEME_PATH "file://" +#define SCHEME_HASH "hash://server/sha256/" static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; @@ -727,6 +728,12 @@ handle_as_scheme (GByteArray *array, NMSetting *setting, const char *key) && (array->data[array->len - 1] == '\0')) { g_object_set (setting, key, array, NULL); return TRUE; + } else if ( (array->len > strlen (SCHEME_HASH)) + && g_str_has_prefix ((const char *) array->data, SCHEME_HASH) + && (array->data[array->len - 1] == '\0')) { + /* It's the HASH scheme, can just set plain data */ + g_object_set (setting, key, array, NULL); + return TRUE; } return FALSE; } diff --git a/src/settings/plugins/keyfile/writer.c b/src/settings/plugins/keyfile/writer.c index 38061a5..7d3272c 100644 --- a/src/settings/plugins/keyfile/writer.c +++ b/src/settings/plugins/keyfile/writer.c @@ -548,6 +548,7 @@ typedef struct ObjectType { NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); const char * (*path_func) (NMSetting8021x *setting); + const char * (*hash_func) (NMSetting8021x *setting); const GByteArray * (*blob_func) (NMSetting8021x *setting); } ObjectType; @@ -558,6 +559,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_ca_cert_scheme, NULL, nm_setting_802_1x_get_ca_cert_path, + nm_setting_802_1x_get_ca_cert_hash, nm_setting_802_1x_get_ca_cert_blob }, { NM_SETTING_802_1X_PHASE2_CA_CERT, @@ -566,6 +568,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_phase2_ca_cert_scheme, NULL, nm_setting_802_1x_get_phase2_ca_cert_path, + NULL, nm_setting_802_1x_get_phase2_ca_cert_blob }, { NM_SETTING_802_1X_CLIENT_CERT, @@ -574,6 +577,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_client_cert_scheme, NULL, nm_setting_802_1x_get_client_cert_path, + NULL, nm_setting_802_1x_get_client_cert_blob }, { NM_SETTING_802_1X_PHASE2_CLIENT_CERT, @@ -582,6 +586,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_phase2_client_cert_scheme, NULL, nm_setting_802_1x_get_phase2_client_cert_path, + NULL, nm_setting_802_1x_get_phase2_client_cert_blob }, { NM_SETTING_802_1X_PRIVATE_KEY, @@ -590,6 +595,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_private_key_scheme, nm_setting_802_1x_get_private_key_format, nm_setting_802_1x_get_private_key_path, + NULL, nm_setting_802_1x_get_private_key_blob }, { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, @@ -598,6 +604,7 @@ static const ObjectType objtypes[10] = { nm_setting_802_1x_get_phase2_private_key_scheme, nm_setting_802_1x_get_phase2_private_key_format, nm_setting_802_1x_get_phase2_private_key_path, + NULL, nm_setting_802_1x_get_phase2_private_key_blob }, { NULL }, @@ -676,7 +683,7 @@ cert_writer (GKeyFile *file, const char *setting_name = nm_setting_get_name (setting); NMSetting8021xCKScheme scheme; NMSetting8021xCKFormat format; - const char *path = NULL, *ext = "pem"; + const char *path = NULL, *hash = NULL, *ext = "pem"; const ObjectType *objtype = NULL; int i; @@ -738,6 +745,11 @@ cert_writer (GKeyFile *file, g_error_free (error); } g_free (new_path); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_HASH) { + hash = objtype->hash_func (NM_SETTING_802_1X (setting)); + g_assert (hash); + + g_key_file_set_string (file, setting_name, key, hash); } else g_assert_not_reached (); } -- 1.8.1.4 From dca713eb64d3e0914874a2f4b6e010bd8d6787c5 Mon Sep 17 00:00:00 2001 From: Gary Ching-Pang Lin <chingpang@gmail.com> Date: Fri, 8 Feb 2013 11:23:15 +0800 Subject: [PATCH 2/4] wifi: add the dbus method to probe the certificate --- introspection/nm-device-wifi.xml | 23 ++++++ src/nm-device-wifi.c | 99 ++++++++++++++++++++++++ src/nm-device-wifi.h | 2 + src/supplicant-manager/nm-supplicant-config.c | 24 ++++++ src/supplicant-manager/nm-supplicant-config.h | 2 + src/supplicant-manager/nm-supplicant-interface.c | 32 ++++++++ src/supplicant-manager/nm-supplicant-interface.h | 4 + 7 files changed, 186 insertions(+) diff --git a/introspection/nm-device-wifi.xml b/introspection/nm-device-wifi.xml index dcfa20c..25adcda 100644 --- a/introspection/nm-device-wifi.xml +++ b/introspection/nm-device-wifi.xml @@ -27,6 +27,18 @@ </tp:docstring> </method> + <method name="ProbeCert"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_device_probe_cert"/> + <arg name="ssid" type="ay" direction="in"> + <tp:docstring> + The SSID of the AP to be probed + </tp:docstring> + </arg> + <tp:docstring> + Probe the certificate of the RADIUS server. + </tp:docstring> + </method> + <property name="HwAddress" type="s" access="read"> <tp:docstring> The active hardware address of the device. @@ -94,6 +106,17 @@ </tp:docstring> </signal> + <signal name="CertReceived"> + <arg name="cert" type="a{sv}" tp:type="String_Variant_Map"> + <tp:docstring> + The certificate of the RADIUS server + </tp:docstring> + </arg> + <tp:docstring> + Emitted when wpa_supplicant replies the certificate of the RADIUS server. + </tp:docstring> + </signal> + <tp:flags name="NM_802_11_DEVICE_CAP" type="u"> <tp:docstring> Flags describing the capabilities of a wireless device. diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index 3ac34e7..7dd027f 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -60,10 +60,14 @@ #include "nm-settings-connection.h" #include "nm-enum-types.h" #include "wifi-utils.h" +#include "nm-dbus-glib-types.h" static gboolean impl_device_get_access_points (NMDeviceWifi *device, GPtrArray **aps, GError **err); +static gboolean impl_device_probe_cert (NMDeviceWifi *device, + GByteArray *ssid, + GError **err); static void impl_device_request_scan (NMDeviceWifi *device, GHashTable *options, @@ -104,6 +108,7 @@ enum { HIDDEN_AP_FOUND, PROPERTIES_CHANGED, SCANNING_ALLOWED, + CERT_RECEIVED, LAST_SIGNAL }; @@ -118,6 +123,7 @@ typedef struct Supplicant { guint sig_ids[SUP_SIG_ID_LEN]; guint iface_error_id; + guint iface_cert_id; /* Timeouts and idles */ guint iface_con_error_cb_id; @@ -1876,6 +1882,89 @@ supplicant_iface_scan_done_cb (NMSupplicantInterface *iface, #define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" #define MAC_ARG(x) ((guint8*)(x))[0],((guint8*)(x))[1],((guint8*)(x))[2],((guint8*)(x))[3],((guint8*)(x))[4],((guint8*)(x))[5] +static void +supplicant_iface_certification_cb (NMSupplicantInterface * iface, + GHashTable *cert, + NMDeviceWifi * self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GValue *value; + const char *subject, *hash; + guint depth; + + value = g_hash_table_lookup (cert, "depth"); + if (!value || !G_VALUE_HOLDS_UINT(value)) { + nm_log_dbg (LOGD_WIFI_SCAN, "Depth was not set"); + return; + } + depth = g_value_get_uint (value); + + value = g_hash_table_lookup (cert, "subject"); + if (!value || !G_VALUE_HOLDS_STRING(value)) + return; + subject = g_value_get_string (value); + + value = g_hash_table_lookup (cert, "cert_hash"); + if (!value || !G_VALUE_HOLDS_STRING(value)) + return; + hash = g_value_get_string (value); + + nm_log_info (LOGD_WIFI_SCAN, "Got Server Certificate %u, subject %s, hash %s", depth, subject, hash); + + if (depth != 0) + return; + + g_signal_emit (self, signals[CERT_RECEIVED], 0, cert); + + if (priv->supplicant.iface_cert_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_cert_id); + priv->supplicant.iface_cert_id = 0; + } + + nm_supplicant_interface_disconnect (iface); +} + +static gboolean +impl_device_probe_cert (NMDeviceWifi *self, + GByteArray *ssid, + GError **err) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSupplicantConfig *config = NULL; + guint id; + gboolean ret = FALSE; + + config = nm_supplicant_config_new_probe (ssid); + if (!config) + goto error; + + /* Hook up signal handler to capture certification signal */ + id = g_signal_connect (priv->supplicant.iface, + "certification", + G_CALLBACK (supplicant_iface_certification_cb), + self); + priv->supplicant.iface_cert_id = id; + + if (!nm_supplicant_interface_set_config (priv->supplicant.iface, config)) + goto error; + + ret = TRUE; + +error: + if (!ret) { + g_set_error_literal (err, + NM_WIFI_ERROR, + NM_WIFI_ERROR_INVALID_CERT_PROBE, + "Couldn't probe RADIUS server certificate"); + if (priv->supplicant.iface_cert_id) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_cert_id); + priv->supplicant.iface_cert_id = 0; + } + } + + return ret; +} + /* * merge_scanned_ap * @@ -3891,6 +3980,16 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass) _nm_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN, 0); + signals[CERT_RECEIVED] = + g_signal_new ("cert-received", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, cert_received), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + DBUS_TYPE_G_MAP_OF_VARIANT); + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass), &dbus_glib_nm_device_wifi_object_info); dbus_g_error_domain_register (NM_WIFI_ERROR, NULL, NM_TYPE_WIFI_ERROR); diff --git a/src/nm-device-wifi.h b/src/nm-device-wifi.h index 368d0b8..74484ae 100644 --- a/src/nm-device-wifi.h +++ b/src/nm-device-wifi.h @@ -49,6 +49,7 @@ typedef enum { NM_WIFI_ERROR_SCAN_NOT_ALLOWED, /*< nick=ScanNotAllowed >*/ NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, /*< nick=ApModeUnsupported >*/ NM_WIFI_ERROR_ADHOC_MODE_UNSUPPORTED, /*< nick=AdhocModeUnsupported >*/ + NM_WIFI_ERROR_INVALID_CERT_PROBE, /*< nick=InvalidCertProbe >*/ } NMWifiError; #define NM_DEVICE_WIFI_HW_ADDRESS "hw-address" @@ -86,6 +87,7 @@ struct _NMDeviceWifiClass void (*hidden_ap_found) (NMDeviceWifi *device, NMAccessPoint *ap); void (*properties_changed) (NMDeviceWifi *device, GHashTable *properties); gboolean (*scanning_allowed) (NMDeviceWifi *device); + void (*cert_received) (NMDeviceWifi *device, GHashTable *cert); }; diff --git a/src/supplicant-manager/nm-supplicant-config.c b/src/supplicant-manager/nm-supplicant-config.c index f5fc154..8e7807c 100644 --- a/src/supplicant-manager/nm-supplicant-config.c +++ b/src/supplicant-manager/nm-supplicant-config.c @@ -174,6 +174,25 @@ nm_supplicant_config_add_option (NMSupplicantConfig *self, return nm_supplicant_config_add_option_with_type (self, key, value, len, TYPE_INVALID, secret); } +NMSupplicantConfig * +nm_supplicant_config_new_probe (const GByteArray *ssid) +{ + NMSupplicantConfig *probe_config; + + if (!ssid) + return NULL; + + probe_config = (NMSupplicantConfig *)g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL); + + nm_supplicant_config_add_option (probe_config, "ssid", (char *)ssid->data, ssid->len, FALSE); + nm_supplicant_config_add_option (probe_config, "key_mgmt", "WPA-EAP", -1, FALSE); + nm_supplicant_config_add_option (probe_config, "eap", "TTLS PEAP TLS", -1, FALSE); + nm_supplicant_config_add_option (probe_config, "identity", " ", -1, FALSE); + nm_supplicant_config_add_option (probe_config, "ca_cert", "probe://", -1, FALSE); + + return probe_config; +} + static gboolean nm_supplicant_config_add_blob (NMSupplicantConfig *self, const char *key, @@ -914,6 +933,11 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self, if (!add_string_val (self, path, "ca_cert", FALSE, FALSE)) return FALSE; break; + case NM_SETTING_802_1X_CK_SCHEME_HASH: + path = nm_setting_802_1x_get_ca_cert_hash (setting); + if (!add_string_val (self, path, "ca_cert", FALSE, FALSE)) + return FALSE; + break; default: break; } diff --git a/src/supplicant-manager/nm-supplicant-config.h b/src/supplicant-manager/nm-supplicant-config.h index a8d3047..c0356c5 100644 --- a/src/supplicant-manager/nm-supplicant-config.h +++ b/src/supplicant-manager/nm-supplicant-config.h @@ -52,6 +52,8 @@ GType nm_supplicant_config_get_type (void); NMSupplicantConfig *nm_supplicant_config_new (void); +NMSupplicantConfig *nm_supplicant_config_new_probe (const GByteArray *ssid); + guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self); void nm_supplicant_config_set_ap_scan (NMSupplicantConfig *self, diff --git a/src/supplicant-manager/nm-supplicant-interface.c b/src/supplicant-manager/nm-supplicant-interface.c index 75450a0..99601b7 100644 --- a/src/supplicant-manager/nm-supplicant-interface.c +++ b/src/supplicant-manager/nm-supplicant-interface.c @@ -66,6 +66,7 @@ enum { SCAN_DONE, /* wifi scan is complete */ CONNECTION_ERROR, /* an error occurred during a connection request */ CREDENTIALS_REQUEST, /* 802.1x identity or password requested */ + CERTIFICATION, /* a RADIUS server certificate was received */ LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; @@ -580,6 +581,17 @@ parse_capabilities (NMSupplicantInterface *self, GHashTable *props) } static void +wpas_iface_got_certification (DBusGProxy *proxy, + const GHashTable *cert_table, + gpointer user_data) +{ + g_signal_emit (user_data, + signals[CERTIFICATION], + 0, + cert_table); +} + +static void wpas_iface_properties_changed (DBusGProxy *proxy, GHashTable *props, gpointer user_data) @@ -908,6 +920,18 @@ interface_add_done (NMSupplicantInterface *self, char *path) self, NULL); + dbus_g_object_register_marshaller (g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_INVALID); + dbus_g_proxy_add_signal (priv->iface_proxy, "Certification", + DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->iface_proxy, "Certification", + G_CALLBACK (wpas_iface_got_certification), + self, + NULL); + priv->props_proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (priv->dbus_mgr), WPAS_DBUS_SERVICE, path, @@ -1706,5 +1730,13 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass) NULL, NULL, _nm_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + signals[CERTIFICATION] = + g_signal_new ("certification", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMSupplicantInterfaceClass, certification), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, DBUS_TYPE_G_MAP_OF_VARIANT); } diff --git a/src/supplicant-manager/nm-supplicant-interface.h b/src/supplicant-manager/nm-supplicant-interface.h index 2f0233c..a6fe89b 100644 --- a/src/supplicant-manager/nm-supplicant-interface.h +++ b/src/supplicant-manager/nm-supplicant-interface.h @@ -115,6 +115,10 @@ typedef struct { void (*credentials_request) (NMSupplicantInterface *iface, const char *field, const char *message); + + /* a RADIUS server certificate was received */ + void (*certification) (NMSupplicantInterface * iface, + const GHashTable * ca_cert); } NMSupplicantInterfaceClass; -- 1.8.1.4 From 6ee4c2109cb5883a789bd002d7eb2918b155d7d9 Mon Sep 17 00:00:00 2001 From: Gary Ching-Pang Lin <chingpang@gmail.com> Date: Fri, 8 Feb 2013 11:24:10 +0800 Subject: [PATCH 3/4] libnm-glib: add the function to probe the certificate --- libnm-glib/libnm-glib.ver | 1 + libnm-glib/nm-device-wifi.c | 69 +++++++++++++++++++++++++++++++++++++++++++++ libnm-glib/nm-device-wifi.h | 5 +++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/libnm-glib/libnm-glib.ver b/libnm-glib/libnm-glib.ver index b7d3321..762cab4 100644 --- a/libnm-glib/libnm-glib.ver +++ b/libnm-glib/libnm-glib.ver @@ -166,6 +166,7 @@ global: nm_device_wifi_get_type; nm_device_wifi_new; nm_device_wifi_request_scan_simple; + nm_device_wifi_probe_cert; nm_device_wimax_error_get_type; nm_device_wimax_error_quark; nm_device_wimax_get_active_nsp; diff --git a/libnm-glib/nm-device-wifi.c b/libnm-glib/nm-device-wifi.c index 47594a5..fbeef07 100644 --- a/libnm-glib/nm-device-wifi.c +++ b/libnm-glib/nm-device-wifi.c @@ -87,6 +87,7 @@ enum { enum { ACCESS_POINT_ADDED, ACCESS_POINT_REMOVED, + CERT_RECEIVED, LAST_SIGNAL }; @@ -414,6 +415,49 @@ access_point_removed (NMObject *self_obj, NMObject *ap_obj) g_signal_emit (self, signals[ACCESS_POINT_REMOVED], 0, ap); } +/** + * nm_device_wifi_probe_cert: + * @device: a #NMDeviceWifi + * @ssid: the ssid of the AP to probe + * + * Probe the certificate of the RADIUS server + * + * Returns: if the probe is sent or not + **/ +gboolean +nm_device_wifi_probe_cert (NMDeviceWifi *device, + const GByteArray *ssid) +{ + NMDeviceWifiPrivate *priv; + GError *error = NULL; + gboolean ret; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (device), FALSE); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + + ret = dbus_g_proxy_call (priv->proxy, "ProbeCert", &error, + DBUS_TYPE_G_UCHAR_ARRAY, ssid, + G_TYPE_INVALID, + G_TYPE_INVALID); + + if (!ret) { + g_warning ("%s: error probe certificate: %s", __func__, error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +static void +cert_received_proxy (DBusGProxy *proxy, GHashTable *cert, gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + + g_signal_emit (self, signals[CERT_RECEIVED], 0, cert); +} + static void clean_up_aps (NMDeviceWifi *self, gboolean notify) { @@ -667,6 +711,13 @@ constructed (GObject *object) nm_object_get_path (NM_OBJECT (object)), NM_DBUS_INTERFACE_DEVICE_WIRELESS); + dbus_g_proxy_add_signal (priv->proxy, "CertReceived", + DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "CertReceived", + G_CALLBACK (cert_received_proxy), + object, NULL); + register_properties (NM_DEVICE_WIFI (object)); g_signal_connect (NM_DEVICE (object), @@ -843,4 +894,22 @@ nm_device_wifi_class_init (NMDeviceWifiClass *wifi_class) g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); + + /** + * NMDeviceWifi::cert-received: + * @device: the wifi device that received the signal + * @subject: the subject of the RADIUS server + * @hash: the hash of the RADIUS server + * + * Notifies that a certificate of a RADIUS server is received. + **/ + signals[CERT_RECEIVED] = + g_signal_new ("cert-received", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, cert_received), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + G_TYPE_HASH_TABLE); } diff --git a/libnm-glib/nm-device-wifi.h b/libnm-glib/nm-device-wifi.h index 76d76b4..1b2ede3 100644 --- a/libnm-glib/nm-device-wifi.h +++ b/libnm-glib/nm-device-wifi.h @@ -78,6 +78,7 @@ typedef struct { /* Signals */ void (*access_point_added) (NMDeviceWifi *device, NMAccessPoint *ap); void (*access_point_removed) (NMDeviceWifi *device, NMAccessPoint *ap); + void (*cert_received) (NMDeviceWifi *device, GHashTable *cert); /* Padding for future expansion */ void (*_reserved1) (void); @@ -85,7 +86,6 @@ typedef struct { void (*_reserved3) (void); void (*_reserved4) (void); void (*_reserved5) (void); - void (*_reserved6) (void); } NMDeviceWifiClass; GType nm_device_wifi_get_type (void); @@ -111,6 +111,9 @@ void nm_device_wifi_request_scan_simple (NMDeviceWifi * NMDeviceWifiRequestScanFn callback, gpointer user_data); +gboolean nm_device_wifi_probe_cert (NMDeviceWifi *device, + const GByteArray *ssid); + G_END_DECLS #endif /* NM_DEVICE_WIFI_H */ -- 1.8.1.4 From a367e7fd56710e7d75fa39665442156b2aec2b12 Mon Sep 17 00:00:00 2001 From: Gary Ching-Pang Lin <chingpang@gmail.com> Date: Thu, 25 Apr 2013 11:13:52 +0800 Subject: [PATCH 4/4] wifi: Probe the Radius server with the user credential Some servers do not accept anonymous probe. --- introspection/nm-device-wifi.xml | 4 ++-- libnm-glib/nm-device-wifi.c | 14 +++++++++--- libnm-glib/nm-device-wifi.h | 2 +- src/nm-device-wifi.c | 33 +++++++++++++++++++++++---- src/supplicant-manager/nm-supplicant-config.c | 31 +++++++++++++++++++++---- src/supplicant-manager/nm-supplicant-config.h | 3 ++- 6 files changed, 72 insertions(+), 15 deletions(-) diff --git a/introspection/nm-device-wifi.xml b/introspection/nm-device-wifi.xml index 25adcda..a1776ed 100644 --- a/introspection/nm-device-wifi.xml +++ b/introspection/nm-device-wifi.xml @@ -29,9 +29,9 @@ <method name="ProbeCert"> <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_device_probe_cert"/> - <arg name="ssid" type="ay" direction="in"> + <arg name="settings" type="a{sa{sv}}" direction="in"> <tp:docstring> - The SSID of the AP to be probed + Connection settings and properties </tp:docstring> </arg> <tp:docstring> diff --git a/libnm-glib/nm-device-wifi.c b/libnm-glib/nm-device-wifi.c index fbeef07..95021ca 100644 --- a/libnm-glib/nm-device-wifi.c +++ b/libnm-glib/nm-device-wifi.c @@ -418,7 +418,7 @@ access_point_removed (NMObject *self_obj, NMObject *ap_obj) /** * nm_device_wifi_probe_cert: * @device: a #NMDeviceWifi - * @ssid: the ssid of the AP to probe + * @partial: the connection settings and properties * * Probe the certificate of the RADIUS server * @@ -426,21 +426,29 @@ access_point_removed (NMObject *self_obj, NMObject *ap_obj) **/ gboolean nm_device_wifi_probe_cert (NMDeviceWifi *device, - const GByteArray *ssid) + NMConnection *partial) { NMDeviceWifiPrivate *priv; + GHashTable *hash = NULL; GError *error = NULL; gboolean ret; g_return_val_if_fail (NM_IS_DEVICE_WIFI (device), FALSE); + g_return_val_if_fail (partial, FALSE); priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + hash = nm_connection_to_hash (partial, NM_SETTING_HASH_FLAG_ALL); + if (hash == NULL) + return FALSE; + ret = dbus_g_proxy_call (priv->proxy, "ProbeCert", &error, - DBUS_TYPE_G_UCHAR_ARRAY, ssid, + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, hash, G_TYPE_INVALID, G_TYPE_INVALID); + g_hash_table_unref (hash); + if (!ret) { g_warning ("%s: error probe certificate: %s", __func__, error->message); g_error_free (error); diff --git a/libnm-glib/nm-device-wifi.h b/libnm-glib/nm-device-wifi.h index 1b2ede3..df41ab3 100644 --- a/libnm-glib/nm-device-wifi.h +++ b/libnm-glib/nm-device-wifi.h @@ -112,7 +112,7 @@ void nm_device_wifi_request_scan_simple (NMDeviceWifi * gpointer user_data); gboolean nm_device_wifi_probe_cert (NMDeviceWifi *device, - const GByteArray *ssid); + NMConnection *partial); G_END_DECLS diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index 7dd027f..305966d 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -66,7 +66,7 @@ static gboolean impl_device_get_access_points (NMDeviceWifi *device, GPtrArray **aps, GError **err); static gboolean impl_device_probe_cert (NMDeviceWifi *device, - GByteArray *ssid, + GHashTable *settings, GError **err); static void impl_device_request_scan (NMDeviceWifi *device, @@ -1926,16 +1926,38 @@ supplicant_iface_certification_cb (NMSupplicantInterface * iface, static gboolean impl_device_probe_cert (NMDeviceWifi *self, - GByteArray *ssid, + GHashTable *settings, GError **err) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMConnection *connection; + NMSettingWireless *setting_wifi; + const GByteArray *ssid; + NMSetting8021x *setting_8021x; NMSupplicantConfig *config = NULL; guint id; gboolean ret = FALSE; - config = nm_supplicant_config_new_probe (ssid); - if (!config) + if (!settings) + goto error; + + connection = nm_connection_new (); + nm_connection_replace_settings (connection, settings, NULL); + + setting_wifi = nm_connection_get_setting_wireless (connection); + if (setting_wifi == NULL) + goto error; + + ssid = nm_setting_wireless_get_ssid (setting_wifi); + if (ssid == NULL) + goto error; + + setting_8021x = nm_connection_get_setting_802_1x (connection); + if (setting_8021x == NULL) + goto error; + + config = nm_supplicant_config_new_probe (ssid, setting_8021x); + if (config == NULL) goto error; /* Hook up signal handler to capture certification signal */ @@ -1951,6 +1973,9 @@ impl_device_probe_cert (NMDeviceWifi *self, ret = TRUE; error: + if (connection) + g_object_unref (connection); + if (!ret) { g_set_error_literal (err, NM_WIFI_ERROR, diff --git a/src/supplicant-manager/nm-supplicant-config.c b/src/supplicant-manager/nm-supplicant-config.c index 8e7807c..b9bf6f1 100644 --- a/src/supplicant-manager/nm-supplicant-config.c +++ b/src/supplicant-manager/nm-supplicant-config.c @@ -175,21 +175,44 @@ nm_supplicant_config_add_option (NMSupplicantConfig *self, } NMSupplicantConfig * -nm_supplicant_config_new_probe (const GByteArray *ssid) +nm_supplicant_config_new_probe (const GByteArray *ssid, NMSetting8021x *setting) { NMSupplicantConfig *probe_config; + const char *identity, *password; + gboolean use_pw = FALSE; + guint32 i, num_eap; - if (!ssid) - return NULL; + g_return_val_if_fail (ssid != NULL, NULL); + g_return_val_if_fail (setting != NULL, NULL); + + num_eap = nm_setting_802_1x_get_num_eap_methods (setting); + for (i = 0; i < num_eap; i++) { + const char *method = nm_setting_802_1x_get_eap_method (setting, i); + + if (method && (strcasecmp (method, "ttls") == 0 || + strcasecmp (method, "peap") == 0)) + use_pw = TRUE; + } + + identity = nm_setting_802_1x_get_identity (setting); + if (!identity) + identity = " "; probe_config = (NMSupplicantConfig *)g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL); nm_supplicant_config_add_option (probe_config, "ssid", (char *)ssid->data, ssid->len, FALSE); nm_supplicant_config_add_option (probe_config, "key_mgmt", "WPA-EAP", -1, FALSE); nm_supplicant_config_add_option (probe_config, "eap", "TTLS PEAP TLS", -1, FALSE); - nm_supplicant_config_add_option (probe_config, "identity", " ", -1, FALSE); + nm_supplicant_config_add_option (probe_config, "identity", identity, -1, FALSE); nm_supplicant_config_add_option (probe_config, "ca_cert", "probe://", -1, FALSE); + if (use_pw) { + password = nm_setting_802_1x_get_password(setting); + if (!password) + return NULL; + nm_supplicant_config_add_option (probe_config, "password", password, -1, TRUE); + } + return probe_config; } diff --git a/src/supplicant-manager/nm-supplicant-config.h b/src/supplicant-manager/nm-supplicant-config.h index c0356c5..9d09239 100644 --- a/src/supplicant-manager/nm-supplicant-config.h +++ b/src/supplicant-manager/nm-supplicant-config.h @@ -52,7 +52,8 @@ GType nm_supplicant_config_get_type (void); NMSupplicantConfig *nm_supplicant_config_new (void); -NMSupplicantConfig *nm_supplicant_config_new_probe (const GByteArray *ssid); +NMSupplicantConfig *nm_supplicant_config_new_probe (const GByteArray *ssid, + NMSetting8021x *setting); guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self); -- 1.8.1.4
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor