summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING5
-rw-r--r--DCO37
-rw-r--r--src/include/s6-networking/sbearssl.h32
-rw-r--r--src/sbearssl/deps-lib/sbearssl3
-rw-r--r--src/sbearssl/sbearssl-internal.h9
-rw-r--r--src/sbearssl/sbearssl_sctx_init_full_generic.c87
-rw-r--r--src/sbearssl/sbearssl_sctx_set_policy_sni.c11
-rw-r--r--src/sbearssl/sbearssl_skey_storagelen.c16
-rw-r--r--src/sbearssl/sbearssl_skey_wipe.c26
-rw-r--r--src/sbearssl/sbearssl_sni_policy_add_keypair_file.c42
-rw-r--r--src/sbearssl/sbearssl_sni_policy_init.c75
-rw-r--r--src/sbearssl/sbearssl_sni_policy_vtable.c71
12 files changed, 414 insertions, 0 deletions
diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..6279422
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,5 @@
+ Please add a Signed-Off-By: line at the end of your commit,
+which certifies that you have the right and authority to pass
+it on as an open-source patch, as explicited in the Developer's
+Certificate of Origin available in this project's DCO file,
+or at https://developercertificate.org/
diff --git a/DCO b/DCO
new file mode 100644
index 0000000..8201f99
--- /dev/null
+++ b/DCO
@@ -0,0 +1,37 @@
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+(c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+(d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
diff --git a/src/include/s6-networking/sbearssl.h b/src/include/s6-networking/sbearssl.h
index 8de12ab..83bc376 100644
--- a/src/include/s6-networking/sbearssl.h
+++ b/src/include/s6-networking/sbearssl.h
@@ -13,6 +13,7 @@
#include <skalibs/stralloc.h>
#include <skalibs/genalloc.h>
#include <skalibs/tai.h>
+#include <skalibs/avltree.h>
/*
* Support library for bearssl.
@@ -153,6 +154,7 @@ extern int sbearssl_skey_from (sbearssl_skey *, br_skey const *, stralloc *) ;
extern int sbearssl_skey_to (sbearssl_skey const *, br_skey *, char *) ;
extern int sbearssl_skey_readfile (char const *, sbearssl_skey *, stralloc *) ;
+extern void sbearssl_skey_wipe (sbearssl_skey, char *) ;
/* Public keys */
@@ -262,6 +264,36 @@ extern int sbearssl_send_environment (br_ssl_engine_context *, sbearssl_handshak
extern void sbearssl_run (br_ssl_engine_context *, int *, tain_t const *, uint32_t, unsigned int, sbearssl_handshake_cbfunc_ref, sbearssl_handshake_cbarg *) gccattr_noreturn ;
+ /* Generic server policy class and server-side SNI implementation */
+
+typedef struct sbearssl_sni_map_s sbearssl_sni_map, *sbearssl_sni_map_ref ;
+struct sbearssl_sni_map_s
+{
+ char const *servername ;
+ sbearssl_skey skey ;
+ size_t chainindex ;
+ size_t chainlen ;
+} ;
+
+typedef struct sbearssl_sni_policy_context_s sbearssl_sni_policy_context, *sbearssl_sni_policy_context_ref ;
+struct sbearssl_sni_policy_context_s
+{
+ br_ssl_server_policy_class const *vtable ;
+ br_skey skey ;
+ avltree map ;
+ genalloc mapga ;
+ genalloc certga ;
+ stralloc storage ;
+}
+
+extern br_ssl_server_policy_class const sbearssl_sni_policy_vtable ;
+extern int sbearssl_sni_policy_init (sbearssl_sni_policy_context *) ;
+extern int sbearssl_sni_policy_add_keypair_file (sbearssl_sni_policy_context *, char const *, char const *, char const *) ;
+
+extern void sbearssl_sctx_init_full_generic (br_ssl_server_context *) ;
+extern void sbearssl_sctx_set_policy_sni (br_ssl_server_context *, sbearssl_sni_policy_context *) ;
+
+
/* s6-tlsc-io and s6-tlsd-io implementations */
extern void sbearssl_client_init_and_run (int *, tain_t const *, uint32_t, uint32_t, unsigned int, char const *, sbearssl_handshake_cbfunc_ref, sbearssl_handshake_cbarg *) gccattr_noreturn ;
diff --git a/src/sbearssl/deps-lib/sbearssl b/src/sbearssl/deps-lib/sbearssl
index 68b3ce1..c0d4507 100644
--- a/src/sbearssl/deps-lib/sbearssl
+++ b/src/sbearssl/deps-lib/sbearssl
@@ -24,8 +24,11 @@ sbearssl_rsa_pkey_to.o
sbearssl_rsa_skey_from.o
sbearssl_rsa_skey_to.o
sbearssl_run.o
+sbearssl_sctx_init_full_generic.o
+sbearssl_sctx_set_policy_sni.o
sbearssl_send_environment.o
sbearssl_server_init_and_run.o
+sbearssl_sni_policy_vtable.o
sbearssl_skey_from.o
sbearssl_skey_readfile.o
sbearssl_skey_to.o
diff --git a/src/sbearssl/sbearssl-internal.h b/src/sbearssl/sbearssl-internal.h
index 21a28d7..d8e0c57 100644
--- a/src/sbearssl/sbearssl-internal.h
+++ b/src/sbearssl/sbearssl-internal.h
@@ -75,4 +75,13 @@ extern int sbearssl_pem_push (br_pem_decoder_context *, char const *, size_t, sb
extern sbearssl_suiteinfo const *const sbearssl_suite_list ;
extern size_t const sbearssl_suite_list_len ;
+typedef struct sbearssl_sni_policy_node_s sbearssl_sni_policy_node, *sbearssl_policy_node_ref ;
+struct sbearssl_sni_policy_node_s
+{
+ size_t servername ;
+ sbearssl_skey skey ;
+ size_t chainindex ;
+ size_t chainlen ;
+} ;
+
#endif
diff --git a/src/sbearssl/sbearssl_sctx_init_full_generic.c b/src/sbearssl/sbearssl_sctx_init_full_generic.c
new file mode 100644
index 0000000..2c930c8
--- /dev/null
+++ b/src/sbearssl/sbearssl_sctx_init_full_generic.c
@@ -0,0 +1,87 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <bearssl.h>
+
+#include <s6-networking/sbearssl.h>
+
+static uint16_t const suites[] =
+{
+ BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* ec cipher */
+ BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* rsa cipher */
+ BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
+ BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
+ BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+ BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
+ BR_TLS_RSA_WITH_AES_128_GCM_SHA256,
+ BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
+ BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
+ BR_TLS_RSA_WITH_AES_256_GCM_SHA384,
+ BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
+ BR_TLS_RSA_WITH_AES_128_CCM,
+ BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
+ BR_TLS_RSA_WITH_AES_256_CCM,
+ BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
+ BR_TLS_RSA_WITH_AES_128_CCM_8,
+ BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
+ BR_TLS_RSA_WITH_AES_256_CCM_8,
+ BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ BR_TLS_RSA_WITH_AES_128_CBC_SHA256,
+ BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ BR_TLS_RSA_WITH_AES_256_CBC_SHA256,
+ BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ BR_TLS_RSA_WITH_AES_128_CBC_SHA,
+ BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ BR_TLS_RSA_WITH_AES_256_CBC_SHA,
+ BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
+} ;
+
+static br_hash_class const *hashes[] =
+{
+ &br_md5_vtable,
+ &br_sha1_vtable,
+ &br_sha224_vtable,
+ &br_sha256_vtable,
+ &br_sha384_vtable,
+ &br_sha512_vtable
+} ;
+
+void sbearssl_sctx_init_full_generic (br_ssl_server_context *sc)
+{
+ br_ssl_server_zero(sc) ;
+ br_ssl_engine_set_versions(&sc->eng, BR_TLS10, BR_TLS12) ;
+ br_ssl_engine_set_suites(&sc->eng, suites, sizeof(suites) / sizeof(suites[0])) ;
+ br_ssl_engine_set_default_ec(&sc->eng) ;
+
+ for (unsigned int i = br_md5_ID ; i <= br_sha512_ID ; i++)
+ br_ssl_engine_set_hash(&sc->eng, i, hashes[i-1]) ;
+
+ br_ssl_engine_set_prf10(&sc->eng, &br_tls10_prf) ;
+ br_ssl_engine_set_prf_sha256(&sc->eng, &br_tls12_sha256_prf) ;
+ br_ssl_engine_set_prf_sha384(&sc->eng, &br_tls12_sha384_prf) ;
+
+ br_ssl_engine_set_default_aes_cbc(&sc->eng) ;
+ br_ssl_engine_set_default_aes_ccm(&sc->eng) ;
+ br_ssl_engine_set_default_aes_gcm(&sc->eng) ;
+ br_ssl_engine_set_default_des_cbc(&sc->eng) ;
+ br_ssl_engine_set_default_chapol(&sc->eng) ;
+}
diff --git a/src/sbearssl/sbearssl_sctx_set_policy_sni.c b/src/sbearssl/sbearssl_sctx_set_policy_sni.c
new file mode 100644
index 0000000..166cd97
--- /dev/null
+++ b/src/sbearssl/sbearssl_sctx_set_policy_sni.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <bearssl.h>
+
+#include <s6-networking/sbearssl.h>
+
+void sbearssl_sctx_set_policy_sni (br_ssl_server_context *sc, sbearssl_sni_policy_context *pol)
+{
+ sc->chain_handler.vtable = pol->vtable ;
+ sc->policy_vtable = &sc->chain_handler.vtable ;
+}
diff --git a/src/sbearssl/sbearssl_skey_storagelen.c b/src/sbearssl/sbearssl_skey_storagelen.c
new file mode 100644
index 0000000..706b5f6
--- /dev/null
+++ b/src/sbearssl/sbearssl_skey_storagelen.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <s6-networking/sbearssl.h>
+
+size_t sbearssl_skey_storagelen (sbearssl_skey const *l)
+{
+ switch (l->type)
+ {
+ case BR_KEYTYPE_RSA :
+ return l->data.rsa.plen + l->data.rsa.qlen + l->data.rsa.dplen + l->data.rsa.dqlen + l->data.rsa.iqlen ;
+ case BR_KEYTYPE_EC :
+ return l->data.ec.xlen ;
+ default :
+ return 0 ;
+ }
+}
diff --git a/src/sbearssl/sbearssl_skey_wipe.c b/src/sbearssl/sbearssl_skey_wipe.c
new file mode 100644
index 0000000..208f89f
--- /dev/null
+++ b/src/sbearssl/sbearssl_skey_wipe.c
@@ -0,0 +1,26 @@
+/* ISC license. */
+
+#include <bearssl.h>
+
+#include <skalibs/bytestr.h>
+
+#include <s6-networking/sbearssl.h>
+
+void sbearssl_skey_wipe (sbearssl_skey *key, char *s)
+{
+ switch (key->type)
+ {
+ case BR_KEYTYPE_RSA :
+ byte_zzero(s + key->rsa.p, key->rsa.plen) ;
+ byte_zzero(s + key->rsa.q, key->rsa.qlen) ;
+ byte_zzero(s + key->rsa.dp, key->rsa.dplen) ;
+ byte_zzero(s + key->rsa.dq, key->rsa.dqlen) ;
+ byte_zzero(s + key->rsa.iq, key->rsa.iqlen) ;
+ break ;
+ case BR_KEYTYPE_EC :
+ byte_zzero(s + key->ec.x, key->ec.xlen) ;
+ break ;
+ default : break ;
+ }
+ byte_zzero(key, sizeof(sbearssl_skey)) ;
+}
diff --git a/src/sbearssl/sbearssl_sni_policy_add_keypair_file.c b/src/sbearssl/sbearssl_sni_policy_add_keypair_file.c
new file mode 100644
index 0000000..e6cc974
--- /dev/null
+++ b/src/sbearssl/sbearssl_sni_policy_add_keypair_file.c
@@ -0,0 +1,42 @@
+/* ISC license. */
+
+#include <string.h>
+#include <errno.h>
+
+#include <bearssl.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/avltree.h>
+
+#include <s6-networking/sbearssl.h>
+#include "sbearssl-internal.h"
+
+int sbearssl_sni_policy_add_keypair_file (sbearssl_sni_policy_context *pol, char const *servername, char const *certfile, char const *keyfile)
+{
+ size_t sabase = pol->storage.len ;
+ size_t gabase = genalloc_len(sbearssl_cert, &pol->certga) ;
+ size_t mbase = genalloc_len(sbearssl_sni_policy_node, &pol->mapga) ;
+ sbearssl_sni_policy_node node = { .servername = sabase, .chainindex = gabase } ;
+
+ if (!stralloc_catb(&pol->storage, servername, strlen(servername) + 1)) return 0 ;
+ if (!sbearssl_cert_readbigpem(certfile, &pol->certga, &pol->storage)) goto err0 ;
+ node.chainlen = genalloc_len(sbearssl_cert, &pol->certga) - node.chainindex ;
+ if (!sbearssl_skey_readfile(keyfile, &node.skey, &pol->storage)) goto err1 ;
+ if (!genalloc_catb(sbearssl_sni_policy_node, &pol->mapga, &node, 1)) goto err2 ;
+ if (!avltree_insert(&pol->map, mbase)) goto err3 ;
+ return 1 ;
+
+ err3:
+ if (mbase) genalloc_setlen(sbearssl_sni_policy_node, &pol->mapga, mbase) ;
+ else genalloc_free(sbearssl_sni_policy_node, &pol->mapga) ;
+ err2:
+ sbearssl_skey_wipe(&pol->skey, pol->storage.s) ;
+ err1:
+ if (gabase) genalloc_setlen(sbearssl_cert, &pol->certga, gabase) ;
+ else genalloc_free(sbearssl_sni_policy_node, &pol->mapga) ;
+ err0:
+ if (sabase) pol->storage.len = sabase ;
+ else stralloc_free(pol->storage) ;
+ return 0 ;
+}
diff --git a/src/sbearssl/sbearssl_sni_policy_init.c b/src/sbearssl/sbearssl_sni_policy_init.c
new file mode 100644
index 0000000..fd0e946
--- /dev/null
+++ b/src/sbearssl/sbearssl_sni_policy_init.c
@@ -0,0 +1,75 @@
+/* ISC license. */
+
+#include <errno.h>
+
+#include <bearssl.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/avltree.h>
+
+#include <s6-networking/sbearssl.h>
+
+#define INSTANCE(c) ((sbearssl_sni_policy_context *)(c))
+
+static int choose (br_ssl_server_policy_class const **pctx, br_ssl_server_context const *sc, br_ssl_server_choices *choices)
+{
+ sbearssl_sni_policy_context *pol = INSTANCE(pctx) ;
+ uint32_t n ;
+ char const *servername = br_ssl_engine_get_server_name(&sc->eng) ;
+ if (!avltree_search(&pol->map, servername, &n)
+ && (!servername[0] || !avltree_search(&pol->map, "", &n)))
+ return 0 ;
+ avltree_free(&pol->map) ;
+ copy_and_free(pol, n) ;
+}
+
+static uint32_t do_keyx (br_ssl_server_policy_class const **pctx, unsigned char *data, size_t *len)
+{
+ sbearssl_sni_policy_context *pol = INSTANCE(pctx) ;
+ switch (pol->skey.type)
+ {
+ case BR_KEYTYPE_RSA : return kx_rsa(pol, data, len) ;
+ case BR_KEYTYPE_EC : return kx_ec(pol, data, len) ;
+ default : return 0 ;
+ }
+}
+
+static size_t do_sign (br_ssl_server_policy_class const **pctx, unsigned int algo_id, unsigned char *data, size_t hv_len, size_t len)
+{
+ sbearssl_sni_policy_context *pol = INSTANCE(pctx) ;
+ switch (pol->skey.type)
+ {
+ case BR_KEYTYPE_RSA : return sign_rsa(pol, algo_id, data, hv_len, len) ;
+ case BR_KEYTYPE_EC : return sign_ec(pol, algo_id, data, hv_len, len) ;
+ default : return 0 ;
+ }
+}
+
+static br_ssl_server_policy_class const vtable =
+{
+ .context_size = sizeof(sbearssl_sni_policy_context),
+ .choose = &choose,
+ .do_keyx = &do_keyx,
+ .do_sign = &do_sign
+} ;
+
+static void *sbearssl_sni_policy_node_dtok (uint32_t d, void *data)
+{
+ return ((sbearssl_sni_policy_context *)data)->storage.s + d ;
+}
+
+static int sbearssl_sni_policy_node_cmp (void const *a, void const *b, void *data)
+{
+ (void)data ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+void sbearssl_sni_policy_init (sbearssl_sni_policy_context *pol)
+{
+ pol->vtable = &vtable ;
+ pol->map = avltree_zero ;
+ pol->mapga = genalloc_zero ;
+ pol->certga = genalloc_zero ;
+ pol->storage = GENALLOC_ZERO ;
+}
diff --git a/src/sbearssl/sbearssl_sni_policy_vtable.c b/src/sbearssl/sbearssl_sni_policy_vtable.c
new file mode 100644
index 0000000..3b39055
--- /dev/null
+++ b/src/sbearssl/sbearssl_sni_policy_vtable.c
@@ -0,0 +1,71 @@
+/* ISC license. */
+
+#include <bearssl.h>
+
+#include <s6-networking/sbearssl.h>
+
+#define INSTANCE(c) ((sbearssl_x509_small_context *)(c))
+
+static void start_chain (br_x509_class const **c, char const *server_name)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ ctx->minimal.vtable->start_chain(&ctx->minimal.vtable, server_name) ;
+
+ ctx->i = 0 ;
+}
+
+static void start_cert (br_x509_class const **c, uint32_t len)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ ctx->minimal.vtable->start_cert(&ctx->minimal.vtable, len) ;
+
+ if (!ctx->i) br_sha256_init(&ctx->hashctx) ;
+}
+
+static void append (br_x509_class const **c, unsigned char const *s, size_t len)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ ctx->minimal.vtable->append(&ctx->minimal.vtable, s, len) ;
+
+ if (!ctx->i) br_sha256_update(&ctx->hashctx, s, len) ;
+}
+
+static void end_cert (br_x509_class const **c)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ ctx->minimal.vtable->end_cert(&ctx->minimal.vtable) ;
+
+ if (!ctx->i) br_sha256_out(&ctx->hashctx, ctx->eehash) ;
+ ctx->i++ ;
+}
+
+static unsigned int end_chain (br_x509_class const **c)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ unsigned int r = ctx->minimal.vtable->end_chain(&ctx->minimal.vtable) ;
+ if (!r)
+ {
+ uint8_t mask = 1 ;
+ for (unsigned int i = 0 ; i < 6 ; i++, mask <<= 1)
+ if (ctx->elts[i].status)
+ *ctx->eltstatus |= ctx->elts[i].status < 0 ? 128 : mask ;
+ }
+ return r ;
+}
+
+static br_x509_pkey const *get_pkey(br_x509_class const *const *c, unsigned int *usages)
+{
+ sbearssl_x509_small_context *ctx = INSTANCE(c) ;
+ return ctx->minimal.vtable->get_pkey(&ctx->minimal.vtable, usages) ;
+}
+
+br_x509_class const sbearssl_x509_small_vtable =
+{
+ .context_size = sizeof(sbearssl_x509_small_context),
+ .start_chain = &start_chain,
+ .start_cert = &start_cert,
+ .append = &append,
+ .end_cert = &end_cert,
+ .end_chain = &end_chain,
+ .get_pkey = &get_pkey,
+} ;