[Date Prev][Date Next] [Chronological] [Thread] [Top]

Re: (ITS#7348) enhancement: check_password contrib module



Ceci est un message signC) cryptographiquement au format MIME.

--------------ms000000000208070102090606
Content-Type: multipart/mixed;
 boundary="------------050908090805020607010105"

This is a multi-part message in MIME format.
--------------050908090805020607010105
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: quoted-printable

Here is the patch.
--=20
Guillaume Rousse
INRIA, Direction des syst=E8mes d'information
Domaine de Voluceau
Rocquencourt - BP 105
78153 Le Chesnay
Tel: 01 39 63 58 31

--------------050908090805020607010105
Content-Type: text/x-patch;
 name="0001-ITS-7348-new-check_password-contrib-module.patch"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
 filename*0="0001-ITS-7348-new-check_password-contrib-module.patch"

=46rom fa2f6730171ab5755f56ca66715b723df726026d Mon Sep 17 00:00:00 2001
From: Guillaume Rousse <guillomovitch@gmail.com>
Date: Tue, 7 Aug 2012 17:39:21 +0200
Subject: [PATCH] ITS #7348: new check_password contrib module


Signed-off-by: Guillaume Rousse <Guillame.Rousse@inria.fr>
---
 contrib/slapd-modules/README                       |   3 +
 contrib/slapd-modules/check-password/Makefile      |  52 +++
 contrib/slapd-modules/check-password/README        | 146 +++++++++
 .../slapd-modules/check-password/check_password.c  | 356 +++++++++++++++=
++++++
 4 files changed, 557 insertions(+)
 create mode 100644 contrib/slapd-modules/check-password/Makefile
 create mode 100644 contrib/slapd-modules/check-password/README
 create mode 100644 contrib/slapd-modules/check-password/check_password.c=


diff --git a/contrib/slapd-modules/README b/contrib/slapd-modules/README
index db74379..d8005ff 100644
--- a/contrib/slapd-modules/README
+++ b/contrib/slapd-modules/README
@@ -20,6 +20,9 @@ allop (overlay)
 autogroup (overlay)
 	Automated updates of group memberships.
=20
+check_password (plugin)
+	External password quality check module for ppolicy
+
 cloak (overlay)
 	Hide specific attributes unless explicitely requested
=20
diff --git a/contrib/slapd-modules/check-password/Makefile b/contrib/slap=
d-modules/check-password/Makefile
new file mode 100644
index 0000000..42dd18f
--- /dev/null
+++ b/contrib/slapd-modules/check-password/Makefile
@@ -0,0 +1,52 @@
+
+LDAP_SRC =3D ../../..
+LDAP_BUILD =3D ../../..
+LDAP_INC =3D -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)=
/servers/slapd
+LDAP_LIB =3D $(LDAP_BUILD)/libraries/libldap_r/libldap_r.la \
+	$(LDAP_BUILD)/libraries/liblber/liblber.la
+
+CRACKLIB_PATH =3D /usr/share/cracklib/pw_dict
+CRACKLIB_INC =3D=20
+CRACKLIB_LIB =3D -lcrack
+
+CONFIG_PATH =3D /etc/openldap/check_password.conf
+
+LIBTOOL =3D $(LDAP_BUILD)/libtool
+CC =3D gcc
+OPT =3D -g -O2 -Wall
+DEFS =3D -DHAVE_CRACKLIB -DCRACKLIB_DICTPATH=3D"\"$(CRACKLIB_PATH)\"" \
+	-DCONFIG_FILE=3D"\"$(CONFIG_PATH)\"" -DDEBUG
+INCS =3D $(LDAP_INC) $(CRACKLIB_INC)
+LIBS =3D $(LDAP_LIB) $(CRACKLIB_LIB)
+
+PROGRAMS =3D check_password.la
+LTVER =3D 0:0:0
+
+prefix=3D/usr/local
+exec_prefix=3D$(prefix)
+ldap_subdir=3D/openldap
+
+libdir=3D$(exec_prefix)/lib
+libexecdir=3D$(exec_prefix)/libexec
+moduledir =3D $(libexecdir)$(ldap_subdir)
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+	$(LIBTOOL) --mode=3Dcompile $(CC) $(OPT) $(DEFS) $(INCS) -c $<
+
+all: $(PROGRAMS)
+
+check_password.la:	check_password.lo
+	$(LIBTOOL) --mode=3Dlink $(CC) $(OPT) -version-info $(LTVER) \
+	-rpath $(moduledir) -module -o $@ $? $(LIBS)
+
+clean:
+	rm -rf *.o *.lo *.la .libs
+
+install: $(PROGRAMS)
+	mkdir -p $(DESTDIR)$(moduledir)
+	for p in $(PROGRAMS) ; do \
+		$(LIBTOOL) --mode=3Dinstall cp $$p $(DESTDIR)$(moduledir) ; \
+	done
+
diff --git a/contrib/slapd-modules/check-password/README b/contrib/slapd-=
modules/check-password/README
new file mode 100644
index 0000000..10191c2
--- /dev/null
+++ b/contrib/slapd-modules/check-password/README
@@ -0,0 +1,146 @@
+
+check_password.c - OpenLDAP pwdChecker library
+
+2007-06-06 Michael Steinmann <msl@calivia.com>
+2008-01-30 Pierre-Yves Bonnetain <py.bonnetain@ba-cst.com>
+2009        Clement Oudot <clem.oudot@gmail.com> - LTB-project
+2009        Jerome HUET - LTB-project
+
+check_password.c is an OpenLDAP pwdPolicyChecker module used to check th=
e
+strength and quality of user-provided passwords.
+
+This module is used as an extension of the OpenLDAP password policy cont=
rols,
+see slapo-ppolicy(5) section pwdCheckModule.
+
+check_password.c will run a number of checks on the passwords to ensure =
minimum
+strength and quality requirements are met. Passwords that do not meet th=
ese
+requirements are rejected.
+
+
+Password checks
+---------------
+ - passwords shorter than 6 characters are rejected if cracklib is used =
(because
+   cracklib WILL reject them).
+
+ - syntactic checks controls how many different character classes are us=
ed
+   (lower, upper, digit and punctuation characters). The minimum number =
of
+   classes is defined in a configuration file. You can set the minimum f=
or each
+   class.
+
+ - passwords are checked against cracklib if cracklib is enabled at comp=
ile
+   time. It can be disabled in configuration file.
+
+INSTALLATION
+------------
+Use the provided Makefile to build the module.
+
+Compilation constants :
+
+CONFIG_FILE : Path to the configuration file.=20
+              Defaults to /etc/openldap/check_password.conf
+
+DEBUG : If defined, check_password will syslog() its actions.
+
+Build dependencies
+cracklib header files (link with -lcrack). The Makefile does not look fo=
r
+cracklib; you may need to provide the paths manually.
+
+Install into the slapd server module path.  Change the installation
+path to match with the OpenLDAP module path in the Makefile.
+
+The module may be defined with slapd.conf parameter "modulepath".
+
+USAGE
+-----
+To use this module you need to add objectClass pwdPolicyChecker with an =

+attribute 'pwdCheckModule: check_password.so' to a password policy entry=
=2E
+
+The module depends on a working cracklib installation including wordlist=
 files.
+If the wordlist files are not readable, the cracklib check will be skipp=
ed
+silently.
+
+Note: pwdPolicyChecker modules are loaded on *every* password change ope=
ration.
+
+Configuration
+-------------
+The configuration file (/etc/openldap/check_password.conf by default) co=
ntains
+parameters for the module. If the file is not found, parameters are give=
n their
+default value.
+
+The syntax of the file is :
+
+parameter value
+
+with spaces being delimiters. Parameter names ARE case sensitive (this m=
ay
+change in the future).
+
+Current parameters :
+
+-  useCracklib: integer. Default value: 1. Set it to 0 to disable crackl=
ib verification.
+   It has no effect if cracklib is not included at compile time.
+
+-  minPoints: integer. Default value: 3. Minimum number of quality point=
s a new
+   password must have to be accepted. One quality point is awarded for e=
ach character
+   class used in the password.
+
+- minUpper: integer. Defaut value: 0. Minimum upper characters expected.=

+
+- minLower: integer. Defaut value: 0. Minimum lower characters expected.=

+
+- minDigit: integer. Defaut value: 0. Minimum digit characters expected.=

+
+- minPunct: integer. Defaut value: 0. Minimum punctuation characters exp=
ected.
+
+Logs
+----
+If a user password is rejected by an OpenLDAP pwdChecker module, the use=
r will
+*not* get a detailed error message, this is by design.
+
+Typical user message from ldappasswd(5):
+  Result: Constraint violation (19)
+  Additional info: Password fails quality checking policy
+
+A more detailed message is written to the server log.
+
+Server log:
+  check_password_quality: module error: (check_password.so)
+  Password for dn=3D".." does not pass required number of strength check=
s (2 of 3)
+
+
+Caveats
+-------
+Runtime errors with this module (such as cracklib configuration problems=
) may
+bring down the slapd process.
+
+Use at your own risk.
+
+
+TODO
+----
+* use proper malloc function, see ITS#4998
+
+
+HISTORY
+-------
+* 2009-10-30 Clement OUDOT - LTB-project
+  Version 1.1
+   - Apply patch from Jerome HUET for minUpper/minLower/minDigit/minPunc=
t
+
+* 2009-02-05 Clement Oudot <clem.oudot@gmail.com> - LINAGORA Group
+  Version 1.0.3
+  - Add useCracklib parameter in config file (with help of Pascal Pejac)=

+  - Prefix log messages with "check_password: "
+  - Log what character type is found for quality checking
+
+* 2008-01-31 Pierre-Yves Bonnetain <py.bonnetain@ba-cst.com>
+  Version 1.0.2
+  - Several bug fixes.
+  - Add external config file
+
+* 2007-06-06 Michael Steinmann <msl@calivia.com>
+  Version 1.0.1
+  - add dn to error messages
+
+* 2007-06-02 Michael Steinmann <msl@calivia.com>
+  Version 1.0
+
diff --git a/contrib/slapd-modules/check-password/check_password.c b/cont=
rib/slapd-modules/check-password/check_password.c
new file mode 100644
index 0000000..da427e7
--- /dev/null
+++ b/contrib/slapd-modules/check-password/check_password.c
@@ -0,0 +1,356 @@
+/*
+ * check_password.c for OpenLDAP
+ *
+ * See LICENSE, README and INSTALL files
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <portable.h>
+#include <slap.h>
+
+#ifdef HAVE_CRACKLIB
+#include "crack.h"
+#endif
+
+#if defined(DEBUG)
+#include <syslog.h>
+#endif
+
+#ifndef CRACKLIB_DICTPATH
+#define CRACKLIB_DICTPATH "/usr/share/cracklib/pw_dict"
+#endif
+
+#ifndef CONFIG_FILE
+#define CONFIG_FILE "/etc/openldap/check_password.conf"
+#endif
+
+#define DEFAULT_QUALITY  3
+#define DEFAULT_CRACKLIB 1
+#define MEMORY_MARGIN    50
+#define MEM_INIT_SZ      64
+#define FILENAME_MAXLEN  512
+
+#define PASSWORD_TOO_SHORT_SZ \
+	"Password for dn=3D\"%s\" is too short (%d/6)"
+#define PASSWORD_QUALITY_SZ \
+	"Password for dn=3D\"%s\" does not pass required number of strength che=
cks (%d of %d)"
+#define BAD_PASSWORD_SZ \
+	"Bad password for dn=3D\"%s\" because %s"
+
+typedef int (*validator) (char*);
+static int read_config_file (char *);
+static validator valid_word (char *);
+static int set_quality (char *);
+static int set_cracklib (char *);
+
+int check_password (char *pPasswd, char **ppErrStr, Entry *pEntry);
+
+static int set_quality (char *value)
+{
+#if defined(DEBUG)
+	syslog(LOG_NOTICE, "check_password: Setting quality to [%s]", value);
+#endif
+
+	/* No need to require more quality than we can check for. */
+	if (!isdigit(*value) || (int) (value[0] - '0') > 4) return DEFAULT_QUAL=
ITY;
+	return (int) (value[0] - '0');
+
+}
+
+static int set_cracklib (char *value)
+{
+#if defined(DEBUG)
+	syslog(LOG_NOTICE, "check_password: Setting cracklib usage to [%s]", va=
lue);
+#endif
+
+
+	return (int) (value[0] - '0');
+
+}
+
+static int set_digit (char *value)
+{
+#if defined(DEBUG)
+	syslog(LOG_NOTICE, "check_password: Setting parameter to [%s]", value);=

+#endif
+	if (!isdigit(*value) || (int) (value[0] - '0') > 9) return 0;
+	return (int) (value[0] - '0');
+}
+
+static validator valid_word (char *word)
+{
+	struct {
+		char * parameter;
+		validator dealer;
+	} list[] =3D { { "minPoints", set_quality },
+		{ "useCracklib", set_cracklib },
+		{ "minUpper", set_digit },
+		{ "minLower", set_digit },
+		{ "minDigit", set_digit },
+		{ "minPunct", set_digit },
+		{ NULL, NULL } };
+	int index =3D 0;
+
+#if defined(DEBUG)
+	syslog(LOG_NOTICE, "check_password: Validating parameter [%s]", word);
+#endif
+
+	while (list[index].parameter !=3D NULL) {
+		if (strlen(word) =3D=3D strlen(list[index].parameter) &&
+				strcmp(list[index].parameter, word) =3D=3D 0) {
+#if defined(DEBUG)
+			syslog(LOG_NOTICE, "check_password: Parameter accepted.");
+#endif
+			return list[index].dealer;
+		}
+		index++;
+	}
+
+#if defined(DEBUG)
+	syslog(LOG_NOTICE, "check_password: Parameter rejected.");
+#endif
+
+	return NULL;
+}
+
+static int read_config_file (char *keyWord)
+{
+	FILE * config;
+	char * line;
+	int returnValue =3D  -1;
+
+	if ((line =3D ber_memcalloc(260, sizeof(char))) =3D=3D NULL) {
+		return returnValue;
+	}
+
+	if ( (config =3D fopen(CONFIG_FILE, "r")) =3D=3D NULL) {
+#if defined(DEBUG)
+		syslog(LOG_ERR, "check_password: Opening file %s failed", CONFIG_FILE)=
;
+#endif
+
+		ber_memfree(line);
+		return returnValue;
+	}
+
+	while (fgets(line, 256, config) !=3D NULL) {
+		char *start =3D line;
+		char *word, *value;
+		validator dealer;
+
+#if defined(DEBUG)
+		/* Debug traces to syslog. */
+		syslog(LOG_NOTICE, "check_password: Got line |%s|", line);
+#endif
+
+		while (isspace(*start) && isascii(*start)) start++;
+
+		if (! isascii(*start))
+			continue;
+
+		if ((word =3D strtok(start, " \t")) && (dealer =3D valid_word(word)) &=
& (strcmp(keyWord,word)=3D=3D0)) {
+			if ((value =3D strtok(NULL, " \t")) =3D=3D NULL)
+				continue;
+
+#if defined(DEBUG)
+			syslog(LOG_NOTICE, "check_password: Word =3D %s, value =3D %s", word,=
 value);
+#endif
+
+			returnValue =3D (*dealer)(value);
+		}
+	}
+
+	fclose(config);
+	ber_memfree(line);
+	return returnValue;
+}
+
+static int realloc_error_message (char ** target, int curlen, int nextle=
n)
+{
+	if (curlen < nextlen + MEMORY_MARGIN) {
+#if defined(DEBUG)
+		syslog(LOG_WARNING, "check_password: Reallocating szErrStr from %d to =
%d",
+				curlen, nextlen + MEMORY_MARGIN);
+#endif
+		ber_memfree(*target);
+		curlen =3D nextlen + MEMORY_MARGIN;
+		*target =3D (char *) ber_memalloc(curlen);
+	}
+
+	return curlen;
+}
+
+	int
+check_password (char *pPasswd, char **ppErrStr, Entry *pEntry)
+{
+
+	char *szErrStr =3D (char *) ber_memalloc(MEM_INIT_SZ);
+	int  mem_len =3D MEM_INIT_SZ;
+
+	int nLen;
+	int nLower =3D 0;
+	int nUpper =3D 0;
+	int nDigit =3D 0;
+	int nPunct =3D 0;
+	int minLower =3D 0;
+	int minUpper =3D 0;
+	int minDigit =3D 0;
+	int minPunct =3D 0;
+	int nQuality =3D 0;
+	int i;
+
+	/* Set a sensible default to keep original behaviour. */
+	int minQuality =3D DEFAULT_QUALITY;
+	int useCracklib =3D DEFAULT_CRACKLIB;
+
+	/** bail out early as cracklib will reject passwords shorter
+	 * than 6 characters
+	 */
+
+	nLen =3D strlen (pPasswd);
+	if ( nLen < 6) {
+		mem_len =3D realloc_error_message(&szErrStr, mem_len,
+				strlen(PASSWORD_TOO_SHORT_SZ) +
+				strlen(pEntry->e_name.bv_val) + 1);
+		sprintf (szErrStr, PASSWORD_TOO_SHORT_SZ, pEntry->e_name.bv_val, nLen)=
;
+		goto fail;
+	}
+
+	/* Read config file */
+	minQuality =3D read_config_file("minPoints");
+
+	useCracklib =3D read_config_file("useCracklib");
+	minUpper =3D read_config_file("minUpper");
+	minLower =3D read_config_file("minLower");
+	minDigit =3D read_config_file("minDigit");
+	minPunct =3D read_config_file("minPunct");
+
+	/** The password must have at least minQuality strength points with one=

+	 * point for the first occurrance of a lower, upper, digit and
+	 * punctuation character
+	 */
+
+	for ( i =3D 0; i < nLen; i++ ) {
+
+		if ( nQuality >=3D minQuality ) break;
+
+		if ( islower (pPasswd[i]) ) {
+			minLower--;
+			if ( !nLower && (minLower < 1)) {
+				nLower =3D 1; nQuality++;
+#if defined(DEBUG)
+				syslog(LOG_NOTICE, "check_password: Found lower character - quality =
raise %d", nQuality);
+#endif
+			}
+			continue;
+		}
+
+		if ( isupper (pPasswd[i]) ) {
+			minUpper--;
+			if ( !nUpper && (minUpper < 1)) {
+				nUpper =3D 1; nQuality++;
+#if defined(DEBUG)
+				syslog(LOG_NOTICE, "check_password: Found upper character - quality =
raise %d", nQuality);
+#endif
+			}
+			continue;
+		}
+
+		if ( isdigit (pPasswd[i]) ) {
+			minDigit--;
+			if ( !nDigit && (minDigit < 1)) {
+				nDigit =3D 1; nQuality++;
+#if defined(DEBUG)
+				syslog(LOG_NOTICE, "check_password: Found digit character - quality =
raise %d", nQuality);
+#endif
+			}
+			continue;
+		}
+
+		if ( ispunct (pPasswd[i]) ) {
+			minPunct--;
+			if ( !nPunct && (minPunct < 1)) {
+				nPunct =3D 1; nQuality++;
+#if defined(DEBUG)
+				syslog(LOG_NOTICE, "check_password: Found punctuation character - qu=
ality raise %d", nQuality);
+#endif
+			}
+			continue;
+		}
+	}
+
+	if ( nQuality < minQuality ) {
+		mem_len =3D realloc_error_message(&szErrStr, mem_len,
+				strlen(PASSWORD_QUALITY_SZ) +
+				strlen(pEntry->e_name.bv_val) + 2);
+		sprintf (szErrStr, PASSWORD_QUALITY_SZ, pEntry->e_name.bv_val,
+				nQuality, minQuality);
+		goto fail;
+	}
+
+#ifdef HAVE_CRACKLIB
+
+	/** Check password with cracklib */
+
+	if ( useCracklib > 0 ) {
+		int   j =3D 0;
+		FILE* fp;
+		char  filename[FILENAME_MAXLEN];
+		char  const* ext[] =3D { "hwm", "pwd", "pwi" };
+		int   nErr =3D 0;
+
+		/**
+		 * Silently fail when cracklib wordlist is not found
+		 */
+
+		for ( j =3D 0; j < 3; j++ ) {
+
+			snprintf (filename, FILENAME_MAXLEN - 1, "%s.%s", \
+					CRACKLIB_DICTPATH, ext[j]);
+
+			if (( fp =3D fopen ( filename, "r")) =3D=3D NULL ) {
+
+				nErr =3D 1;
+				break;
+
+			} else {
+
+				fclose (fp);
+
+			}
+		}
+
+		char *r;
+		if ( nErr  =3D=3D 0) {
+
+			r =3D (char *) FascistCheck (pPasswd, CRACKLIB_DICTPATH);
+			if ( r !=3D NULL ) {
+				mem_len =3D realloc_error_message(&szErrStr, mem_len,
+						strlen(BAD_PASSWORD_SZ) +
+						strlen(pEntry->e_name.bv_val) +
+						strlen(r));
+				sprintf (szErrStr, BAD_PASSWORD_SZ, pEntry->e_name.bv_val, r);
+				goto fail;
+			}
+		}
+	}
+
+	else {
+#if defined(DEBUG)
+		syslog(LOG_NOTICE, "check_password: Cracklib verification disabled by =
configuration");
+#endif
+	}
+
+#endif
+
+	*ppErrStr =3D strdup ("");
+	ber_memfree(szErrStr);
+	return (LDAP_SUCCESS);
+
+fail:
+	*ppErrStr =3D strdup (szErrStr);
+	ber_memfree(szErrStr);
+	return (EXIT_FAILURE);
+
+}
+
--=20
1.7.11.4


--------------050908090805020607010105--

--------------ms000000000208070102090606
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: Signature cryptographique S/MIME

MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIN7jCC
BIowggNyoAMCAQICECf06hH0eobEbp27bqkXBwcwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UE
BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9vdDAeFw0w
NTA2MDcwODA5MTBaFw0yMDA1MzAxMDQ4MzhaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
VVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVRO
LVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWlsMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3BYHW8OWX5ShpHornMSMxqmNVN
NRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQy
lbsMTzC9mKALi+VuG6JG+ni8om+rWV6lL8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXq
vgvOdjp6Dpvq/NonWz1zHyLmSGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6
hw2v+vPhwvCkxWeM1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu
9mIwFIws6wIDAQABo4HhMIHeMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0G
A1UdDgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zB7BgNVHR8EdDByMDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRU
cnVzdEV4dGVybmFsQ0FSb290LmNybDA2oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0Fk
ZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAZ2IkRbyispgCi
54fBm5AD236hEv0e8+LwAamUVEJrmgnEoG3XkJIEA2Z5Q3H8+G+v23ZF4jcaPd3kWQR4rBz0
g0bzes9bhHIt5UbBuhgRKfPLSXmHPLptBZ2kbWhPrXIUNqi5sf2/z3/wpGqUNVCPz4FtVbHd
WTBK322gnGQfSXzvNrv042n0+DmPWq1LhTq3Du3Tzw1EovsEv+QvcI4l+1pUBrPQxLxtjftz
Mizpm4QkLdZ/kXpoAlAfDj9N6cz1u2fo3BwuO/xOzf4CjuOoEwqlJkRl6RDyTVKnrtw+ymsy
XEFs/vVdoOr/0fqbhlhtPZZH5f4ulQTCAMyOofK7MIIElTCCA32gAwIBAgIQK7maWQuBMVtp
KDwyDC57STANBgkqhkiG9w0BAQUFADA7MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5B
MRswGQYDVQQDExJURVJFTkEgUGVyc29uYWwgQ0EwHhcNMTEwMTExMDAwMDAwWhcNMTQwMTEw
MjM1OTU5WjBYMQswCQYDVQQGEwJGUjEOMAwGA1UEChMFSU5SSUExGTAXBgNVBAMTEEd1aWxs
YXVtZSBST1VTU0UxHjAcBgkqhkiG9w0BCQIWD3JvdXNzZUBpbnJpYS5mcjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANT2GRniP4zFepDfXQ9qHZCqmyGFsnxP1sAK5gRwh2xm
7EJgNSAlRVkmYdkrohltfjGJWvzmlIc8lgbzPSddeHhlc3SI8ZV3b9gWlyq3asH4oPJ3KuKh
d8s2JLoll4pYRo+EqH/kLr8Tz3xfDEHtXNXiZ4QM6kniyLyk2QKc8UUsxd8f8gbiUW8yP2rB
9MgwbKYHn+EudkGbn+krQGbrBQRK83phYVGNnRy13mlu8Mb+9mmNCJq8cL6vhhIsGB4VRvhb
k3pcZc2zFm9QJFziYLpdqINoh3vmSVHEVN0Oa3KecuXjiaboonCzXnxvdgLWQYAJNglpP7TV
H4PTw+xXLZsCAwEAAaOCAXYwggFyMB8GA1UdIwQYMBaAFGNNQ1oZSD/ERsECur/uDuWCt2am
MB0GA1UdDgQWBBRPxfiKCFknW2HnMSChZnApNelBwjAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0T
AQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDBAYIKwYBBQUHAwIwGAYDVR0gBBEwDzANBgsr
BgEEAbIxAQICHTA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLnRjcy50ZXJlbmEub3Jn
L1RFUkVOQVBlcnNvbmFsQ0EuY3JsMHIGCCsGAQUFBwEBBGYwZDA6BggrBgEFBQcwAoYuaHR0
cDovL2NydC50Y3MudGVyZW5hLm9yZy9URVJFTkFQZXJzb25hbENBLmNydDAmBggrBgEFBQcw
AYYaaHR0cDovL29jc3AudGNzLnRlcmVuYS5vcmcwJAYDVR0RBB0wG4EZR3VpbGxhdW1lLlJv
dXNzZUBpbnJpYS5mcjANBgkqhkiG9w0BAQUFAAOCAQEAQi0EyaS/cO1avHkqFkOVhX5kuG3y
kUlOIrzga2dnR2H7ylRILOTxBLweXL2DKJN+ZPJleLJSmdVcstq0GneSA7D1kJt0aPdMHqI6
8fMcppQer+N1V/9LYJWWRTV6RFrlSduzEU5tmdlG7QbYtvgNtvBliZL06AYPO54FDkHrhfnJ
ldS55eeuAkwFrYkJP1Aa+zo/H2ytzVnIICtZMsZSa7Y2xo4j5Ykxd0HIsc48lmnkDnZ08+yf
AOjJis2tc9eeBdcvQ/Pck+woIDeW9vnahCRcOOqCbmith0cqFvEm8cb1ajPIKmlUGBJi97Qh
XJV8FZcuIK1Naelj/8MDbcd3ZzCCBMMwggOroAMCAQICEHP+V/rfuMUIgXtmuWvwLe8wDQYJ
KoZIhvcNAQEFBQAwga4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs
dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMTYwNAYDVQQDEy1VVE4tVVNFUkZpcnN0LUNsaWVu
dCBBdXRoZW50aWNhdGlvbiBhbmQgRW1haWwwHhcNMDkwNTE4MDAwMDAwWhcNMjgxMjMxMjM1
OTU5WjA7MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRswGQYDVQQDExJURVJFTkEg
UGVyc29uYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIFdn1M2ojoZAN
z7sFRMOrH0o1hRohhaBP+PBA4kpDm/5bsbC/tFfcdYBBS2Qa9ttPb4/QJUU1+erLSvr72tPt
RYgRlDbkzKgN78U9N+0We+PClZ5YM38i+/j/7Oa+264KZSUih9pvhItG6ECGKD+/VgjiSumD
ouki+y36tigfkcHDcftTwCtOpAyhbp1V7ezhJIc6COINHOTETdDLJ/qEZObRl51WJFuTuyku
Q+JBaj3iSmX8ml9ahoe8h8d5gJaZUcaQD2SRmX0Q3awsAyrheGT+zj1O9CtQEUvRWNSbA/B/
9TtTsFND+8UvxAQpGjqs11Xp0Q6V0Tsxf3hPriktAgMBAAGjggFNMIIBSTAfBgNVHSMEGDAW
gBSJgmd9xJ0mcABLtFBIfN49rgRufTAdBgNVHQ4EFgQUY01DWhlIP8RGwQK6v+4O5YK3ZqYw
DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwGAYDVR0gBBEwDzANBgsrBgEE
AbIxAQICHTBYBgNVHR8EUTBPME2gS6BJhkdodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVRO
LVVTRVJGaXJzdC1DbGllbnRBdXRoZW50aWNhdGlvbmFuZEVtYWlsLmNybDBvBggrBgEFBQcB
AQRjMGEwOAYIKwYBBQUHMAKGLGh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9VVE5BQUFDbGll
bnRfQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqG
SIb3DQEBBQUAA4IBAQAGK6lTLxPcXDkWzIafXkx7cvvsjVWKXpoK/1NMdvQGPVDPV/Ciz6+Z
jKr+oBl2PpkDMvp1gziKu2uapQwTstQbduaULmeYWeORbAKQmpzIYEtVq8qIWo0r5WmVAwfR
1A78JCIuWbFjpF/t2SNy5JzOOlxsH0+pAMkd/vp/RS22LoTdDyegWRhO1XYlRfSZJnnbb58j
90O7Kw8Eo4EmLLd7Nfk9d19AIeZ/HaWWWr3QyxY6bLthi4r9BDlECsss4cvOLhCYGtvgk+1J
ZGQIIJ+3o1Dwot3KtMZ8DD3nXhXcJ4bkOjtSWherqQZTK50Jc2QcAcP9MNKHA2/kFQN6OV9o
MYIDBzCCAwMCAQEwTzA7MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRswGQYDVQQD
ExJURVJFTkEgUGVyc29uYWwgQ0ECECu5mlkLgTFbaSg8Mgwue0kwCQYFKw4DAhoFAKCCAY0w
GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTIwODA3MTU1NzI1
WjAjBgkqhkiG9w0BCQQxFgQUQ1jLCcKguMOiIFP7wDC8SLbfFfYwXgYJKwYBBAGCNxAEMVEw
TzA7MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRswGQYDVQQDExJURVJFTkEgUGVy
c29uYWwgQ0ECECu5mlkLgTFbaSg8Mgwue0kwYAYLKoZIhvcNAQkQAgsxUaBPMDsxCzAJBgNV
BAYTAk5MMQ8wDQYDVQQKEwZURVJFTkExGzAZBgNVBAMTElRFUkVOQSBQZXJzb25hbCBDQQIQ
K7maWQuBMVtpKDwyDC57STBsBgkqhkiG9w0BCQ8xXzBdMAsGCWCGSAFlAwQBKjALBglghkgB
ZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsO
AwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIBANISMWbCVg4clpyyoU7WyOoP
ENBalFbEzAbA7iBn6edoAEV8jgj41VcGlNsevz8kQrb59GzkjOqMXsIwHTsGvjky738QcFPu
uXyb8ZY+ExFOhgscUVlQ924+idn6xPMstKuM/cPKrknqdrRsS6igOTMVhKjI0s0MX2vsSA8/
FLd48o7B8XE5vWCXpJKExRgZvJdbzxrY3fSFugo3/jrCPz62mquY9u4U55nspkADfd6Ws86J
tzVhlZzAM+CGmBvXETbPOJpP6HV2ORAXhxSX/x2Rn07Rm8kShMVUSeSpg2cH5MiyAXNFwfix
kZDykKSrudGq7u+YrAC/Q6isV4ohEZMAAAAAAAA=
--------------ms000000000208070102090606--