[PATCH] unit: Test netlink multicast group registration
by Ossama Othman
Register for the rtnetlink RTNLGRP_LINK multicast group to test the
ELL netlink multicast group registration code. Only registration and
deregistration is performed for now. An actual RTNLGRP_LINK multicast
notification is not triggered.
---
unit/test-netlink.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/unit/test-netlink.c b/unit/test-netlink.c
index ce522a6..928830a 100644
--- a/unit/test-netlink.c
+++ b/unit/test-netlink.c
@@ -27,6 +27,7 @@
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
+#include <assert.h>
#include <ell/ell.h>
@@ -72,10 +73,16 @@ done:
l_main_quit();
}
+static void link_notification(uint16_t type, void const * data,
+ uint32_t len, void * user_data)
+{
+}
+
int main(int argc, char *argv[])
{
struct l_netlink *netlink;
struct ifinfomsg msg;
+ unsigned int link_id;
if (!l_main_init())
return -1;
@@ -91,8 +98,13 @@ int main(int argc, char *argv[])
l_netlink_send(netlink, RTM_GETLINK, NLM_F_DUMP, &msg, sizeof(msg),
getlink_callback, NULL, NULL);
+ link_id = l_netlink_register(netlink, RTNLGRP_LINK,
+ link_notification, NULL, NULL);
+
l_main_run();
+ assert(l_netlink_unregister(netlink, link_id));
+
l_netlink_destroy(netlink);
l_main_exit();
--
2.7.4
5 years, 3 months
[PATCH] netlink: Include <stdint.h> for fixed width types
by Ossama Othman
The prototypes in the ELL netlink.h header use the C99 fixed width
integer types. Include <stdint.h> to make netlink.h self-contained.
---
ell/netlink.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/ell/netlink.h b/ell/netlink.h
index d7baa89..b40933f 100644
--- a/ell/netlink.h
+++ b/ell/netlink.h
@@ -24,6 +24,7 @@
#define __ELL_NETLINK_H
#include <stdbool.h>
+#include <stdint.h>
#ifdef __cplusplus
extern "C" {
--
2.7.4
5 years, 3 months
[PATCH v2 1/3] key: Use keyring restriction keyctl
by Mat Martineau
Now that the restricted keyring patches have been applied to keys-next
and are on track for the v4.12 merge window, ELL needs to be updated for
the final API.
There are two significant changes from the previous API:
1. Key restrictions are now applied in a separate step after a keyring
is created, not at creation time.
2. The first key added to an empty, "chain" restricted keyring no longer
bypasses the signature check.
The latter required a change to l_keyring_new() so that the root key
could be added to a keyring after it is created but before it is
restricted. There's a new l_keyring_restrict() function to restrict an
existing keyring.
---
ell/key.c | 76 ++++++++++++++++++++++++++++++++++++++++-----------------------
ell/key.h | 13 ++++++-----
ell/tls.c | 48 ++++++++++++++++++++++++++++++++++------
3 files changed, 96 insertions(+), 41 deletions(-)
diff --git a/ell/key.c b/ell/key.c
index db9abb1..fba400f 100644
--- a/ell/key.c
+++ b/ell/key.c
@@ -81,6 +81,10 @@ struct keyctl_pkey_params {
};
#endif
+#ifndef KEYCTL_RESTRICT_KEYRING
+#define KEYCTL_RESTRICT_KEYRING 29
+#endif
+
static int32_t internal_keyring;
struct l_key {
@@ -89,7 +93,6 @@ struct l_key {
};
struct l_keyring {
- int type;
int32_t serial;
};
@@ -208,6 +211,17 @@ static long kernel_dh_compute(int32_t private, int32_t prime, int32_t base,
return result >= 0 ? result : -errno;
}
+static long kernel_restrict_keyring(int32_t serial, const char *keytype,
+ const char *restriction)
+{
+ long result;
+
+ result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial, keytype,
+ restriction);
+
+ return result >= 0 ? result : -errno;
+}
+
static long kernel_key_eds(int op, int32_t serial, const char *encoding,
const char *hash, const void *in, void *out,
size_t len_in, size_t len_out)
@@ -655,32 +669,47 @@ LIB_EXPORT bool l_key_verify(struct l_key *key,
(memcmp(data, compare_hash, hash_len) == 0);
}
-LIB_EXPORT struct l_keyring *l_keyring_new(enum l_keyring_type type,
- const struct l_keyring *trusted)
+LIB_EXPORT struct l_keyring *l_keyring_new(void)
{
struct l_keyring *keyring;
char *description;
- char *payload = NULL;
- size_t payload_length = 0;
if (!internal_keyring && !setup_internal_keyring())
return NULL;
- switch (type) {
- case L_KEYRING_SIMPLE:
- break;
- case L_KEYRING_TRUSTED_ASYM:
- case L_KEYRING_TRUSTED_ASYM_CHAIN:
+ keyring = l_new(struct l_keyring, 1);
+ description = l_strdup_printf("ell-keyring-%p", keyring);
+ keyring->serial = kernel_add_key("keyring", description, NULL, 0,
+ internal_keyring);
+ l_free(description);
+
+ if (keyring->serial < 0) {
+ l_free(keyring);
+ return NULL;
+ }
+
+ return keyring;
+}
+
+LIB_EXPORT bool l_keyring_restrict(struct l_keyring *keyring,
+ enum l_keyring_restriction res,
+ const struct l_keyring *trusted)
+{
+ char *restriction = NULL;
+ long result;
+
+ switch (res) {
+ case L_KEYRING_RESTRICT_ASYM:
+ case L_KEYRING_RESTRICT_ASYM_CHAIN:
{
char *option = "";
- if (type == L_KEYRING_TRUSTED_ASYM_CHAIN)
+ if (res == L_KEYRING_RESTRICT_ASYM_CHAIN)
option = ":chain";
- payload = l_strdup_printf(
- "restrict=asymmetric:key_or_keyring:%d%s",
- trusted ? trusted->serial : 0, option);
- payload_length = strlen(payload);
+ restriction = l_strdup_printf("key_or_keyring:%d%s",
+ trusted ? trusted->serial : 0,
+ option);
break;
}
@@ -689,21 +718,12 @@ LIB_EXPORT struct l_keyring *l_keyring_new(enum l_keyring_type type,
return NULL;
}
- keyring = l_new(struct l_keyring, 1);
- keyring->type = type;
- description = l_strdup_printf("ell-keyring-%p", keyring);
- keyring->serial = kernel_add_key("keyring", description, payload,
- payload_length,
- internal_keyring);
- l_free(description);
- l_free(payload);
+ result = kernel_restrict_keyring(keyring->serial, "asymmetric",
+ restriction);
- if (keyring->serial < 0) {
- l_free(keyring);
- keyring = NULL;
- }
+ l_free(restriction);
- return keyring;
+ return result == 0;
}
LIB_EXPORT void l_keyring_free(struct l_keyring *keyring)
diff --git a/ell/key.h b/ell/key.h
index 35c63eb..717bd44 100644
--- a/ell/key.h
+++ b/ell/key.h
@@ -40,10 +40,9 @@ enum l_key_type {
L_KEY_RSA,
};
-enum l_keyring_type {
- L_KEYRING_SIMPLE = 0,
- L_KEYRING_TRUSTED_ASYM,
- L_KEYRING_TRUSTED_ASYM_CHAIN,
+enum l_keyring_restriction {
+ L_KEYRING_RESTRICT_ASYM = 0,
+ L_KEYRING_RESTRICT_ASYM_CHAIN,
};
enum l_key_cipher_type {
@@ -89,8 +88,10 @@ bool l_key_verify(struct l_key *key, enum l_key_cipher_type cipher,
enum l_checksum_type checksum, const void *data,
const void *sig, size_t len_data, size_t len_sig);
-struct l_keyring *l_keyring_new(enum l_keyring_type type,
- const struct l_keyring *trust);
+struct l_keyring *l_keyring_new(void);
+
+bool l_keyring_restrict(struct l_keyring *keyring, enum l_keyring_restriction res,
+ const struct l_keyring *trust);
void l_keyring_free(struct l_keyring *keyring);
void l_keyring_free_norevoke(struct l_keyring *keyring);
diff --git a/ell/tls.c b/ell/tls.c
index 06e3341..6636fbf 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -2469,9 +2469,10 @@ static void tls_key_cleanup(struct l_key **p)
l_key_free_norevoke(*p);
}
-static int tls_cert_verify_with_keyring(struct tls_cert *cert,
+static bool tls_cert_verify_with_keyring(struct tls_cert *cert,
struct l_keyring *ring,
- struct tls_cert *root)
+ struct tls_cert *root,
+ struct l_keyring *trusted)
{
if (!cert)
return true;
@@ -2488,14 +2489,33 @@ static int tls_cert_verify_with_keyring(struct tls_cert *cert,
!memcmp(cert->asn1, root->asn1, root->size))
return true;
- if (tls_cert_verify_with_keyring(cert->issuer, ring, root)) {
+ if (tls_cert_verify_with_keyring(cert->issuer, ring, root, trusted)) {
L_AUTO_CLEANUP_VAR(struct l_key *, key, tls_key_cleanup);
key = l_key_new(L_KEY_RSA, cert->asn1, cert->size);
if (!key)
return false;
- return l_keyring_link(ring, key);
+ if (!l_keyring_link(ring, key))
+ return false;
+
+ if (trusted || cert->issuer)
+ return true;
+
+ /*
+ * If execution reaches this point, it's known that:
+ * * No trusted root key was supplied, so the chain is only
+ * being checked against its own root
+ * * The keyring 'ring' is not restricted yet
+ * * The chain's root cert was just linked in to the
+ * previously empty keyring 'ring'.
+ *
+ * By restricting 'ring' now, the rest of the certs in
+ * the chain will have their signature validated using 'key'
+ * as the root.
+ */
+ return l_keyring_restrict(ring, L_KEYRING_RESTRICT_ASYM_CHAIN,
+ trusted);
}
return false;
@@ -2520,7 +2540,7 @@ bool tls_cert_verify_certchain(struct tls_cert *certchain,
L_AUTO_CLEANUP_VAR(struct l_key *, ca_key, tls_key_cleanup);
ca_key = NULL;
- ca_ring = l_keyring_new(L_KEYRING_SIMPLE, NULL);
+ ca_ring = l_keyring_new();
if (!ca_ring)
return false;
@@ -2529,11 +2549,25 @@ bool tls_cert_verify_certchain(struct tls_cert *certchain,
return false;
}
- verify_ring = l_keyring_new(L_KEYRING_TRUSTED_ASYM_CHAIN, ca_ring);
+ verify_ring = l_keyring_new();
if (!verify_ring)
return false;
- return tls_cert_verify_with_keyring(certchain, verify_ring, ca_cert);
+ /*
+ * If a CA cert was supplied, restrict verify_ring now so
+ * everything else in certchain is validated against the CA.
+ * Otherwise, verify_ring will be restricted after the root of
+ * certchain is added to verify_ring by
+ * tls_cert_verify_with_keyring().
+ */
+ if (ca_ring && !l_keyring_restrict(verify_ring,
+ L_KEYRING_RESTRICT_ASYM_CHAIN,
+ ca_ring)) {
+ return false;
+ }
+
+ return tls_cert_verify_with_keyring(certchain, verify_ring, ca_cert,
+ ca_ring);
}
void tls_cert_free_certchain(struct tls_cert *cert)
--
2.12.2
5 years, 3 months
[PATCH 1/2] key: Use keyring restriction keyctl
by Mat Martineau
Now that the restricted keyring patches have been applied to keys-next
and are on track for the v4.12 merge window, ELL needs to be updated for
the final API.
There are two significant changes from the previous API:
1. Key restrictions are now applied in a separate step after a keyring
is created, not at creation time.
2. The first key added to an empty, "chain" restricted keyring no longer
bypasses the signature check.
The latter required a change to l_keyring_new() so that the root key
could be added to a keyring after it is created but before it is
restricted. There's a new l_keyring_restrict() function to restrict an
existing keyring.
---
ell/key.c | 76 ++++++++++++++++++++++++++++++++++++++++-----------------------
ell/key.h | 13 ++++++-----
ell/tls.c | 20 ++++++++++++-----
3 files changed, 69 insertions(+), 40 deletions(-)
diff --git a/ell/key.c b/ell/key.c
index db9abb1..fba400f 100644
--- a/ell/key.c
+++ b/ell/key.c
@@ -81,6 +81,10 @@ struct keyctl_pkey_params {
};
#endif
+#ifndef KEYCTL_RESTRICT_KEYRING
+#define KEYCTL_RESTRICT_KEYRING 29
+#endif
+
static int32_t internal_keyring;
struct l_key {
@@ -89,7 +93,6 @@ struct l_key {
};
struct l_keyring {
- int type;
int32_t serial;
};
@@ -208,6 +211,17 @@ static long kernel_dh_compute(int32_t private, int32_t prime, int32_t base,
return result >= 0 ? result : -errno;
}
+static long kernel_restrict_keyring(int32_t serial, const char *keytype,
+ const char *restriction)
+{
+ long result;
+
+ result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial, keytype,
+ restriction);
+
+ return result >= 0 ? result : -errno;
+}
+
static long kernel_key_eds(int op, int32_t serial, const char *encoding,
const char *hash, const void *in, void *out,
size_t len_in, size_t len_out)
@@ -655,32 +669,47 @@ LIB_EXPORT bool l_key_verify(struct l_key *key,
(memcmp(data, compare_hash, hash_len) == 0);
}
-LIB_EXPORT struct l_keyring *l_keyring_new(enum l_keyring_type type,
- const struct l_keyring *trusted)
+LIB_EXPORT struct l_keyring *l_keyring_new(void)
{
struct l_keyring *keyring;
char *description;
- char *payload = NULL;
- size_t payload_length = 0;
if (!internal_keyring && !setup_internal_keyring())
return NULL;
- switch (type) {
- case L_KEYRING_SIMPLE:
- break;
- case L_KEYRING_TRUSTED_ASYM:
- case L_KEYRING_TRUSTED_ASYM_CHAIN:
+ keyring = l_new(struct l_keyring, 1);
+ description = l_strdup_printf("ell-keyring-%p", keyring);
+ keyring->serial = kernel_add_key("keyring", description, NULL, 0,
+ internal_keyring);
+ l_free(description);
+
+ if (keyring->serial < 0) {
+ l_free(keyring);
+ return NULL;
+ }
+
+ return keyring;
+}
+
+LIB_EXPORT bool l_keyring_restrict(struct l_keyring *keyring,
+ enum l_keyring_restriction res,
+ const struct l_keyring *trusted)
+{
+ char *restriction = NULL;
+ long result;
+
+ switch (res) {
+ case L_KEYRING_RESTRICT_ASYM:
+ case L_KEYRING_RESTRICT_ASYM_CHAIN:
{
char *option = "";
- if (type == L_KEYRING_TRUSTED_ASYM_CHAIN)
+ if (res == L_KEYRING_RESTRICT_ASYM_CHAIN)
option = ":chain";
- payload = l_strdup_printf(
- "restrict=asymmetric:key_or_keyring:%d%s",
- trusted ? trusted->serial : 0, option);
- payload_length = strlen(payload);
+ restriction = l_strdup_printf("key_or_keyring:%d%s",
+ trusted ? trusted->serial : 0,
+ option);
break;
}
@@ -689,21 +718,12 @@ LIB_EXPORT struct l_keyring *l_keyring_new(enum l_keyring_type type,
return NULL;
}
- keyring = l_new(struct l_keyring, 1);
- keyring->type = type;
- description = l_strdup_printf("ell-keyring-%p", keyring);
- keyring->serial = kernel_add_key("keyring", description, payload,
- payload_length,
- internal_keyring);
- l_free(description);
- l_free(payload);
+ result = kernel_restrict_keyring(keyring->serial, "asymmetric",
+ restriction);
- if (keyring->serial < 0) {
- l_free(keyring);
- keyring = NULL;
- }
+ l_free(restriction);
- return keyring;
+ return result == 0;
}
LIB_EXPORT void l_keyring_free(struct l_keyring *keyring)
diff --git a/ell/key.h b/ell/key.h
index 35c63eb..717bd44 100644
--- a/ell/key.h
+++ b/ell/key.h
@@ -40,10 +40,9 @@ enum l_key_type {
L_KEY_RSA,
};
-enum l_keyring_type {
- L_KEYRING_SIMPLE = 0,
- L_KEYRING_TRUSTED_ASYM,
- L_KEYRING_TRUSTED_ASYM_CHAIN,
+enum l_keyring_restriction {
+ L_KEYRING_RESTRICT_ASYM = 0,
+ L_KEYRING_RESTRICT_ASYM_CHAIN,
};
enum l_key_cipher_type {
@@ -89,8 +88,10 @@ bool l_key_verify(struct l_key *key, enum l_key_cipher_type cipher,
enum l_checksum_type checksum, const void *data,
const void *sig, size_t len_data, size_t len_sig);
-struct l_keyring *l_keyring_new(enum l_keyring_type type,
- const struct l_keyring *trust);
+struct l_keyring *l_keyring_new(void);
+
+bool l_keyring_restrict(struct l_keyring *keyring, enum l_keyring_restriction res,
+ const struct l_keyring *trust);
void l_keyring_free(struct l_keyring *keyring);
void l_keyring_free_norevoke(struct l_keyring *keyring);
diff --git a/ell/tls.c b/ell/tls.c
index 06e3341..cd85fad 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -2471,7 +2471,8 @@ static void tls_key_cleanup(struct l_key **p)
static int tls_cert_verify_with_keyring(struct tls_cert *cert,
struct l_keyring *ring,
- struct tls_cert *root)
+ struct tls_cert *root,
+ struct l_keyring *trusted)
{
if (!cert)
return true;
@@ -2488,14 +2489,21 @@ static int tls_cert_verify_with_keyring(struct tls_cert *cert,
!memcmp(cert->asn1, root->asn1, root->size))
return true;
- if (tls_cert_verify_with_keyring(cert->issuer, ring, root)) {
+ if (tls_cert_verify_with_keyring(cert->issuer, ring, root, trusted)) {
+ bool result;
L_AUTO_CLEANUP_VAR(struct l_key *, key, tls_key_cleanup);
key = l_key_new(L_KEY_RSA, cert->asn1, cert->size);
if (!key)
return false;
- return l_keyring_link(ring, key);
+ result = l_keyring_link(ring, key);
+
+ if (result && !cert->issuer)
+ result = l_keyring_restrict(ring, L_KEYRING_RESTRICT_ASYM_CHAIN,
+ trusted);
+
+ return result;
}
return false;
@@ -2520,7 +2528,7 @@ bool tls_cert_verify_certchain(struct tls_cert *certchain,
L_AUTO_CLEANUP_VAR(struct l_key *, ca_key, tls_key_cleanup);
ca_key = NULL;
- ca_ring = l_keyring_new(L_KEYRING_SIMPLE, NULL);
+ ca_ring = l_keyring_new();
if (!ca_ring)
return false;
@@ -2529,11 +2537,11 @@ bool tls_cert_verify_certchain(struct tls_cert *certchain,
return false;
}
- verify_ring = l_keyring_new(L_KEYRING_TRUSTED_ASYM_CHAIN, ca_ring);
+ verify_ring = l_keyring_new();
if (!verify_ring)
return false;
- return tls_cert_verify_with_keyring(certchain, verify_ring, ca_cert);
+ return tls_cert_verify_with_keyring(certchain, verify_ring, ca_cert, ca_ring);
}
void tls_cert_free_certchain(struct tls_cert *cert)
--
2.12.2
5 years, 3 months
[PATCH] unit: Test generic netlink family registration
by Ossama Othman
Verify that genl family registration works by attempting to set a
family_appeared watch for the "nlctrl" family, and registering a
callback for it. The "nlctrl" generic netlink family always exists in
the kernel so it is a suitable family to use for testing the
family_appeared watch and related family registration operations.
---
unit/test-genl.c | 114 +++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 93 insertions(+), 21 deletions(-)
diff --git a/unit/test-genl.c b/unit/test-genl.c
index 44fbbb0..632eceb 100644
--- a/unit/test-genl.c
+++ b/unit/test-genl.c
@@ -29,6 +29,15 @@
#include <ell/ell.h>
+struct test_data
+{
+ struct l_genl_family *appeared_family;
+ struct l_genl_family *vanished_family;
+
+ unsigned int group_id;
+ bool vanished_called;
+};
+
static void do_debug(const char *str, void *user_data)
{
const char *prefix = user_data;
@@ -36,38 +45,102 @@ static void do_debug(const char *str, void *user_data)
l_info("%s%s", prefix, str);
}
+static void notify_callback(struct l_genl_msg * msg, void * user_data)
+{
+}
+
+static void family_appeared(void *user_data)
+{
+ struct test_data *data = user_data;
+
+ data->group_id = l_genl_family_register(data->appeared_family,
+ "notify",
+ notify_callback,
+ NULL,
+ NULL);
+}
+
static void family_vanished(void *user_data)
{
- bool *vanished_called = user_data;
+ struct test_data *data = user_data;
+
+ data->vanished_called = true;
+}
+
+static bool prep_family_appeared(struct l_genl *genl,
+ struct test_data *data)
+{
+ /*
+ * Set a family_appeared watch for the "nlctrl" family.
+ *
+ * The "nlctrl" generic netlink family always exists in the
+ * kernel so it is a suitable family to use for testing
+ * the family_appeared watch and related family registration
+ * operations.
+ */
+ data->appeared_family = l_genl_family_new(genl, "nlctrl");
+
+ return l_genl_family_set_watches(data->appeared_family,
+ family_appeared, NULL,
+ data, NULL);
+}
+
+static bool prep_family_vanished(struct l_genl *genl,
+ struct test_data *data)
+{
+ /*
+ * Use a bogus family name to trigger the vanished watch to
+ * be called during the ELL event loop run.
+ */
+ static const char BOGUS_GENL_NAME[] = "bogus_genl_family";
+
+ data->vanished_family = l_genl_family_new(genl, BOGUS_GENL_NAME);
+ return l_genl_family_set_watches(data->vanished_family,
+ NULL, family_vanished,
+ data, NULL);
+}
- *vanished_called = true;
+static bool check_test_data(struct test_data *data)
+{
+ return data->group_id != 0 && data->vanished_called;
}
static void idle_callback(struct l_idle *idle, void *user_data)
{
+ struct test_data *data = user_data;
static int count = 0;
/*
- * Allow the main loop to iterate at least twice to allow the
- * generic netlink watches to be called.
+ * Exit the event loop if the desired results have been
+ * obtained, but limit the number of iterations to prevent the
+ * loop from running indefinetely if the conditions for
+ * success are never reached.
+ *
+ * Allow the main loop to iterate at least four times to allow
+ * the generic netlink watches and family registration to be
+ * called and completed, respectively.
*/
- if (++count > 1)
+ if (check_test_data(data) || ++count > 3)
l_main_quit();
}
+static bool destroy_test_data(struct test_data *data)
+{
+ bool unregistered =
+ l_genl_family_unregister(data->appeared_family,
+ data->group_id);
+
+ l_genl_family_unref(data->vanished_family);
+ l_genl_family_unref(data->appeared_family);
+
+ return unregistered;
+}
+
int main(int argc, char *argv[])
{
struct l_genl *genl;
- struct l_genl_family *family;
struct l_idle *idle;
-
- /*
- * Use a bogus family name to trigger the vanished watch to
- * be called.
- */
- static const char BOGUS_GENL_NAME[] = "bogus_genl_family";
-
- bool vanished_called = false;
+ struct test_data data = { .group_id = 0 };
if (!l_main_init())
return -1;
@@ -78,22 +151,21 @@ int main(int argc, char *argv[])
l_genl_set_debug(genl, do_debug, "[GENL] ", NULL);
- family = l_genl_family_new(genl, BOGUS_GENL_NAME);
- l_genl_family_set_watches(family, NULL, family_vanished,
- &vanished_called, NULL);
+ assert(prep_family_appeared(genl, &data));
+ assert(prep_family_vanished(genl, &data));
- idle = l_idle_create(idle_callback, NULL, NULL);
+ idle = l_idle_create(idle_callback, &data, NULL);
l_main_run();
l_idle_remove(idle);
- l_genl_family_unref(family);
+ assert(check_test_data(&data));
+ assert(destroy_test_data(&data));
+
l_genl_unref(genl);
l_main_exit();
- assert(vanished_called);
-
return 0;
}
--
2.7.4
5 years, 3 months