Hi all I had a bit time to improve the format speciers of the filters. The filters now support '%u' for the key and '%h' for the table name. This speciers are choosen to be compatible with login_ldap(8) of openbsd[0]. The '%s' specifier still works as the key for backwords compability.
I also have written a manpage. I hope it is understandable and the troff is correct. Patches are attached. Philipp [0] I plan to send my version of aldap to openbsd, so openbsd also benefit from my improvements. But I haven't had the time to do so.
From 8926b3137ad1dad3dfff81408141740d330927a1 Mon Sep 17 00:00:00 2001 From: Philipp Takacs <phil...@bureaucracy.de> Date: Wed, 28 Aug 2024 09:03:48 +0200 Subject: [PATCH 1/2] aldap add %u and %h filter format specifier The filter allows now to use %u and %h for key and table. These specier were chosen to allow compatibility with login_ldap(8). The %s specifier is an alias for %u. --- aldap.c | 140 ++++++++++++++++++++++++++++++++++----------------- aldap.h | 7 ++- table_ldap.c | 5 +- 3 files changed, 103 insertions(+), 49 deletions(-) diff --git a/aldap.c b/aldap.c index 6ead891..0e17cf1 100644 --- a/aldap.c +++ b/aldap.c @@ -43,13 +43,13 @@ #define ALDAP_VERSION 3 static struct ber_element *ldap_parse_search_filter(struct ber_element *, - const char *, const char *); + const char *, const struct aldap_filter_ctx *); static struct ber_element *ldap_do_parse_search_filter( - struct ber_element *, const char **, const char *); + struct ber_element *, const char **, const struct aldap_filter_ctx *); struct aldap_stringset *aldap_get_stringset(struct ber_element *); char *utoa(char *); static int isu8cont(unsigned char); -char *parseval(const char *, size_t, const char *); +static char *parseval(const char *, size_t, const struct aldap_filter_ctx *); int aldap_create_page_control(struct ber_element *, int, struct aldap_page_control *); int aldap_send(struct aldap *, @@ -281,7 +281,7 @@ fail: int aldap_search(struct aldap *ldap, char *basedn, enum scope scope, const char *filter, - const char *key, char * const *attrs, int typesonly, int sizelimit, int timelimit, + const struct aldap_filter_ctx *ctx, char * const *attrs, int typesonly, int sizelimit, int timelimit, struct aldap_page_control *page) { struct ber_element *root = NULL, *ber, *c; @@ -308,7 +308,7 @@ aldap_search(struct aldap *ldap, char *basedn, enum scope scope, const char *fil goto fail; } - if ((ber = ldap_parse_search_filter(ber, filter, key)) == NULL) { + if ((ber = ldap_parse_search_filter(ber, filter, ctx)) == NULL) { ldap->err = ALDAP_ERR_PARSER_ERROR; goto fail; } @@ -967,7 +967,7 @@ aldap_get_stringset(struct ber_element *elm) * NULL, parse failed */ static struct ber_element * -ldap_parse_search_filter(struct ber_element *ber, const char *filter, const char *key) +ldap_parse_search_filter(struct ber_element *ber, const char *filter, const struct aldap_filter_ctx *ctx) { struct ber_element *elm; const char *cp; @@ -979,7 +979,7 @@ ldap_parse_search_filter(struct ber_element *ber, const char *filter, const char return (NULL); } - if ((elm = ldap_do_parse_search_filter(ber, &cp, key)) == NULL) + if ((elm = ldap_do_parse_search_filter(ber, &cp, ctx)) == NULL) return (NULL); if (*cp != '\0') { @@ -1009,7 +1009,7 @@ ldap_parse_search_filter(struct ber_element *ber, const char *filter, const char * */ static struct ber_element * -ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const char *key) +ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const struct aldap_filter_ctx *ctx) { struct ber_element *elm, *root = NULL; char *parsed_val; @@ -1042,7 +1042,7 @@ ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const ch while (*cp == '(') { if ((elm = - ldap_do_parse_search_filter(elm, &cp, key)) == NULL) + ldap_do_parse_search_filter(elm, &cp, ctx)) == NULL) goto bad; } @@ -1056,7 +1056,7 @@ ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const ch ober_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_NOT); cp++; /* now points to sub-filter */ - if ((elm = ldap_do_parse_search_filter(root, &cp, key)) == NULL) + if ((elm = ldap_do_parse_search_filter(root, &cp, ctx)) == NULL) goto bad; if (*cp != ')') /* trailing `)` of filter */ @@ -1147,7 +1147,7 @@ ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const ch else type = LDAP_FILT_SUBS_ANY; - if ((parsed_val = parseval(attr_val, len, key)) == + if ((parsed_val = parseval(attr_val, len, ctx)) == NULL) goto callfail; elm = ober_add_nstring(elm, parsed_val, @@ -1162,7 +1162,7 @@ ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const ch break; } - if ((parsed_val = parseval(attr_val, len, key)) == NULL) + if ((parsed_val = parseval(attr_val, len, ctx)) == NULL) goto callfail; elm = ober_add_nstring(elm, parsed_val, strlen(parsed_val)); free(parsed_val); @@ -1430,17 +1430,44 @@ isu8cont(unsigned char c) return (c & (0x80 | 0x40)) == 0x80; } +static ssize_t +attach_param(char **buffer, size_t *size, size_t pos, const char *param) +{ + char *newbuffer; + size_t len, newsize; + + if (!param) { + return 0; + } + + len = strlen(param); + if (!len) { + return 0; + } + + newsize = *size + len; + newbuffer = realloc(*buffer, newsize); + if (!newbuffer) { + return -1; + } + memcpy(newbuffer + pos, param, len); + *buffer = newbuffer; + *size = newsize; + return len; +} + /* * Parse a LDAP value * notes: * the argument p should be a NUL-terminated sequence of ASCII bytes */ -char * -parseval(const char *p, size_t len, const char *key) +static char * +parseval(const char *p, size_t len, const struct aldap_filter_ctx *ctx) { char hex[3]; - char *buffer, *newbuffer; - size_t i, j, keylen, size, newsize; + char *buffer; + size_t i, j, size; + ssize_t paramlen; size = len + 1; if ((buffer = calloc(1, size)) == NULL) @@ -1452,37 +1479,56 @@ parseval(const char *p, size_t len, const char *key) buffer[i] = (char)strtoumax(hex, NULL, 16); j += 3; } else if (p[j] == '%') { - switch (p[j + 1]) { - case '%': - buffer[i] = '%'; - j += 2; - break; - case 's': - if (!key) { - free(buffer); - return NULL; - } - keylen = strlen(key); - if (!keylen) { - j += 2; - break; - } - newsize = size + keylen; - if ((newbuffer = realloc(buffer, newsize)) == NULL) { - free(buffer); - return NULL; - } - buffer = newbuffer; - size = newsize; - memcpy(buffer + i, key, keylen); - i += keylen - 1; - j += 2; - break; - default: - buffer[i] = '%'; - j++; - break; - } + switch (p[j + 1]) { + case '%': + buffer[i] = '%'; + j += 2; + break; + case 's': /* Backwards compability */ + case 'u': /* username */ + if (ctx) { + paramlen = attach_param(&buffer, &size, i, ctx->username); + } else { + paramlen = 0; + } + + if (paramlen == -1) { + free(buffer); + return NULL; + } + + /* Importend + * the loop increases i (the position on the output) on each iteration + * but this might insert nothing + * so it needs to decrease i by 1 to be on the last written position + */ + i += paramlen - 1; + j += 2; + break; + case 'h': /* hostname */ + if (ctx) { + paramlen = attach_param(&buffer, &size, i, ctx->hostname); + } else { + paramlen = 0; + } + + if (paramlen == -1) { + free(buffer); + return NULL; + } + + /* Importend + * the loop increases i (the position on the output) on each iteration + * but this might insert nothing + * so it needs to decrease i by 1 to be on the last written position + */ + i += paramlen - 1; + j += 2; + break; + default: + free(buffer); + return NULL; + } } else { buffer[i] = p[j]; j++; diff --git a/aldap.h b/aldap.h index 8f45c60..9f69747 100644 --- a/aldap.h +++ b/aldap.h @@ -89,6 +89,11 @@ struct aldap_stringset { struct ber_octetstring *str; }; +struct aldap_filter_ctx { + char *username; + char *hostname; +}; + struct aldap_url { int protocol; char *host; @@ -221,7 +226,7 @@ int aldap_req_starttls(struct aldap *); int aldap_bind(struct aldap *, char *, char *); int aldap_unbind(struct aldap *); -int aldap_search(struct aldap *, char *, enum scope, const char *, const char *, char * const *, int, int, int, struct aldap_page_control *); +int aldap_search(struct aldap *, char *, enum scope, const char *, const struct aldap_filter_ctx *, char * const *, int, int, int, struct aldap_page_control *); int aldap_get_errno(struct aldap *, const char **); int aldap_get_resultcode(struct aldap_message *); diff --git a/table_ldap.c b/table_ldap.c index 7d6b267..7dd488c 100644 --- a/table_ldap.c +++ b/table_ldap.c @@ -580,6 +580,7 @@ lookup_query(int type) static void table_ldap_callback(struct request *req) { + struct aldap_filter_ctx ctx; char ldapid[sizeof(int)*2+1]; int ret; struct query *q = lookup_query(req->s); @@ -614,7 +615,9 @@ table_ldap_callback(struct request *req) return; } - ret = aldap_search(aldap, basedn, LDAP_SCOPE_SUBTREE, q->filter, req->key, attrs, false, num, 0, NULL); + ctx.username = req->key; + ctx.hostname = req->table; + ret = aldap_search(aldap, basedn, LDAP_SCOPE_SUBTREE, q->filter, &ctx, attrs, false, num, 0, NULL); if (ret < 0) { table_api_error(req->id, req->o, NULL); ldap_open(); -- 2.39.2
From 5f40004673dd53a1110f1846020ce5395db99216 Mon Sep 17 00:00:00 2001 From: Philipp Takacs <phil...@bureaucracy.de> Date: Sun, 1 Sep 2024 11:34:12 +0200 Subject: [PATCH 2/2] add manpage --- Makefile.am | 2 + table-ldap.5 | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 table-ldap.5 diff --git a/Makefile.am b/Makefile.am index 9132a4f..3edfec6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,8 @@ table_ldap_SOURCES = table_ldap.c aldap.c ber.c dict.c log.c table_api.c util.c LDADD = $(LIBOBJS) +dist_man5_MANS = table-ldap.5 + EXTRA_DIST = aldap.h ber.h compat.h config.h.in \ dict.h log.h table_api.h util.h diff --git a/table-ldap.5 b/table-ldap.5 new file mode 100644 index 0000000..b41f4b0 --- /dev/null +++ b/table-ldap.5 @@ -0,0 +1,309 @@ +.\" +.\" Copyright (c) 2013 Eric Faurot <e...@openbsd.org> +.\" Copyright (c) Philipp Takacs <philipp+open...@bureaucracy.de> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 25 2024 $ +.Dt TABLE_LDAP 5 +.Os +.Sh NAME +.Nm table_ldap +.Nd format description for smtpd LDAP tables +.Sh DESCRIPTION +This manual page documents the file format of LDAP tables used by the +.Xr smtpd 8 +mail daemon. +.Pp +The format described here applies to tables as defined in +.Xr smtpd.conf 5 . +.Sh LDAP TABLE +A ldap table allows to access userinfo, aliases, domains, and mailaddresses +that are stored in a directory and are provided by an LDAP implementation. +.Pp +The table is used by +.Xr smtpd 8 +when user information such as user-id and/or +home directory is required for a delivery, when a domain lookup may be required, +and/or when looking for an alias. +.Pp +A LDAP table consists of a LDAP tree starting from a base. +Each table lookup will search for the matching objects. + +.Sh LDAP TABLE CONFIG FILE +.Ss COMMON OPTIONS +.Pp +.Bl -tag -width Ds +.It Xo +.Ic url: +.Ar url +.Xc +This is the LDAP url which is used to connect to your LDAP server. +For example: +.Bd -literal -offset indent +url: ldap://ldap.example.com +.Ed +.Pp + +.It Xo +.Ic username: +.Ar binddn +.Xc +The Distinguished Name which is used to bind to the ldap server. +For example: +.Bd -literal -offset indent +username: cn=smtpd,cn=sysaccounts,cn=etc,dc=example,dc=com +.Ed +.Pp + +.It Xo +.Ic password: +.Ar password +.Xc +The password for the binddn. +For example: +.Bd -literal -offset indent +password: OpenSMTPDRules! +.Ed +.Pp + +.It Xo +.Ic basedn: +.Ar basedn +.Xc +The basedn used for each LDAP search request. +For example: +.Bd -literal -offset indent +basedn cn=users,cn=accounts,dc=example,dc=com +.Ed +.El + +.Ss SERVICE OPTIONS +For each service there are two options: +A ldap filter and a list of the attributes. + +.Pp +.Bl -tag -width Ds +.It Xo +.Ic alias_filter: +.Ar filter +.Xc +This filter is used when the table is used in an alias (or virtual alias) context. +The filter should match all entries for this particular alias. +.Pp + +.It Xo +.Ic alias_attributes: +.Ar recipient_atribute +.Xc +The attribute which holds the recipients for an alias. +When the attribute is present multible times, +then all attribute values are returned. +.Pp + +.It Xo +.Ic credentials_filter: +.Ar filter +.Xc +This filter is used to look up credentials for a specific user. +The filter should match exactly one object. +When the filter match multible objects only the last object returned by the serer is used. +.Pp + +.It Xo +.Ic credentials_attributes: +.Ar username_attribute, passwordhash_attribute +.Xc +The attributes for username and password hash. +The password hash must be encoded like the output of +.Xr smtpctl 8 +encrypt subcommand. +When one attribute is missing the object is ignored. +When a attribute is present multiple times, +then only the first one is used. +.Pp + +.It Xo +.Ic domain_filter: +.Ar filter +.Xc +The filter for domains is used to lookup a domain. +.Xr smtpd 8 +currently only checks if a domain exists in the table. +.Pp + +.It Xo +.Ic domain_attributes: +.Ar attribute +.Xc +This attribute is currently not used, because +.Xr smtpd 8 +does only check if the domain exists. +.Pp + +.It Xo +.Ic userinfo_filter: +.Ar filter +.Xc +This filter is to lookup the userinfo for an userbase. +The filter should match exactly one object. +When the filter match multible objects only the last object returned by the server is used. +When a attribute is present multiple times, +then only the first one is used. +.Pp + +.It Xo +.Ic userinfo_attributes: +.Ar uid_attribute, gid_attribute, home_attribute +.Xc +The attributes for uid, gid, and the home directory of the user. +When one attribute is missing the object is ignored. +When a attribute is present multiple times, +then only the first one is used. +.Pp + +.It Xo +.Ic mailaddr_filter: +.Ar filter +.Xc +This filter is to match mailaddres. +.Xr smtpd 8 +currently only checks if a domain exists in the table. +.Xr +.Pp + +.It Xo +.Ic mailaddr_attributes: +.Ar attribute +.Xc +This attribute is currently not used, because +.Xr smtpd 8 +does only check if the mailaddr exists. +.Pp + +.It Xo +.Ic mailaddrmap_filter: +.Ar filter +.Xc +This filter is to map users to mailaddresses. +The filter should return all object conaining the mailaddress of the user. +.Xr +.Pp + +.It Xo +.Ic mailaddrmap_attributes: +.Ar mail_attribute +.Xc +The attribute containe the mailaddresses of the user. +When the attribute is present multiple times, +then all attribute values are returned. +.El + +.Sh FORMAT SPECIFIERS + +The filter options are designed to allow per request +Filters allow some specifiers in the attribute values of the filter. +The following speciers are defined: + +.Bl -column %s -offset indent +.It %u The key of the request +.It %h The name of the table +.It %% The literal '%' +.It %s Depricated alias for the the key +.El + +When a not defined specier is used the filter will not work. + +.Sh EXAMPLES +.Ss USERS EXAMPLE +This example show how to use the ldap table for user accounts stored in ldap. +Mails for local users are deliverd to a lmtp server. +Most of the example works with a normal ldap schema. +Only for the smtpPassword a schemaextention is required. + +.Ic Pa /etc/mail/ldap_users.conf +.Bd -literal -offset indent +url: ldap.example.com +username: uid=smtpd,cn=services,dc=example,dc=com +password: OpenSMTPDRules! +basedn: cn=users,dc=example,dc=com + +alias_filter: (&(objectclass=posixaccount)(mail=%u)) +alias_attributes: uid + +credentials_filter: (&(objectclass=posixaccount)(uid=%u)) +credentials_attributes: uid, smtpPassword + +userinfo_filter: (&(objectclass=posixaccount)(uid=%u)) +userinfo_attributes: uidNumber, gidNumber, homedir + +mailaddr_filter: (&(objectclass=posixaccount)(mail=%s)) +mailaddr_attributes: mail + +mailaddrmap_filter: (&(objectclass=posixaccount)(uid=%s)) +mailaddrmap_attributes: mail +.Ed + +.Ic Pa /etc/mail/smtpd.conf +.Bd -literal -offset indent +table users ldap:/etc/mail/ldap_users.conf + +pki mail.example.com cert "/etc/ssl/mail.example.com.crt" +pki mail.example.com key "/etc/ssl/private/mail.example.com.key" + +listen on egress port 25 tls pki mail.example.com +listen on egress port 587 tls-require pki mail.example.com senders <users> auth <users> + +match for rcpt-to <users> action "ldapusers" +match from auth for any action "relay" +match from any reject + +action "ldapusers" lmtp "lmtp.example.com:1234" userbase <users> virtual <users> +action "relay" relay +.Ed + +With this example a user can auth against the credentials stored in ldap. +The user can send mails with the addresses specified in ldap as sender. +Mails for the addresses specified in ldap are maped to the user and deliverd over lmtp. + +.Sh BUGS + +Currently only check and lookup requests are implemented. +Fetch and update should also be supported. + +The auth request is not suported so the password must be readable by the binddn and formated for smtpd. + +Pagination is currently not supported. + +.Sh STANDARDS +.Rs +.%A J. Sermersheim +.%D June 2006 +.%R RFC 4511 +.%T Lightweight Directory Access Protocol (LDAP): The Protocol +.Re + +.Rs +.%A J. Sermersheim +.%A M. Smith +.%D June 2006 +.%R RFC 4515 +.%T Lightweight Directory Access Protocol (LDAP): String Representation of Search Filters +.Re + +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr smtpctl 8 , +.Xr smtpd 8 , +.Xr encrypt 1 , +.Xr crypt 3 -- 2.39.2