summaryrefslogtreecommitdiffstats
path: root/usr.sbin/ldapd
diff options
context:
space:
mode:
authorreyk <reyk@openbsd.org>2018-05-14 07:53:47 +0000
committerreyk <reyk@openbsd.org>2018-05-14 07:53:47 +0000
commit841a002bbb0474e66ad2bdd437353f6586841859 (patch)
tree18d8785a20c1b9ca177e954cbbbfe8d5e8338d79 /usr.sbin/ldapd
parentUse a SRP list to protect pfkeyv2 sockets, thus removing the need to (diff)
downloadwireguard-openbsd-841a002bbb0474e66ad2bdd437353f6586841859.tar.xz
wireguard-openbsd-841a002bbb0474e66ad2bdd437353f6586841859.zip
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@
Diffstat (limited to 'usr.sbin/ldapd')
-rw-r--r--usr.sbin/ldapd/auth.c33
-rw-r--r--usr.sbin/ldapd/ldapd.conf.511
-rw-r--r--usr.sbin/ldapd/ldapd.h4
-rw-r--r--usr.sbin/ldapd/modify.c40
-rw-r--r--usr.sbin/ldapd/parse.y31
-rw-r--r--usr.sbin/ldapd/search.c10
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;
}