File 0006-Add-argon2-kdf-support.patch of Package grub2

From be88a47abf4302155a52856e853b8316c92f9629 Mon Sep 17 00:00:00 2001
From: Michael Chang <mchang@suse.com>
Date: Wed, 7 Jun 2023 20:39:44 +0800
Subject: [PATCH 6/9] Add argon2 kdf support

---
 Makefile.util.def                           |  29 ++++++
 grub-core/disk/luks2.c                      |  24 ++++-
 grub-core/lib/libgcrypt-argon2/cipher/kdf.c |  28 ++++++
 include/grub/gcry/argon2.h                  |  18 ++++
 util/grub-mkpasswd-argon2.c                 | 100 ++------------------
 5 files changed, 104 insertions(+), 95 deletions(-)
 create mode 100644 include/grub/gcry/argon2.h

diff --git a/Makefile.util.def b/Makefile.util.def
index ea1e5ecb0..b23163e64 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -201,6 +201,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBLZMA)';
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
@@ -221,6 +222,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBTASN1)';
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
@@ -239,6 +241,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -254,6 +257,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -280,6 +284,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -296,6 +301,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -335,6 +341,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -351,6 +358,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM) -lfuse';
   condition = COND_GRUB_MOUNT;
@@ -369,6 +377,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(FREETYPE_LIBS)';
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
@@ -388,6 +397,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -410,6 +420,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubkern.a;
   ldadd = libgrubgcry.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   cppflags = '-DGRUB_SETUP_FUNC=grub_util_bios_setup';
@@ -431,6 +442,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubkern.a;
   ldadd = libgrubgcry.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   cppflags = '-DGRUB_SETUP_FUNC=grub_util_sparc_setup';
@@ -448,6 +460,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   emu_condition = COND_NOT_emu;
@@ -464,6 +477,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -479,6 +493,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   emu_condition = COND_NOT_emu;
@@ -633,6 +648,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 
@@ -680,6 +696,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -722,6 +739,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -758,6 +776,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1334,6 +1353,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1349,6 +1369,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1364,6 +1385,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1380,6 +1402,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   condition = COND_HAVE_CXX;
@@ -1396,6 +1419,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1411,6 +1435,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1429,6 +1454,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1445,6 +1471,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
   emu_condition = COND_NOT_emu;
@@ -1464,6 +1491,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
@@ -1493,6 +1521,7 @@ program = {
   ldadd = libgrubmods.a;
   ldadd = libgrubgcry.a;
   ldadd = libgrubkern.a;
+  ldadd = libargon2.a;
   ldadd = grub-core/lib/gnulib/libgnu.a;
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
diff --git a/grub-core/disk/luks2.c b/grub-core/disk/luks2.c
index 30fd05eb1..a20de65a3 100644
--- a/grub-core/disk/luks2.c
+++ b/grub-core/disk/luks2.c
@@ -26,6 +26,7 @@
 #include <grub/crypto.h>
 #include <grub/partition.h>
 #include <grub/i18n.h>
+#include <grub/gcry/argon2.h>
 
 #include <base64.h>
 #include <json.h>
@@ -436,6 +437,8 @@ luks2_decrypt_key (grub_uint8_t *out_key,
   const gcry_md_spec_t *hash;
   gcry_err_code_t gcry_ret;
   grub_err_t ret;
+  gcry_error_t err;
+  unsigned long param[4] = {k->area.key_size, k->kdf.u.argon2.time, k->kdf.u.argon2.memory, k->kdf.u.argon2.cpus};
 
   if (!base64_decode (k->kdf.salt, grub_strlen (k->kdf.salt),
 		     (char *)salt, &saltlen))
@@ -444,13 +447,30 @@ luks2_decrypt_key (grub_uint8_t *out_key,
       goto err;
     }
 
+
   /* Calculate the binary area key of the user supplied passphrase. */
   switch (k->kdf.type)
     {
       case LUKS2_KDF_TYPE_ARGON2I:
+	err = my_kdf_derive (GRUB_GCRY_KDF_ARGON2, GRUB_GCRY_KDF_ARGON2I, param, 4,
+                       passphrase, passphraselen, salt, saltlen, NULL, 0, NULL, 0,
+                       param[0], area_key);
+	if (err)
+	  {
+	    ret = grub_crypto_gcry_error (err);
+	    goto err;
+	  }
+	break;
       case LUKS2_KDF_TYPE_ARGON2ID:
-	ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Argon2 not supported");
-	goto err;
+	err = my_kdf_derive (GRUB_GCRY_KDF_ARGON2, GRUB_GCRY_KDF_ARGON2ID, param, 4,
+                       passphrase, passphraselen, salt, saltlen, NULL, 0, NULL, 0,
+                       param[0], area_key);
+	if (err)
+	  {
+	    ret = grub_crypto_gcry_error (err);
+	    goto err;
+	  }
+	break;
       case LUKS2_KDF_TYPE_PBKDF2:
 	hash = grub_crypto_lookup_md_by_name (k->kdf.u.pbkdf2.hash);
 	if (!hash)
diff --git a/grub-core/lib/libgcrypt-argon2/cipher/kdf.c b/grub-core/lib/libgcrypt-argon2/cipher/kdf.c
index 8f24b99b5..f97cc324b 100644
--- a/grub-core/lib/libgcrypt-argon2/cipher/kdf.c
+++ b/grub-core/lib/libgcrypt-argon2/cipher/kdf.c
@@ -26,6 +26,7 @@ GRUB_MOD_LICENSE ("GPLv3+");
 #define xfree(a)         gcry_free ((a))
 #include "cipher.h"
 #include "kdf-internal.h"
+#include <grub/gcry/argon2.h>
 
 extern gcry_md_spec_t _gcry_digest_spec_blake2b_512;
 
@@ -730,3 +731,30 @@ gcry_kdf_close (gcry_kdf_hd_t h)
       break;
     }
 }
+
+gcry_error_t
+my_kdf_derive (int algo, int subalgo,
+               const unsigned long *params, unsigned int paramslen,
+               const unsigned char *pass, grub_size_t passlen,
+               const unsigned char *salt, grub_size_t saltlen,
+               const unsigned char *key, grub_size_t keylen,
+               const unsigned char *ad, grub_size_t adlen,
+               grub_size_t outlen, unsigned char *out)
+{
+  gcry_error_t err;
+  gcry_kdf_hd_t hd;
+
+  err = gcry_kdf_open (&hd, algo, subalgo, params, paramslen,
+                       pass, passlen, salt, saltlen, key, keylen,
+                       ad, adlen);
+  if (err)
+    return err;
+
+  err = gcry_kdf_compute (hd, NULL);
+
+  if (!err)
+    err = gcry_kdf_final (hd, outlen, out);
+
+  gcry_kdf_close (hd);
+  return err;
+}
diff --git a/include/grub/gcry/argon2.h b/include/grub/gcry/argon2.h
new file mode 100644
index 000000000..43e3c99a2
--- /dev/null
+++ b/include/grub/gcry/argon2.h
@@ -0,0 +1,18 @@
+#ifndef _ARGON2_H
+#define _ARGON2_H
+
+#define GRUB_GCRY_KDF_ARGON2   64
+#define GRUB_GCRY_KDF_ARGON2D  0
+#define GRUB_GCRY_KDF_ARGON2I  1
+#define GRUB_GCRY_KDF_ARGON2ID 2
+
+gcry_error_t
+my_kdf_derive (int algo, int subalgo,
+               const unsigned long *params, unsigned int paramslen,
+               const unsigned char *pass, grub_size_t passlen,
+               const unsigned char *salt, grub_size_t saltlen,
+               const unsigned char *key, grub_size_t keylen,
+               const unsigned char *ad, grub_size_t adlen,
+               grub_size_t outlen, unsigned char *out);
+
+#endif /* _ARGON2_H */
diff --git a/util/grub-mkpasswd-argon2.c b/util/grub-mkpasswd-argon2.c
index 6c92ce371..c73ae52a6 100644
--- a/util/grub-mkpasswd-argon2.c
+++ b/util/grub-mkpasswd-argon2.c
@@ -25,6 +25,7 @@
 #include <grub/util/misc.h>
 #include <grub/i18n.h>
 #include <grub/misc.h>
+#include <grub/gcry/argon2.h>
 
 #include <unistd.h>
 #include <stdio.h>
@@ -47,92 +48,6 @@
  *                            *
  ******************************/
 
-/* Algorithm IDs for the KDFs.  */
-enum gcry_kdf_algos
-  {
-    GCRY_KDF_NONE = 0,
-    GCRY_KDF_SIMPLE_S2K = 16,
-    GCRY_KDF_SALTED_S2K = 17,
-    GCRY_KDF_ITERSALTED_S2K = 19,
-    GCRY_KDF_PBKDF1 = 33,
-    GCRY_KDF_PBKDF2 = 34,
-    GCRY_KDF_SCRYPT = 48,
-    GCRY_KDF_ARGON2   = 64,
-    GCRY_KDF_BALLOON  = 65
-  };
-
-enum gcry_kdf_subalgo_argon2
-  {
-    GCRY_KDF_ARGON2D  = 0,
-    GCRY_KDF_ARGON2I  = 1,
-    GCRY_KDF_ARGON2ID = 2
-  };
-
-/* Derive a key from a passphrase.  */
-gpg_error_t gcry_kdf_derive (const void *passphrase, size_t passphraselen,
-                             int algo, int subalgo,
-                             const void *salt, size_t saltlen,
-                             unsigned long iterations,
-                             size_t keysize, void *keybuffer);
-
-/* Another API to derive a key from a passphrase.  */
-typedef struct gcry_kdf_handle *gcry_kdf_hd_t;
-
-typedef void (*gcry_kdf_job_fn_t) (void *priv);
-typedef int (*gcry_kdf_dispatch_job_fn_t) (void *jobs_context,
-                                           gcry_kdf_job_fn_t job_fn,
-                                           void *job_priv);
-typedef int (*gcry_kdf_wait_all_jobs_fn_t) (void *jobs_context);
-
-/* Exposed structure for KDF computation to decouple thread functionality.  */
-typedef struct gcry_kdf_thread_ops
-{
-  void *jobs_context;
-  gcry_kdf_dispatch_job_fn_t dispatch_job;
-  gcry_kdf_wait_all_jobs_fn_t wait_all_jobs;
-} gcry_kdf_thread_ops_t;
-
-gcry_error_t gcry_kdf_open (gcry_kdf_hd_t *hd, int algo, int subalgo,
-                            const unsigned long *param, unsigned int paramlen,
-                            const void *passphrase, size_t passphraselen,
-                            const void *salt, size_t saltlen,
-                            const void *key, size_t keylen,
-                            const void *ad, size_t adlen);
-gcry_error_t gcry_kdf_compute (gcry_kdf_hd_t h,
-                               const gcry_kdf_thread_ops_t *ops);
-gcry_error_t gcry_kdf_final (gcry_kdf_hd_t h, size_t resultlen, void *result);
-void gcry_kdf_close (gcry_kdf_hd_t h);
-
-static gcry_error_t
-my_kdf_derive (int parallel,
-               int algo, int subalgo,
-               const unsigned long *params, unsigned int paramslen,
-               const unsigned char *pass, size_t passlen,
-               const unsigned char *salt, size_t saltlen,
-               const unsigned char *key, size_t keylen,
-               const unsigned char *ad, size_t adlen,
-               size_t outlen, unsigned char *out)
-{
-  gcry_error_t err;
-  gcry_kdf_hd_t hd;
-
-  (void)parallel;
-
-  err = gcry_kdf_open (&hd, algo, subalgo, params, paramslen,
-                       pass, passlen, salt, saltlen, key, keylen,
-                       ad, adlen);
-  if (err)
-    return err;
-
-  err = gcry_kdf_compute (hd, NULL);
-
-  if (!err)
-    err = gcry_kdf_final (hd, outlen, out);
-
-  gcry_kdf_close (hd);
-  return err;
-}
-
 static void
 check_argon2 (void)
 {
@@ -170,7 +85,7 @@ check_argon2 (void)
     }
   };
   int i;
-  int subalgo = GCRY_KDF_ARGON2D;
+  int subalgo = GRUB_GCRY_KDF_ARGON2D;
   int count = 0;
 
  again:
@@ -178,8 +93,7 @@ check_argon2 (void)
   if (verbose)
     fprintf (stderr, "checking ARGON2 test vector %d\n", count);
 
-  err = my_kdf_derive (0,
-                       GCRY_KDF_ARGON2, subalgo, param, 4,
+  err = my_kdf_derive (GRUB_GCRY_KDF_ARGON2, subalgo, param, 4,
                        pass, 32, salt, 16, key, 8, ad, 12,
                        32, out);
   if (err)
@@ -194,10 +108,10 @@ check_argon2 (void)
     }
 
   /* Next algo */
-  if (subalgo == GCRY_KDF_ARGON2D)
-    subalgo = GCRY_KDF_ARGON2I;
-  else if (subalgo == GCRY_KDF_ARGON2I)
-    subalgo = GCRY_KDF_ARGON2ID;
+  if (subalgo == GRUB_GCRY_KDF_ARGON2D)
+    subalgo = GRUB_GCRY_KDF_ARGON2I;
+  else if (subalgo == GRUB_GCRY_KDF_ARGON2I)
+    subalgo = GRUB_GCRY_KDF_ARGON2ID;
 
   count++;
   if (count < 3)
-- 
2.40.1

openSUSE Build Service is sponsored by