On Fri, May 11, 2018 at 10:42:32PM +0200, Reyk Floeter wrote:
> Hi!
> 
> (resent to tech@)
> the following ldapd patch allows filter rules to match on attributes.
> 
> This can be used to allow users to change their password (and a few
> other things) but not their entire dn.
> 
> For example, in ldapd.conf:
> 
>       allow read access to any by self
>       allow write access to any attribute shadowPassword by self
>       allow write access to any attribute sshPublicKey by self
>       allow write access to any attribute gecos by self
>       allow write access to any attribute loginShell by self
> 
> Alternatively, this would also work:
> 
>       allow write access to any by self
>       deny write access to any attribute mail by self
> 
> This only supports "write" (modify, add, delete) and not "read"
> (search) filter rules.  The search mode will be more complicated and I
> will look at this later.
> 
> Thoughts?  OK?

ok.  Read filters would be good to have, but this is a nice addition as it is.

> 
> Reyk
> 
> Index: usr.sbin/ldapd/auth.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/auth.c,v
> retrieving revision 1.12
> diff -u -p -u -p -r1.12 auth.c
> --- usr.sbin/ldapd/auth.c     20 Jan 2017 11:55:08 -0000      1.12
> +++ usr.sbin/ldapd/auth.c     11 May 2018 14:09:01 -0000
> @@ -33,7 +33,7 @@
>  
>  static int
>  aci_matches(struct aci *aci, struct conn *conn, struct namespace *ns,
> -    char *dn, int rights, enum scope scope)
> +    char *dn, int rights, char *attr, enum scope scope)
>  {
>       struct btval     key;
>  
> @@ -98,6 +98,13 @@ aci_matches(struct aci *aci, struct conn
>                       return 0;
>       }
>  
> +     if (aci->attribute != NULL) {
> +             if (attr == NULL)
> +                     return 0;
> +             if (strcasecmp(aci->attribute, attr) != 0)
> +                     return 0;
> +     }
> +
>       return 1;
>  }
>  
> @@ -105,7 +112,7 @@ aci_matches(struct aci *aci, struct conn
>   */
>  int
>  authorized(struct conn *conn, struct namespace *ns, int rights, char *dn,
> -    int scope)
> +    char *attr, int scope)
>  {
>       struct aci      *aci;
>       int              type = ACI_ALLOW;
> @@ -124,33 +131,41 @@ authorized(struct conn *conn, struct nam
>       if ((rights & (ACI_WRITE | ACI_CREATE)) != 0)
>               type = ACI_DENY;
>  
> -     log_debug("requesting %02X access to %s by %s, in namespace %s",
> +     log_debug("requesting %02X access to %s%s%s by %s, in namespace %s",
>           rights,
>           dn ? dn : "any",
> +         attr ? " attribute " : "",
> +         attr ? attr : "",
>           conn->binddn ? conn->binddn : "any",
>           ns ? ns->suffix : "global");
>  
>       SIMPLEQ_FOREACH(aci, &conf->acl, entry) {
> -             if (aci_matches(aci, conn, ns, dn, rights, scope)) {
> +             if (aci_matches(aci, conn, ns, dn, rights,
> +                 attr, scope)) {
>                       type = aci->type;
> -                     log_debug("%s by: %s %02X access to %s by %s",
> +                     log_debug("%s by: %s %02X access to %s%s%s by %s",
>                           type == ACI_ALLOW ? "allowed" : "denied",
>                           aci->type == ACI_ALLOW ? "allow" : "deny",
>                           aci->rights,
>                           aci->target ? aci->target : "any",
> +                         aci->attribute ? " attribute " : "",
> +                         aci->attribute ? aci->attribute : "",
>                           aci->subject ? aci->subject : "any");
>               }
>       }
>  
>       if (ns != NULL) {
>               SIMPLEQ_FOREACH(aci, &ns->acl, entry) {
> -                     if (aci_matches(aci, conn, ns, dn, rights, scope)) {
> +                     if (aci_matches(aci, conn, ns, dn, rights,
> +                         attr, scope)) {
>                               type = aci->type;
> -                             log_debug("%s by: %s %02X access to %s by %s",
> +                             log_debug("%s by: %s %02X access to %s%s%s by 
> %s",
>                                   type == ACI_ALLOW ? "allowed" : "denied",
>                                   aci->type == ACI_ALLOW ? "allow" : "deny",
>                                   aci->rights,
>                                   aci->target ? aci->target : "any",
> +                                 aci->attribute ? " attribute " : "",
> +                                 aci->attribute ? aci->attribute : "",
>                                   aci->subject ? aci->subject : "any");
>                       }
>               }
> @@ -319,7 +334,7 @@ ldap_auth_simple(struct request *req, ch
>               return LDAP_INVALID_CREDENTIALS;
>       } else {
>               if (!authorized(req->conn, ns, ACI_BIND, binddn,
> -                 LDAP_SCOPE_BASE))
> +                 NULL, LDAP_SCOPE_BASE))
>                       return LDAP_INSUFFICIENT_ACCESS;
>  
>               elm = namespace_get(ns, binddn);
> Index: usr.sbin/ldapd/ldapd.conf.5
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/ldapd.conf.5,v
> retrieving revision 1.22
> diff -u -p -u -p -r1.22 ldapd.conf.5
> --- usr.sbin/ldapd/ldapd.conf.5       17 Oct 2016 14:03:17 -0000      1.22
> +++ usr.sbin/ldapd/ldapd.conf.5       11 May 2018 14:09:01 -0000
> @@ -248,6 +248,13 @@ This is the default if no scope is speci
>  The filter rule applies to the root DSE.
>  .El
>  .Pp
> +The scope scope can be restricted to an optional attribute:
> +.Bl -tag -width Ds
> +.It attribute Ar name
> +The filter rule applies to the specified attribute.
> +Attributes can only be specified for write access rules.
> +.El
> +.Pp
>  Finally, the filter rule can match a bind DN:
>  .Bl -tag -width Ds
>  .It by any
> Index: usr.sbin/ldapd/ldapd.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/ldapd.h,v
> retrieving revision 1.28
> diff -u -p -u -p -r1.28 ldapd.h
> --- usr.sbin/ldapd/ldapd.h    24 Feb 2017 14:28:31 -0000      1.28
> +++ usr.sbin/ldapd/ldapd.h    11 May 2018 14:09:01 -0000
> @@ -461,7 +461,7 @@ extern struct imsgev      *iev_ldapd;
>  int                   ldap_bind(struct request *req);
>  void                  ldap_bind_continue(struct conn *conn, int ok);
>  int                   authorized(struct conn *conn, struct namespace *ns,
> -                             int rights, char *dn, int scope);
> +                             int rights, char *dn, char *attr, int scope);
>  
>  /* parse.y */
>  int                   parse_config(char *filename);
> Index: usr.sbin/ldapd/modify.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/modify.c,v
> retrieving revision 1.20
> diff -u -p -u -p -r1.20 modify.c
> --- usr.sbin/ldapd/modify.c   28 Jul 2017 12:58:52 -0000      1.20
> +++ usr.sbin/ldapd/modify.c   11 May 2018 14:09:01 -0000
> @@ -32,10 +32,11 @@ int
>  ldap_delete(struct request *req)
>  {
>       struct btval             key;
> -     char                    *dn;
> +     char                    *dn, *s;
>       struct namespace        *ns;
>       struct referrals        *refs;
>       struct cursor           *cursor;
> +     struct ber_element      *entry, *elm, *a;
>       int                      rc = LDAP_OTHER;
>  
>       ++stats.req_mod;
> @@ -54,7 +55,7 @@ ldap_delete(struct request *req)
>                       return ldap_refer(req, dn, NULL, refs);
>       }
>  
> -     if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE))
> +     if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
>               return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
>  
>       if (namespace_begin(ns) != 0) {
> @@ -91,6 +92,24 @@ ldap_delete(struct request *req)
>               goto done;
>       }
>  
> +     if ((entry = namespace_get(ns, dn)) == NULL) {
> +             rc = LDAP_NO_SUCH_OBJECT;
> +             goto done;
> +     }
> +
> +     /* Fail if this leaf node includes non-writeable attributes */
> +     if (entry->be_encoding != BER_TYPE_SEQUENCE)
> +             goto done;
> +     for (elm = entry->be_sub; elm != NULL; elm = elm->be_next) {
> +             a = elm->be_sub;
> +             if (a && ber_get_string(a, &s) == 0 &&
> +                 !authorized(req->conn, ns, ACI_WRITE, dn, s,
> +                 LDAP_SCOPE_BASE)) {
> +                     rc = LDAP_INSUFFICIENT_ACCESS;
> +                     goto done;
> +             }
> +     }
> +
>       if (namespace_del(ns, dn) == 0 && namespace_commit(ns) == 0)
>               rc = LDAP_SUCCESS;
>  
> @@ -132,7 +151,7 @@ ldap_add(struct request *req)
>                       return ldap_refer(req, dn, NULL, refs);
>       }
>  
> -     if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE))
> +     if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
>               return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
>  
>       /* Check that we're not adding immutable attributes.
> @@ -141,6 +160,9 @@ ldap_add(struct request *req)
>               attr = elm->be_sub;
>               if (attr == NULL || ber_get_string(attr, &s) != 0)
>                       return ldap_respond(req, LDAP_PROTOCOL_ERROR);
> +             if (!authorized(req->conn, ns, ACI_WRITE, dn, s,
> +                 LDAP_SCOPE_BASE))
> +                     return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
>               if (!ns->relax) {
>                       at = lookup_attribute(conf->schema, s);
>                       if (at == NULL) {
> @@ -242,8 +264,14 @@ ldap_modify(struct request *req)
>                       return ldap_refer(req, dn, NULL, refs);
>       }
>  
> -     if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE))
> -             return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
> +     /* Check authorization for each mod to consider attributes */
> +     for (mod = mods->be_sub; mod; mod = mod->be_next) {
> +             if (ber_scanf_elements(mod, "{E{es", &op, &prev, &attr) != 0)
> +                     return ldap_respond(req, LDAP_PROTOCOL_ERROR);
> +             if (!authorized(req->conn, ns, ACI_WRITE, dn, attr,
> +                 LDAP_SCOPE_BASE))
> +                     return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
> +     }
>  
>       if (namespace_begin(ns) == -1) {
>               if (errno == EBUSY) {
> Index: usr.sbin/ldapd/parse.y
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/parse.y,v
> retrieving revision 1.26
> diff -u -p -u -p -r1.26 parse.y
> --- usr.sbin/ldapd/parse.y    26 Apr 2018 14:12:19 -0000      1.26
> +++ usr.sbin/ldapd/parse.y    11 May 2018 14:09:02 -0000
> @@ -96,7 +96,7 @@ struct ldapd_config *conf;
>  SPLAY_GENERATE(ssltree, ssl, ssl_nodes, ssl_cmp);
>  
>  static struct aci    *mk_aci(int type, int rights, enum scope scope,
> -                             char *target, char *subject);
> +                             char *target, char *subject, char *attr);
>  
>  typedef struct {
>       union {
> @@ -120,7 +120,7 @@ static struct namespace *current_ns = NU
>  %token  <v.number>   NUMBER
>  %type        <v.number>      port ssl boolean comp_level
>  %type        <v.number>      aci_type aci_access aci_rights aci_right 
> aci_scope
> -%type        <v.string>      aci_target aci_subject certname
> +%type        <v.string>      aci_target aci_attr aci_subject certname
>  %type        <v.aci>         aci
>  
>  %%
> @@ -294,8 +294,8 @@ comp_level        : /* empty */                   { $$ = 
> 6; }
>               | LEVEL NUMBER                  { $$ = $2; }
>               ;
>  
> -aci          : aci_type aci_access TO aci_scope aci_target aci_subject {
> -                     if (($$ = mk_aci($1, $2, $4, $5, $6)) == NULL) {
> +aci          : aci_type aci_access TO aci_scope aci_target aci_attr 
> aci_subject {
> +                     if (($$ = mk_aci($1, $2, $4, $5, $6, $7)) == NULL) {
>                               free($5);
>                               free($6);
>                               YYERROR;
> @@ -303,7 +303,7 @@ aci               : aci_type aci_access TO aci_scope 
>               }
>               | aci_type aci_access {
>                       if (($$ = mk_aci($1, $2, LDAP_SCOPE_SUBTREE, NULL,
> -                         NULL)) == NULL) {
> +                         NULL, NULL)) == NULL) {
>                               YYERROR;
>                       }
>               }
> @@ -338,6 +338,10 @@ aci_target       : ANY                           { $$ = 
> NULL; }
>               | STRING                        { $$ = $1; normalize_dn($$); }
>               ;
>  
> +aci_attr     : /* empty */                   { $$ = NULL; }
> +             | ATTRIBUTE STRING              { $$ = $2; }
> +             ;
> +
>  aci_subject  : /* empty */                   { $$ = NULL; }
>               | BY ANY                        { $$ = NULL; }
>               | BY STRING                     { $$ = $2; normalize_dn($$); }
> @@ -425,6 +429,7 @@ lookup(char *s)
>               { "access",             ACCESS },
>               { "allow",              ALLOW },
>               { "any",                ANY },
> +             { "attribute",          ATTRIBUTE },
>               { "bind",               BIND },
>               { "by",                 BY },
>               { "cache-size",         CACHE_SIZE },
> @@ -1134,7 +1139,8 @@ interface(const char *s, const char *cer
>  }
>  
>  static struct aci *
> -mk_aci(int type, int rights, enum scope scope, char *target, char *subject)
> +mk_aci(int type, int rights, enum scope scope, char *target, char *attr,
> +    char *subject)
>  {
>       struct aci      *aci;
>  
> @@ -1146,14 +1152,23 @@ mk_aci(int type, int rights, enum scope 
>       aci->rights = rights;
>       aci->scope = scope;
>       aci->target = target;
> +     aci->attribute = attr;
>       aci->subject = subject;
>  
> -     log_debug("%s %02X access to %s scope %d by %s",
> +     log_debug("%s %02X access to %s%s%s scope %d by %s",
>           aci->type == ACI_DENY ? "deny" : "allow",
>           aci->rights,
>           aci->target ? aci->target : "any",
> +         aci->attribute ? " attribute " : "",
> +         aci->attribute ? aci->attribute : "",
>           aci->scope,
>           aci->subject ? aci->subject : "any");
> +
> +     if (aci->attribute && aci->rights != ACI_WRITE) {
> +             yyerror("attributes only supported for write access filters");
> +             free(aci);
> +             return NULL;
> +     }
>  
>       return aci;
>  }
> Index: usr.sbin/ldapd/search.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/search.c,v
> retrieving revision 1.18
> diff -u -p -u -p -r1.18 search.c
> --- usr.sbin/ldapd/search.c   20 Jan 2017 11:55:08 -0000      1.18
> +++ usr.sbin/ldapd/search.c   11 May 2018 14:09:02 -0000
> @@ -222,7 +222,7 @@ check_search_entry(struct btval *key, st
>       }
>  
>       if (!authorized(search->conn, search->ns, ACI_READ, dn0,
> -         LDAP_SCOPE_BASE)) {
> +         NULL, LDAP_SCOPE_BASE)) {
>               /* LDAP_INSUFFICIENT_ACCESS */
>               free(dn0);
>               return 0;
> @@ -880,7 +880,7 @@ ldap_search(struct request *req)
>       if (*search->basedn == '\0') {
>               /* request for the root DSE */
>               if (!authorized(req->conn, NULL, ACI_READ, "",
> -                 LDAP_SCOPE_BASE)) {
> +                 NULL, LDAP_SCOPE_BASE)) {
>                       reason = LDAP_INSUFFICIENT_ACCESS;
>                       goto done;
>               }
> @@ -897,7 +897,7 @@ ldap_search(struct request *req)
>       if (strcasecmp(search->basedn, "cn=schema") == 0) {
>               /* request for the subschema subentries */
>               if (!authorized(req->conn, NULL, ACI_READ,
> -                 "cn=schema", LDAP_SCOPE_BASE)) {
> +                 "cn=schema", NULL, LDAP_SCOPE_BASE)) {
>                       reason = LDAP_INSUFFICIENT_ACCESS;
>                       goto done;
>               }
> @@ -926,7 +926,7 @@ ldap_search(struct request *req)
>       }
>  
>       if (!authorized(req->conn, search->ns, ACI_READ,
> -         search->basedn, search->scope)) {
> +         search->basedn, NULL, search->scope)) {
>               reason = LDAP_INSUFFICIENT_ACCESS;
>               goto done;
>       }
> 

Reply via email to