You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by co...@apache.org on 2010/06/01 23:19:07 UTC
svn commit: r950248 - in /httpd/httpd/trunk: CHANGES
docs/manual/mod/mod_authnz_ldap.html.en docs/manual/mod/mod_authnz_ldap.xml
modules/aaa/mod_authnz_ldap.c
Author: covener
Date: Tue Jun 1 21:19:06 2010
New Revision: 950248
URL: http://svn.apache.org/viewvc?rev=950248&view=rev
Log:
mod_authnz_ldap: Search or Comparison during authorization phase
can use the credentials from the authentication phase
(AuthLDAPSearchAsUSer,AuthLDAPCompareAsUser).
PR 48340
Submitted by: Domenico Rotiroti, Eric Covener
Reviewed by: Eric Covener
Modified:
httpd/httpd/trunk/CHANGES
httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.html.en
httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.xml
httpd/httpd/trunk/modules/aaa/mod_authnz_ldap.c
Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=950248&r1=950247&r2=950248&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Tue Jun 1 21:19:06 2010
@@ -28,6 +28,11 @@ Changes with Apache 2.3.7
processing is completed, avoiding orphaned callback pointers.
[Brett Gervasoni <brettg senseofsecurity.com>, Jeff Trawick]
+ *) mod_authnz_ldap: Search or Comparison during authorization phase
+ can use the credentials from the authentication phase
+ (AuthLDAPSearchAsUSer,AuthLDAPCompareAsUser).
+ PR 48340 [Domenico Rotiroti, Eric Covener]
+
*) mod_authnz_ldap: Allow the initial DN search during authentication
to use the HTTP username/pass instead of an anonymous or hard-coded
LDAP id (AuthLDAPInitialBindAsUser, AuthLDAPInitialBindPattern).
Modified: httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.html.en
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.html.en?rev=950248&r1=950247&r2=950248&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.html.en (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.html.en Tue Jun 1 21:19:06 2010
@@ -64,6 +64,7 @@ for HTTP Basic authentication.</td></tr>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapbinddn">AuthLDAPBindDN</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapbindpassword">AuthLDAPBindPassword</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapcharsetconfig">AuthLDAPCharsetConfig</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#authldapcompareasuser">AuthLDAPCompareAsUser</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapcomparednonserver">AuthLDAPCompareDNOnServer</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapdereferencealiases">AuthLDAPDereferenceAliases</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapgroupattribute">AuthLDAPGroupAttribute</a></li>
@@ -73,6 +74,7 @@ for HTTP Basic authentication.</td></tr>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapmaxsubgroupdepth">AuthLDAPMaxSubGroupDepth</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapremoteuserattribute">AuthLDAPRemoteUserAttribute</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapremoteuserisdn">AuthLDAPRemoteUserIsDN</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#authldapsearchasuser">AuthLDAPSearchAsUser</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapsubgroupattribute">AuthLDAPSubGroupAttribute</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapsubgroupclass">AuthLDAPSubGroupClass</a></li>
<li><img alt="" src="../images/down.gif" /> <a href="#authldapurl">AuthLDAPUrl</a></li>
@@ -872,6 +874,40 @@ authorization</td></tr>
</div>
<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="AuthLDAPCompareAsUser" id="AuthLDAPCompareAsUser">AuthLDAPCompareAsUser</a> <a name="authldapcompareasuser" id="authldapcompareasuser">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Use the authenticated users credentials to perform authorization comparisons</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>AuthLDAPCompareAsUser on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>AuthLDAPCompareAsUser off</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Override">Override:</a></th><td>AuthConfig</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_authnz_ldap</td></tr>
+<tr><th><a href="directive-dict.html#Compatibility">Compatibility:</a></th><td>Available in version 2.3.7 and later</td></tr>
+</table>
+ <p>When set, and <code class="module"><a href="../mod/mod_authnz_ldap.html">mod_authnz_ldap</a></code> has authenticated the
+ user, comparisons for authorization use the credentials provided via
+ HTTP basic authentication instead of the servers own credentials.</p>
+
+ <p> The <em>ldap-attribute</em>, <em>ldap-user</em>, and <em>ldap-group</em> (single-level only)
+ authorization checks use comparisons.</p>
+
+ <p>This directive only has effect on the comparisons performed during
+ nested group processing when <code class="directive"><a href="# authldapsearchasuser">
+ AuthLDAPSearchAsUser</a></code> is also enabled.</p>
+
+ <p> This directive should only be used when your LDAP server doesn't
+ accept anonymous comparisons and you cannot use a dedicated
+ <code class="directive"><a href="#authldapbinddn">AuthLDAPBindDN</a></code>.
+ </p>
+
+<h3>See also</h3>
+<ul>
+<li><code class="directive"><a href="#authldapinitialbindasuser">AuthLDAPInitialBindAsUser</a></code></li>
+<li><code class="directive"><a href="#authldapsearchasuser">AuthLDAPSearchAsUser</a></code></li>
+</ul>
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="directive-section"><h2><a name="AuthLDAPCompareDNOnServer" id="AuthLDAPCompareDNOnServer">AuthLDAPCompareDNOnServer</a> <a name="authldapcomparednonserver" id="authldapcomparednonserver">Directive</a></h2>
<table class="directive">
<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Use the LDAP server to compare the DNs</td></tr>
@@ -990,6 +1026,8 @@ own username, instead of anonymously or
<ul>
<li><code class="directive"><a href="../mod/mod_authnnz_ldap.html#authldapinitialbindpattern">AuthLDAPInitialBindPattern</a></code></li>
<li><code class="directive"><a href="../mod/mod_authnnz_ldap.html#authldapbinddn">AuthLDAPBindDN</a></code></li>
+<li><code class="directive"><a href="#authldapcompareasuser">AuthLDAPCompareAsUser</a></code></li>
+<li><code class="directive"><a href="#authldapsearchasuser">AuthLDAPSearchAsUser</a></code></li>
</ul>
</div>
<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
@@ -1101,6 +1139,40 @@ environment variable</td></tr>
</div>
<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="AuthLDAPSearchAsUser" id="AuthLDAPSearchAsUser">AuthLDAPSearchAsUser</a> <a name="authldapsearchasuser" id="authldapsearchasuser">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Use the authenticated users credentials to perform authorization searches</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>AuthLDAPSearchAsUser on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>AuthLDAPSearchAsUser off</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Override">Override:</a></th><td>AuthConfig</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_authnz_ldap</td></tr>
+<tr><th><a href="directive-dict.html#Compatibility">Compatibility:</a></th><td>Available in version 2.3.7 and later</td></tr>
+</table>
+ <p>When set, and <code class="module"><a href="../mod/mod_authnz_ldap.html">mod_authnz_ldap</a></code> has authenticated the
+ user, searches for authorization use the credentials provided via
+ HTTP basic authentication instead of the servers own credentials.</p>
+
+ <p> The <em>ldap-filter</em> and <em>ldap-dn</em> authorization
+ checks use searches.</p>
+
+ <p>This directive only has effect on the comparisons performed during
+ nested group processing when <code class="directive"><a href="# authldapcompareasuser">
+ AuthLDAPCompareAsUser</a></code> is also enabled.</p>
+
+ <p> This directive should only be used when your LDAP server doesn't
+ accept anonymous searches and you cannot use a dedicated
+ <code class="directive"><a href="#authldapbinddn">AuthLDAPBindDN</a></code>.
+ </p>
+
+<h3>See also</h3>
+<ul>
+<li><code class="directive"><a href="#authldapinitialbindasuser">AuthLDAPInitialBindAsUser</a></code></li>
+<li><code class="directive"><a href="#authldapcompareasuser">AuthLDAPCompareAsUser</a></code></li>
+</ul>
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="directive-section"><h2><a name="AuthLDAPSubGroupAttribute" id="AuthLDAPSubGroupAttribute">AuthLDAPSubGroupAttribute</a> <a name="authldapsubgroupattribute" id="authldapsubgroupattribute">Directive</a></h2>
<table class="directive">
<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Specifies the attribute labels, one value per
Modified: httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.xml?rev=950248&r1=950247&r2=950248&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.xml (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_authnz_ldap.xml Tue Jun 1 21:19:06 2010
@@ -837,6 +837,8 @@ own username, instead of anonymously or
</usage>
<seealso><directive module="mod_authnnz_ldap">AuthLDAPInitialBindPattern</directive></seealso>
<seealso><directive module="mod_authnnz_ldap">AuthLDAPBindDN</directive></seealso>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPCompareAsUser</directive></seealso>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPSearchAsUser</directive></seealso>
</directivesynopsis>
<directivesynopsis>
@@ -939,6 +941,38 @@ to perform a DN lookup</description>
</directivesynopsis>
<directivesynopsis>
+<name>AuthLDAPCompareAsUser</name>
+<description>Use the authenticated users credentials to perform authorization comparisons</description>
+<syntax>AuthLDAPCompareAsUser on|off</syntax>
+<default>AuthLDAPCompareAsUser off</default>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<compatibility>Available in version 2.3.7 and later</compatibility>
+<override>AuthConfig</override>
+
+<usage>
+ <p>When set, and <module>mod_authnz_ldap</module> has authenticated the
+ user, LDAP comparisons for authorization use the queried distinguished name (DN)
+ and HTTP basic authentication password of the authenticated user instead of
+ the servers configured credentials.</p>
+
+ <p> The <em>ldap-attribute</em>, <em>ldap-user</em>, and <em>ldap-group</em> (single-level only)
+ authorization checks use comparisons.</p>
+
+ <p>This directive only has effect on the comparisons performed during
+ nested group processing when <directive module="mod_authnz_ldap">
+ AuthLDAPSearchAsUser</directive> is also enabled.</p>
+
+ <p> This directive should only be used when your LDAP server doesn't
+ accept anonymous comparisons and you cannot use a dedicated
+ <directive module="mod_authnz_ldap">AuthLDAPBindDN</directive>.
+ </p>
+</usage>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPInitialBindAsUser</directive></seealso>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPSearchAsUser</directive></seealso>
+</directivesynopsis>
+
+<directivesynopsis>
<name>AuthLDAPCompareDNOnServer</name>
<description>Use the LDAP server to compare the DNs</description>
<syntax>AuthLDAPCompareDNOnServer on|off</syntax>
@@ -1086,6 +1120,38 @@ environment variable</description>
</directivesynopsis>
<directivesynopsis>
+<name>AuthLDAPSearchAsUser</name>
+<description>Use the authenticated users credentials to perform authorization searches</description>
+<syntax>AuthLDAPSearchAsUser on|off</syntax>
+<default>AuthLDAPSearchAsUser off</default>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<compatibility>Available in version 2.3.7 and later</compatibility>
+<override>AuthConfig</override>
+
+<usage>
+ <p>When set, and <module>mod_authnz_ldap</module> has authenticated the
+ user, LDAP searches for authorization use the queried distinguished name (DN)
+ and HTTP basic authentication password of the authenticated user instead of
+ the servers configured credentials.</p>
+
+ <p> The <em>ldap-filter</em> and <em>ldap-dn</em> authorization
+ checks use searches.</p>
+
+ <p>This directive only has effect on the comparisons performed during
+ nested group processing when <directive module="mod_authnz_ldap">
+ AuthLDAPCompareAsUser</directive> is also enabled.</p>
+
+ <p> This directive should only be used when your LDAP server doesn't
+ accept anonymous searches and you cannot use a dedicated
+ <directive module="mod_authnz_ldap">AuthLDAPBindDN</directive>.
+ </p>
+</usage>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPInitialBindAsUser</directive></seealso>
+<seealso><directive module="mod_authnz_ldap">AuthLDAPCompareAsUser</directive></seealso>
+</directivesynopsis>
+
+<directivesynopsis>
<name>AuthLDAPSubGroupAttribute</name>
<description>Specifies the attribute labels, one value per
directive line, used to distinguish the members of the current group that
Modified: httpd/httpd/trunk/modules/aaa/mod_authnz_ldap.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_authnz_ldap.c?rev=950248&r1=950247&r2=950248&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/aaa/mod_authnz_ldap.c (original)
+++ httpd/httpd/trunk/modules/aaa/mod_authnz_ldap.c Tue Jun 1 21:19:06 2010
@@ -83,18 +83,25 @@ typedef struct {
int initial_bind_as_user; /* true if we should try to bind (to lookup DN) directly with the basic auth username */
ap_regex_t *bind_regex; /* basic auth -> bind'able username regex */
const char *bind_subst; /* basic auth -> bind'able username substitution */
+ int search_as_user; /* true if authz searches should be done with the users credentials (when we did authn) */
+ int compare_as_user; /* true if authz compares should be done with the users credentials (when we did authn) */
} authn_ldap_config_t;
typedef struct {
char *dn; /* The saved dn from a successful search */
char *user; /* The username provided by the client */
const char **vals; /* The additional values pulled during the DN search*/
+ char *password; /* if this module successfully authenticates, the basic auth password, else null */
} authn_ldap_request_t;
-enum auth_ldap_phase{
+enum auth_ldap_phase {
LDAP_AUTHN, LDAP_AUTHZ
};
-
+
+enum auth_ldap_optype {
+ LDAP_SEARCH, LDAP_COMPARE, LDAP_COMPARE_AND_SEARCH /* nested groups */
+};
+
/* maximum group elements supported */
#define GROUPATTR_MAX_ELTS 10
@@ -412,6 +419,32 @@ static const char *ldap_determine_binddn
return result;
}
+
+/* Some LDAP servers restrict who can search or compare, and the hard-coded ID
+ * might be good for the DN lookup but not for later operations.
+ */
+static util_ldap_connection_t *get_connection_for_authz(request_rec *r, enum auth_ldap_optype type) {
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ char *binddn = sec->binddn;
+ char *bindpw = sec->bindpw;
+
+ /* If the per-request config isn't set, we didn't authenticate this user, and leave the default credentials */
+ if (req && req->password &&
+ ((type == LDAP_SEARCH && sec->search_as_user) ||
+ (type == LDAP_COMPARE && sec->compare_as_user) ||
+ (type == LDAP_COMPARE_AND_SEARCH && sec->compare_as_user && sec->search_as_user))){
+ binddn = req->dn;
+ bindpw = req->password;
+ }
+
+ return util_ldap_connection_find(r, sec->host, sec->port,
+ binddn, bindpw,
+ sec->deref, sec->secure);
+}
/*
* Authentication Phase
* --------------------
@@ -545,6 +578,7 @@ start_over:
/* mark the user and DN */
req->dn = apr_pstrdup(r->pool, dn);
req->user = apr_pstrdup(r->pool, user);
+ req->password = apr_pstrdup(r->pool, password);
if (sec->user_is_dn) {
r->user = req->dn;
}
@@ -591,9 +625,7 @@ static authz_status ldapuser_check_autho
}
if (sec->host) {
- ldc = util_ldap_connection_find(r, sec->host, sec->port,
- sec->binddn, sec->bindpw, sec->deref,
- sec->secure);
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
apr_pool_cleanup_register(r->pool, ldc,
authnz_ldap_cleanup_connection_close,
apr_pool_cleanup_null);
@@ -732,9 +764,7 @@ static authz_status ldapgroup_check_auth
}
if (sec->host) {
- ldc = util_ldap_connection_find(r, sec->host, sec->port,
- sec->binddn, sec->bindpw, sec->deref,
- sec->secure);
+ ldc = get_connection_for_authz(r, LDAP_COMPARE); /* for the top-level group only */
apr_pool_cleanup_register(r->pool, ldc,
authnz_ldap_cleanup_connection_close,
apr_pool_cleanup_null);
@@ -869,6 +899,15 @@ static authz_status ldapgroup_check_auth
return AUTHZ_GRANTED;
}
case LDAP_COMPARE_FALSE: {
+ /* nested groups need searches and compares, so grab a new handle */
+ authnz_ldap_cleanup_connection_close(ldc);
+ apr_pool_cleanup_kill(r->pool, ldc,authnz_ldap_cleanup_connection_close);
+
+ ldc = get_connection_for_authz(r, LDAP_COMPARE_AND_SEARCH);
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"[%" APR_PID_T_FMT "] auth_ldap authorise: require group \"%s\": "
"failed [%s][%d - %s], checking sub-groups",
@@ -932,9 +971,7 @@ static authz_status ldapdn_check_authori
}
if (sec->host) {
- ldc = util_ldap_connection_find(r, sec->host, sec->port,
- sec->binddn, sec->bindpw, sec->deref,
- sec->secure);
+ ldc = get_connection_for_authz(r, LDAP_SEARCH); /* _comparedn is a searche */
apr_pool_cleanup_register(r->pool, ldc,
authnz_ldap_cleanup_connection_close,
apr_pool_cleanup_null);
@@ -1046,9 +1083,7 @@ static authz_status ldapattribute_check_
}
if (sec->host) {
- ldc = util_ldap_connection_find(r, sec->host, sec->port,
- sec->binddn, sec->bindpw, sec->deref,
- sec->secure);
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
apr_pool_cleanup_register(r->pool, ldc,
authnz_ldap_cleanup_connection_close,
apr_pool_cleanup_null);
@@ -1165,9 +1200,7 @@ static authz_status ldapfilter_check_aut
}
if (sec->host) {
- ldc = util_ldap_connection_find(r, sec->host, sec->port,
- sec->binddn, sec->bindpw, sec->deref,
- sec->secure);
+ ldc = get_connection_for_authz(r, LDAP_SEARCH);
apr_pool_cleanup_register(r->pool, ldc,
authnz_ldap_cleanup_connection_close,
apr_pool_cleanup_null);
@@ -1249,8 +1282,14 @@ static authz_status ldapfilter_check_aut
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"[%" APR_PID_T_FMT "] auth_ldap authorize: checking dn match %s",
getpid(), dn);
+ if (sec->compare_as_user) {
+ /* ldap-filter is the only authz that requires a search and a compare */
+ apr_pool_cleanup_kill(r->pool, ldc, authnz_ldap_cleanup_connection_close);
+ authnz_ldap_cleanup_connection_close(ldc);
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
+ }
result = util_ldap_cache_comparedn(r, ldc, sec->url, req->dn, dn,
- sec->compare_dn_on_server);
+ sec->compare_dn_on_server);
}
switch(result) {
@@ -1607,6 +1646,14 @@ static const command_rec authnz_ldap_cmd
AP_INIT_TAKE2("AuthLDAPInitialBindPattern", set_bind_pattern, NULL, OR_AUTHCFG,
"The regex and substitution to determine a username that can bind based on an HTTP basic auth username"),
+ AP_INIT_FLAG("AuthLDAPSearchAsUser", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, search_as_user), OR_AUTHCFG,
+ "Set to 'on' to perform authorization-based searches with the users credentials, when this module"
+ " has also performed authentication. Does not affect nested groups lookup."),
+ AP_INIT_FLAG("AuthLDAPCompareAsUser", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, compare_as_user), OR_AUTHCFG,
+ "Set to 'on' to perform authorization-based compares with the users credentials, when this module"
+ " has also performed authentication. Does not affect nested groups lookups."),
{NULL}
};