Blob Blame History Raw
From 85bbc387c3f7bcfd094b78e2fc2779e27aca348f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Tue, 20 Nov 2018 11:56:09 +0100
Subject: [PATCH 10/15] lib: refuse to activate profile if unsupported feature
 was selected

Resolves:
https://github.com/pbrezina/authselect/issues/107
---
 include/authselect.h        |  1 +
 src/lib/authselect.c        | 44 +++++++++++++++++++++++++++++++++++++
 src/lib/util/string.c       | 40 +++++++++++++++++++++++++++++++++
 src/lib/util/string.h       |  6 +++++
 src/lib/util/string_array.c | 30 +++++++++++++++++++++++++
 src/lib/util/string_array.h | 15 +++++++++++++
 src/tests/Makefile.am       |  1 +
 7 files changed, 137 insertions(+)

diff --git a/include/authselect.h b/include/authselect.h
index 462f54291e8283d5778005ac1747dd03258584be..110f22a864157f898541be573e800ed7d6c38e0a 100644
--- a/include/authselect.h
+++ b/include/authselect.h
@@ -70,6 +70,7 @@ enum authselect_profile_type {
  *   authselect and @force_overwrite must be set to true to force
  *   overwrite the existing files.
  * - ENOENT if the profile was not found.
+ * - EINVAL if unsupported feature was provided.
  * - Other errno code on generic error.
  */
 int
diff --git a/src/lib/authselect.c b/src/lib/authselect.c
index 3148f63b60534480b05b1ddb5587e4ed09aaa84a..1b4337e2e0eae4f298e59f90cc9c8659891e23f3 100644
--- a/src/lib/authselect.c
+++ b/src/lib/authselect.c
@@ -29,6 +29,45 @@
 #include "lib/files/files.h"
 #include "lib/profiles/profiles.h"
 
+static bool
+authselect_check_features(const struct authselect_profile *profile,
+                          const char **features)
+{
+    const char *similar;
+    bool result = true;
+    char **supported;
+    int i;
+
+    if (features == NULL) {
+        return true;
+    }
+
+    supported = authselect_profile_features(profile);
+    if (supported == NULL) {
+        ERROR("Unable to obtain supported features");
+        return false;
+    }
+
+    for (i = 0; features[i] != NULL; i++) {
+        if (string_array_has_value(supported, features[i])) {
+            continue;
+        }
+
+        result = false;
+        similar = string_array_find_similar(features[i], supported, 5);
+        if (similar != NULL) {
+            ERROR("Unknown profile feature [%s], did you mean [%s]?",
+                  features[i], similar);
+        } else {
+            ERROR("Unknown profile feature [%s]", features[i]);
+        }
+    }
+
+    string_array_free(supported);
+
+    return result;
+}
+
 _PUBLIC_ void
 authselect_set_debug_fn(authselect_debug_fn fn, void *pvt)
 {
@@ -53,6 +92,11 @@ authselect_activate(const char *profile_id,
         return ret;
     }
 
+    if (!authselect_check_features(profile, features)) {
+        ret = EINVAL;
+        goto done;
+    }
+
     if (force_overwrite) {
         INFO("Enforcing activation!");
         ret = authselect_profile_activate(profile, features);
diff --git a/src/lib/util/string.c b/src/lib/util/string.c
index 73ae778663bd8db024620a58987833ede60307d1..e6803bcd4e41adc05eaa623089009b17ccd4f49b 100644
--- a/src/lib/util/string.c
+++ b/src/lib/util/string.c
@@ -329,3 +329,43 @@ string_replace_shake(char *str, size_t original_length)
         str[pos] = '\0';
     }
 }
+
+static int
+min3(unsigned int a, unsigned int b, unsigned int c)
+{
+    if (a < b && a < c) {
+        return a;
+    } else if (b < a && b < c) {
+        return b;
+    }
+
+    return c;
+}
+
+int
+string_levenshtein(const char *a, const char *b)
+{
+    unsigned int len_a = strlen(a);
+    unsigned int len_b = strlen(b);
+    unsigned int x;
+    unsigned int y;
+    unsigned int last_diag;
+    unsigned int old_diag;
+    unsigned int column[len_a + 1];
+
+    for (y = 1; y <= len_a; y++) {
+        column[y] = y;
+    }
+
+    for (x = 1; x <= len_b; x++) {
+        column[0] = x;
+        for (y = 1, last_diag = x - 1; y <= len_a; y++) {
+            old_diag = column[y];
+            column[y] = min3(column[y] + 1, column[y - 1] + 1,
+                             last_diag + (a[y - 1] == b[x - 1] ? 0 : 1));
+            last_diag = old_diag;
+        }
+    }
+
+    return column[len_a];
+}
diff --git a/src/lib/util/string.h b/src/lib/util/string.h
index d7ca04491e5ceff20bd1d6e136ea151166156546..e550d853d3fa0699909b84cc9febdae9d5884b9f 100644
--- a/src/lib/util/string.h
+++ b/src/lib/util/string.h
@@ -179,4 +179,10 @@ string_remove_remainder(char *str, size_t from);
 void
 string_replace_shake(char *str, size_t original_length);
 
+/**
+ * Compute Levenshtein distance of two strings.
+ */
+int
+string_levenshtein(const char *a, const char *b);
+
 #endif /* _STRING_H_ */
diff --git a/src/lib/util/string_array.c b/src/lib/util/string_array.c
index 43e30d0008b5709f97da9c43f8f2c28ef2475df5..e56d66bdcce7c8a1cf99f9b91068614c4b8d3d81 100644
--- a/src/lib/util/string_array.c
+++ b/src/lib/util/string_array.c
@@ -25,6 +25,7 @@
 
 #include "common/common.h"
 #include "lib/util/string_array.h"
+#include "lib/util/string.h"
 
 char **
 string_array_create(size_t num_items)
@@ -195,3 +196,32 @@ string_array_sort(char **array)
     qsort(array, string_array_count(array), sizeof(char *),
           string_array_sort_callback);
 }
+
+const char *
+string_array_find_similar(const char *value, char **array, int max_distance)
+{
+    const char *word = NULL;
+    int current;
+    int best;
+    int i;
+
+    for (i = 0; array[i] != NULL; i++) {
+        current = string_levenshtein(value, array[i]);
+        if (word == NULL) {
+            best = current;
+            word = array[i];
+            continue;
+        }
+
+        if (current < best) {
+            best = current;
+            word = array[i];
+        }
+    }
+
+    if (best > max_distance) {
+        return NULL;
+    }
+
+    return word;
+}
diff --git a/src/lib/util/string_array.h b/src/lib/util/string_array.h
index 06aa46c008058163e5557d51e18258fa4e9a1523..ba9760b5d66a9619ca8edea5e3418c5cfbbec929 100644
--- a/src/lib/util/string_array.h
+++ b/src/lib/util/string_array.h
@@ -138,4 +138,19 @@ string_array_concat(char **to, char **items, bool unique);
 void
 string_array_sort(char **array);
 
+/**
+ * Find similar word inside a NULL-terminated array, based on Levenshtein
+ * distance algorithm.
+ *
+ * @param value        Value to search in @array.
+ * @param array        NULL-terminated string array.
+ * @param max_distance Maximum distance between two strings. If the real
+ *                     distance is greater then this value, those string
+ *                     are not considered as similar.
+ *
+ * @return Most similar word that was found or NULL if non was found.
+ */
+const char *
+string_array_find_similar(const char *value, char **array, int max_distance);
+
 #endif /* _STRING_ARRAY_H_ */
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 1cfebeab6bf3b36e760372b05482fd9f322feab1..e1505e6b5787a669065e5ea3c7acf55ea110c4da 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -23,6 +23,7 @@ check_PROGRAMS = $(TESTS)
 test_util_string_array_SOURCES = \
     test_util_string_array.c \
     ../lib/util/string_array.c \
+    ../lib/util/string.c \
     $(NULL)
 test_util_string_array_CFLAGS = \
     $(AM_CFLAGS)
-- 
2.17.2