diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2018-05-14 07:53:48 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2018-05-14 07:53:48 +0000 |
commit | 34d89714ef390dfb54fee4b16eb100d6f1b25534 (patch) | |
tree | b596d9fe81a218b1b0895d7f78b6f0be86f086aa | |
parent | a50c3525b460dd399538dfb713bf5519518ce4f7 (diff) |
Add support to filter 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:
allow read access to any by self
allow write access to any attribute userPassword by self
This is currently only supported for "write" (modify, add, delete) and
not "read" (search) filter rules.
OK jmatthew@
-rw-r--r-- | usr.sbin/ldapd/auth.c | 33 | ||||
-rw-r--r-- | usr.sbin/ldapd/ldapd.conf.5 | 11 | ||||
-rw-r--r-- | usr.sbin/ldapd/ldapd.h | 4 | ||||
-rw-r--r-- | usr.sbin/ldapd/modify.c | 40 | ||||
-rw-r--r-- | usr.sbin/ldapd/parse.y | 31 | ||||
-rw-r--r-- | usr.sbin/ldapd/search.c | 10 |
6 files changed, 97 insertions, 32 deletions
diff --git a/usr.sbin/ldapd/auth.c b/usr.sbin/ldapd/auth.c index 45f9ef81ddd..b61cb9c5bce 100644 --- a/usr.sbin/ldapd/auth.c +++ b/usr.sbin/ldapd/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.12 2017/01/20 11:55:08 benno Exp $ */ +/* $OpenBSD: auth.c,v 1.13 2018/05/14 07:53:47 reyk Exp $ */ /* * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> @@ -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 *conn, struct namespace *ns, 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 *conn, struct namespace *ns, */ 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 namespace *ns, int rights, char *dn, 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, char *binddn, struct ber_element *auth) 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); diff --git a/usr.sbin/ldapd/ldapd.conf.5 b/usr.sbin/ldapd/ldapd.conf.5 index 5f7dbc42f2e..769aa785cdc 100644 --- a/usr.sbin/ldapd/ldapd.conf.5 +++ b/usr.sbin/ldapd/ldapd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ldapd.conf.5,v 1.22 2016/10/17 14:03:17 jca Exp $ +.\" $OpenBSD: ldapd.conf.5,v 1.23 2018/05/14 07:53:47 reyk Exp $ .\" .\" Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> .\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> @@ -17,7 +17,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: October 17 2016 $ +.Dd $Mdocdate: May 14 2018 $ .Dt LDAPD.CONF 5 .Os .Sh NAME @@ -248,6 +248,13 @@ This is the default if no scope is specified. 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 diff --git a/usr.sbin/ldapd/ldapd.h b/usr.sbin/ldapd/ldapd.h index 92e19188141..bc9b53e0c67 100644 --- a/usr.sbin/ldapd/ldapd.h +++ b/usr.sbin/ldapd/ldapd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ldapd.h,v 1.28 2017/02/24 14:28:31 gsoares Exp $ */ +/* $OpenBSD: ldapd.h,v 1.29 2018/05/14 07:53:47 reyk Exp $ */ /* * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> @@ -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); diff --git a/usr.sbin/ldapd/modify.c b/usr.sbin/ldapd/modify.c index eda202d9417..0cd8742d3fe 100644 --- a/usr.sbin/ldapd/modify.c +++ b/usr.sbin/ldapd/modify.c @@ -1,4 +1,4 @@ -/* $OpenBSD: modify.c,v 1.20 2017/07/28 12:58:52 florian Exp $ */ +/* $OpenBSD: modify.c,v 1.21 2018/05/14 07:53:47 reyk Exp $ */ /* * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> @@ -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) { diff --git a/usr.sbin/ldapd/parse.y b/usr.sbin/ldapd/parse.y index 85c4db7f760..68c885852b3 100644 --- a/usr.sbin/ldapd/parse.y +++ b/usr.sbin/ldapd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.26 2018/04/26 14:12:19 krw Exp $ */ +/* $OpenBSD: parse.y,v 1.27 2018/05/14 07:53:47 reyk Exp $ */ /* * Copyright (c) 2009, 2010 Martin Hedenfalk <martinh@openbsd.org> @@ -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 = NULL; %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_target aci_subject { } | 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 *cert, } 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,15 +1152,24 @@ mk_aci(int type, int rights, enum scope scope, char *target, char *subject) 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; } diff --git a/usr.sbin/ldapd/search.c b/usr.sbin/ldapd/search.c index 715aa92b94d..a12593aceac 100644 --- a/usr.sbin/ldapd/search.c +++ b/usr.sbin/ldapd/search.c @@ -1,4 +1,4 @@ -/* $OpenBSD: search.c,v 1.18 2017/01/20 11:55:08 benno Exp $ */ +/* $OpenBSD: search.c,v 1.19 2018/05/14 07:53:47 reyk Exp $ */ /* * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> @@ -222,7 +222,7 @@ check_search_entry(struct btval *key, struct btval *val, struct search *search) } 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; } |