File nm-certificate-formats.patch of Package NetworkManager

From ac71f8308f20ccb850881da11ae7de5d74f27445 Mon Sep 17 00:00:00 2001
From: Tambet Ingo <tambet@gmail.com>
Date: Fri, 21 Nov 2008 11:14:24 +0200
Subject: [PATCH] Support multiple formats of certificates.

---
 libnm-util/nm-setting-8021x.c                      |   15 +-
 libnm-util/nm-setting-8021x.h                      |    3 +
 libnm-util/nm-setting.h                            |    1 +
 src/supplicant-manager/nm-supplicant-config.c      |  198 ++++++++++++++++----
 .../nm-supplicant-settings-verify.c                |    6 +
 .../plugins/keyfile/nm-keyfile-connection.c        |   12 ++
 system-settings/plugins/keyfile/reader.c           |   79 ++++++--
 7 files changed, 250 insertions(+), 64 deletions(-)

diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c
index 04d2905..e7bb8eb 100644
--- a/libnm-util/nm-setting-8021x.c
+++ b/libnm-util/nm-setting-8021x.c
@@ -707,6 +707,9 @@ need_private_key_password (GByteArray *key, const char *password)
 	GError *error = NULL;
 	gboolean needed = TRUE;
 
+	if (key && key->data && g_str_has_prefix ((char *) key->data, NM_SETTING_802_1X_CK_FORMAT_FILE))
+		return FALSE;
+
 	/* See if a private key password is needed, which basically is whether
 	 * or not the private key is a PKCS#12 file or not, since PKCS#1 files
 	 * are decrypted by the settings service.
@@ -1368,7 +1371,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "CA certificate",
 							   "CA certificate",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_CA_PATH,
@@ -1384,7 +1387,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "Client certificate",
 							   "Client certificate",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_PHASE1_PEAPVER,
@@ -1432,7 +1435,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "Phase2 CA certificate",
 							   "Phase2 CA certificate",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_PHASE2_CA_PATH,
@@ -1448,7 +1451,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "Phase2 client certificate",
 							   "Phase2 client certificate",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_PASSWORD,
@@ -1464,7 +1467,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "Private key",
 							   "Private key",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_PRIVATE_KEY_PASSWORD,
@@ -1480,7 +1483,7 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 							   "Phase2 private key",
 							   "Phase2 private key",
 							   DBUS_TYPE_G_UCHAR_ARRAY,
-							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET));
+							   G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_CERTIFICATE));
 
 	g_object_class_install_property
 		(object_class, PROP_PHASE2_PRIVATE_KEY_PASSWORD,
diff --git a/libnm-util/nm-setting-8021x.h b/libnm-util/nm-setting-8021x.h
index 91d494d..2c063b9 100644
--- a/libnm-util/nm-setting-8021x.h
+++ b/libnm-util/nm-setting-8021x.h
@@ -82,6 +82,9 @@ GQuark nm_setting_802_1x_error_quark (void);
 #define NM_SETTING_802_1X_PIN "pin"
 #define NM_SETTING_802_1X_PSK "psk"
 
+#define NM_SETTING_802_1X_CK_FORMAT_ID   "id:"
+#define NM_SETTING_802_1X_CK_FORMAT_FILE "file:"
+
 typedef struct {
 	NMSetting parent;
 } NMSetting8021x;
diff --git a/libnm-util/nm-setting.h b/libnm-util/nm-setting.h
index 8303c74..77f8d4b 100644
--- a/libnm-util/nm-setting.h
+++ b/libnm-util/nm-setting.h
@@ -56,6 +56,7 @@ GQuark nm_setting_error_quark (void);
 #define NM_SETTING_PARAM_REQUIRED     (1 << (1 + G_PARAM_USER_SHIFT))
 #define NM_SETTING_PARAM_SECRET       (1 << (2 + G_PARAM_USER_SHIFT))
 #define NM_SETTING_PARAM_FUZZY_IGNORE (1 << (3 + G_PARAM_USER_SHIFT))
+#define NM_SETTING_PARAM_CERTIFICATE  (1 << (4 + G_PARAM_USER_SHIFT))
 
 #define NM_SETTING_NAME "name"
 
diff --git a/src/supplicant-manager/nm-supplicant-config.c b/src/supplicant-manager/nm-supplicant-config.c
index d087b2c..a80cc93 100644
--- a/src/supplicant-manager/nm-supplicant-config.c
+++ b/src/supplicant-manager/nm-supplicant-config.c
@@ -554,6 +554,165 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	return TRUE;
 }
 
+static gboolean
+add_certificates (NMSupplicantConfig *self, NMSetting8021x *setting, const char *connection_uid)
+{
+	const GByteArray *array;
+	const char *str;
+	char *value;
+	gboolean send_private_key_passwd;
+	gboolean send_client_cert;
+	gboolean success;
+
+	array = nm_setting_802_1x_get_ca_cert (setting);
+	if (array && array->data) {
+		str = (char *) array->data;
+
+		if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID))
+			nm_supplicant_config_add_option (self, "ca_cert_id",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+											 -1, FALSE);
+		else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE))
+			nm_supplicant_config_add_option (self, "ca_cert",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+											 -1, FALSE);
+		else {
+			ADD_BLOB_VAL (array, "ca_cert", connection_uid);
+		}
+	}
+
+	array = nm_setting_802_1x_get_private_key (setting);
+	if (array && array->data) {
+		str = (char *) array->data;
+
+		if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID)) {
+			nm_supplicant_config_add_option (self, "key_id",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+											 -1, FALSE);
+
+			send_private_key_passwd = FALSE;
+			send_client_cert = TRUE;
+		} else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE)) {
+			nm_supplicant_config_add_option (self, "private_key",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+											 -1, FALSE);
+
+			send_private_key_passwd = TRUE;
+			send_client_cert = TRUE;
+		} else {
+			ADD_BLOB_VAL (array, "private_key", connection_uid);
+
+			if (nm_setting_802_1x_get_private_key_type (setting) == NM_SETTING_802_1X_CK_TYPE_PKCS12) {
+				send_private_key_passwd = TRUE;
+				send_client_cert = FALSE;
+			} else {
+				send_private_key_passwd = FALSE;
+				send_client_cert = TRUE;
+			}
+		}
+	}
+
+	if (send_private_key_passwd) {
+		ADD_STRING_VAL (nm_setting_802_1x_get_private_key_password (setting),
+						"private_key_passwd", FALSE, FALSE, TRUE);
+	} 
+
+	if (send_client_cert) {
+		array = nm_setting_802_1x_get_client_cert (setting);
+		if (array && array->data) {
+			str = (char *) array->data;
+
+			if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID))
+				nm_supplicant_config_add_option (self, "cert_id", 
+												 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+												 -1, FALSE);
+			else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE))
+				nm_supplicant_config_add_option (self, "client_cert",
+												 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+												 -1, FALSE);
+			else {
+				ADD_BLOB_VAL (array, "client_cert", connection_uid);
+			}
+		}
+	}
+
+	/* phase 2 */
+
+	array = nm_setting_802_1x_get_phase2_ca_cert (setting);
+	if (array && array->data) {
+		str = (char *) array->data;
+
+		if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID)) {
+			nm_supplicant_config_add_option (self, "ca_cert2_id",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+											 -1, FALSE);
+		} else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE)) {
+			nm_supplicant_config_add_option (self, "ca_cert2",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+											 -1, FALSE);
+		} else {
+			ADD_BLOB_VAL (array, "ca_cert", connection_uid);
+		}
+	}
+
+	array = nm_setting_802_1x_get_phase2_private_key (setting);
+	if (array && array->data) {
+		str = (char *) array->data;
+
+		if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID)) {
+			nm_supplicant_config_add_option (self, "key2_id",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+											 -1, FALSE);
+
+			send_private_key_passwd = FALSE;
+			send_client_cert = TRUE;
+		} else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE)) {
+			nm_supplicant_config_add_option (self, "private_key2",
+											 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+											 -1, FALSE);
+
+			send_private_key_passwd = TRUE;
+			send_client_cert = TRUE;
+		} else {
+			ADD_BLOB_VAL (array, "private_key2", connection_uid);
+
+			if (nm_setting_802_1x_get_phase2_private_key_type (setting) == NM_SETTING_802_1X_CK_TYPE_PKCS12) {
+				send_private_key_passwd = TRUE;
+				send_client_cert = FALSE;
+			} else {
+				send_private_key_passwd = FALSE;
+				send_client_cert = TRUE;
+			}
+		}
+	}
+
+	if (send_private_key_passwd) {
+		ADD_STRING_VAL (nm_setting_802_1x_get_phase2_private_key_password (setting),
+						"private_key2_passwd", FALSE, FALSE, TRUE);
+	} 
+
+	if (send_client_cert) {
+		array = nm_setting_802_1x_get_phase2_client_cert (setting);
+		if (array && array->data) {
+			str = (char *) array->data;
+
+			if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_ID)) {
+				nm_supplicant_config_add_option (self, "cert2_id", 
+												 str + strlen (NM_SETTING_802_1X_CK_FORMAT_ID),
+												 -1, FALSE);
+			} else if (g_str_has_prefix (str, NM_SETTING_802_1X_CK_FORMAT_FILE)) {
+				nm_supplicant_config_add_option (self, "client_cert2",
+												 str + strlen (NM_SETTING_802_1X_CK_FORMAT_FILE),
+												 -1, FALSE);
+			} else {
+				ADD_BLOB_VAL (array, "client_cert2", connection_uid);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
 gboolean
 nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
                                         NMSetting8021x *setting,
@@ -564,7 +723,6 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 	char *value, *tmp;
 	gboolean success;
 	GString *phase1, *phase2;
-	const GByteArray *array;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
 	g_return_val_if_fail (setting != NULL, FALSE);
@@ -620,45 +778,11 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 		ADD_STRING_VAL (phase2->str, "phase2", FALSE, FALSE, FALSE);
 	g_string_free (phase2, TRUE);
 
-	ADD_BLOB_VAL (nm_setting_802_1x_get_ca_cert (setting), "ca_cert", connection_uid);
-
-	array = nm_setting_802_1x_get_private_key (setting);
-	if (array) {
-		ADD_BLOB_VAL (array, "private_key", connection_uid);
-
-		switch (nm_setting_802_1x_get_private_key_type (setting)) {
-		case NM_SETTING_802_1X_CK_TYPE_PKCS12:
-			/* Only add the private key password for PKCS#12 keys */
-			ADD_STRING_VAL (nm_setting_802_1x_get_private_key_password (setting), "private_key_passwd", FALSE, FALSE, TRUE);
-			break;
-		default:
-			/* Only add the client cert if the private key is not PKCS#12 */
-			ADD_BLOB_VAL (nm_setting_802_1x_get_client_cert (setting), "client_cert", connection_uid);
-			break;
-		}
-	}
-
-	ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_ca_cert (setting), "ca_cert2", connection_uid);
-
-	array = nm_setting_802_1x_get_phase2_private_key (setting);
-	if (array) {
-		ADD_BLOB_VAL (array, "private_key2", connection_uid);
-
-		switch (nm_setting_802_1x_get_phase2_private_key_type (setting)) {
-		case NM_SETTING_802_1X_CK_TYPE_PKCS12:
-			/* Only add the private key password for PKCS#12 keys */
-			ADD_STRING_VAL (nm_setting_802_1x_get_phase2_private_key_password (setting), "private_key2_passwd", FALSE, FALSE, TRUE);
-			break;
-		default:
-			/* Only add the client cert if the private key is not PKCS#12 */
-			ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_client_cert (setting), "client_cert2", connection_uid);
-			break;
-		}
-	}
-
 	ADD_STRING_VAL (nm_setting_802_1x_get_identity (setting), "identity", FALSE, FALSE, FALSE);
 	ADD_STRING_VAL (nm_setting_802_1x_get_anonymous_identity (setting), "anonymous_identity", FALSE, FALSE, FALSE);
 
+	add_certificates (self, setting, connection_uid);
+
 	return TRUE;
 }
 
diff --git a/src/supplicant-manager/nm-supplicant-settings-verify.c b/src/supplicant-manager/nm-supplicant-settings-verify.c
index b8bd9fc..71b16da 100644
--- a/src/supplicant-manager/nm-supplicant-settings-verify.c
+++ b/src/supplicant-manager/nm-supplicant-settings-verify.c
@@ -102,16 +102,22 @@ static const struct Opt opt_table[] = {
 	{ "identity",           TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "password",           TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "ca_cert_id",         TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "cert_id",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key_passwd", TYPE_BYTES,   0, 1024, FALSE,  NULL },
+	{ "key_id",             TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "phase1",             TYPE_KEYWORD, 0, 0, TRUE, phase1_allowed },
 	{ "phase2",             TYPE_KEYWORD, 0, 0, TRUE, phase2_allowed },
 	{ "anonymous_identity", TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert2",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "ca_cert2_id",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "cert2_id",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key2_passwd",TYPE_BYTES,   0, 1024, FALSE,  NULL },
+	{ "key2_id",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "pin",                TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "pcsc",               TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "nai",                TYPE_BYTES,   0, 0, FALSE,  NULL },
diff --git a/system-settings/plugins/keyfile/nm-keyfile-connection.c b/system-settings/plugins/keyfile/nm-keyfile-connection.c
index c65b1b6..4b7cb0d 100644
--- a/system-settings/plugins/keyfile/nm-keyfile-connection.c
+++ b/system-settings/plugins/keyfile/nm-keyfile-connection.c
@@ -114,6 +114,18 @@ add_secrets (NMSetting *setting,
 	} else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_MAP_OF_STRING)) {
 		/* Flatten the string hash by pulling its keys/values out */
 		g_hash_table_foreach (g_value_get_boxed (value), copy_one_secret, secrets);
+	} else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_UCHAR_ARRAY)) {
+		const GByteArray *array;
+
+		array = (GByteArray *) g_value_get_boxed (value);
+		if (array && array->len > 0) {
+			GValue *v;
+
+			v = g_slice_new0 (GValue);
+			g_value_init (v, DBUS_TYPE_G_UCHAR_ARRAY);
+			g_value_copy (value, v);
+			g_hash_table_insert (secrets, g_strdup (key), v);
+		}
 	} else
 		g_message ("%s: unhandled secret %s type %s", __func__, key, G_VALUE_TYPE_NAME (value));
 }
diff --git a/system-settings/plugins/keyfile/reader.c b/system-settings/plugins/keyfile/reader.c
index 9efd130..b8c4099 100644
--- a/system-settings/plugins/keyfile/reader.c
+++ b/system-settings/plugins/keyfile/reader.c
@@ -29,6 +29,7 @@
 #include <nm-setting-ip4-config.h>
 #include <nm-setting-vpn.h>
 #include <nm-setting-connection.h>
+#include <nm-setting-8021x.h>
 #include <arpa/inet.h>
 #include <string.h>
 
@@ -337,6 +338,56 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 	g_strfreev (keys);
 }
 
+static void
+read_guchar_array (GKeyFile *file, NMSetting *setting, const char *key)
+{
+	gint *tmp;
+	GByteArray *array;
+	gsize length;
+	int i;
+
+	tmp = g_key_file_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
+	array = g_byte_array_sized_new (length);
+	for (i = 0; i < length; i++) {
+		int val = tmp[i];
+		unsigned char v = (unsigned char) (val & 0xFF);
+
+		if (val < 0 || val > 255)
+			g_warning ("Value out of range for a byte value");
+		else
+			g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
+	}
+
+	g_object_set (setting, key, array, NULL);
+	g_byte_array_free (array, TRUE);
+	g_free (tmp);
+}
+
+static void
+read_certificate (NMSetting *setting, GKeyFile *file, const char *key)
+{
+	char *value;
+
+	value = g_key_file_get_value (file, nm_setting_get_name (setting), key, NULL);
+	if (!value)
+		return;
+
+	if (g_str_has_prefix (value, NM_SETTING_802_1X_CK_FORMAT_ID) || 
+		g_str_has_prefix (value, NM_SETTING_802_1X_CK_FORMAT_FILE)) {
+		GByteArray *array;
+		gsize len;
+
+		len = strlen (value);
+		array = g_byte_array_sized_new (len);
+		g_byte_array_append (array, (guint8 *) value, len);
+		g_object_set (setting, key, array, NULL);
+		g_byte_array_free (array, TRUE);
+	} else
+		read_guchar_array (file, setting, key);
+
+	g_free (value);
+}
+
 typedef struct {
 	GKeyFile *keyfile;
 	gboolean secrets;
@@ -390,6 +441,12 @@ read_one_setting_value (NMSetting *setting,
 		return;
 	}
 
+	/* Certificates are handled differently */
+	if (flags & NM_SETTING_PARAM_CERTIFICATE) {
+		read_certificate (setting, file, key);
+		return;
+	}
+
 	type = G_VALUE_TYPE (value);
 
 	if (type == G_TYPE_STRING) {
@@ -432,27 +489,7 @@ read_one_setting_value (NMSetting *setting,
 		g_free (tmp_str);
 		g_object_set (setting, key, uint_val, NULL);
  	} else if (type == DBUS_TYPE_G_UCHAR_ARRAY) {
-		gint *tmp;
-		GByteArray *array;
-		gsize length;
-		int i;
-
-		tmp = g_key_file_get_integer_list (file, setting_name, key, &length, NULL);
-
-		array = g_byte_array_sized_new (length);
-		for (i = 0; i < length; i++) {
-			int val = tmp[i];
-			unsigned char v = (unsigned char) (val & 0xFF);
-
-			if (val < 0 || val > 255)
-				g_warning ("Value out of range for a byte value");
-			else
-				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
-		}
-
-		g_object_set (setting, key, array, NULL);
-		g_byte_array_free (array, TRUE);
-		g_free (tmp);
+		read_guchar_array (file, setting, key);
  	} else if (type == DBUS_TYPE_G_LIST_OF_STRING) {
 		gchar **sa;
 		gsize length;
-- 
1.6.0.2

openSUSE Build Service is sponsored by