On 2024/01/24 09:38:01 +0100, Philipp <phil...@bureaucracy.de> wrote: > Hi Omar > > Thanks for the feedback. A updated patch is attached. > > [2024-01-23 11:26] Omar Polo <o...@omarpolo.com> > > On 2024/01/23 01:24:57 +0100, Philipp <phil...@bureaucracy.de> wrote: > > > I have had a bit of time and implemented ldaps support for table-ldap. > > > It is currently untested and has some todos. But I would say it's > > > complete enough to share. So other can comment on the code. A patch > > > is attached > > > > I don't use ldap and completely lack the experience with it, so I can > > only provide some feedback on the code itself, not if it makes sense to > > have TLS in here nor provide testing. > > As said in the other thread I'll test this tomorrow.
thanks :) > > [...] > > > /* XXX this should be moved to a different function */ > > > - if (ber->fd != -1) > > > + if (ber->fd != -1) { > > > + if (ber->tls_ctx) { > > > + return tls_write(ber->tls_ctx, ber->br_wbuf, len); > > > > This, and the other calls to tls_read are wrong. Even in the blocking > > case, we need to wrap these in a loop to handle TLS_WANT_POLLIN and > > TLS_WANT_POLLOUT, as per the manpage example. > > > > I remember being bitten by this, as libretls (libtls over openssl) > > returns the TLS_WANT_* more often than LibreSSL' libtls. > > A good to know. I'm a bit unsure about my solution. Do I need to acualy > poll() in the read/write wrapper or is it good enough to just call it in > a loop? since we're using blocking i/o just calling it in a loop is enough. poll(2) here would just be wasting time for a syscall with no real benefit. > > > [...] > > > +static struct tls * > > > +ldaps_connect(int fd, char *hostname) > > > +{ > > > + /* XXX log */ > > > + struct tls *ctx = NULL; > > > + struct tls_config *config = tls_config_new(); > > > + if (!config) > > > + goto fail; > > > + if (tls_config_set_ca_path(config, "/etc/ssl/certs") == -1) > > > + goto fail; > > > > Is this needed? out-of-the-box on OpenBSD this directory doesn't exist, > > but don't know if it's the "de-facto" place to store certs for ldap. In > > any case, I think it should be at least configurable. > > This is WIP, I have just added the default ca store of debian. I coudn't > find if libtls does this by default. A config option for a ca file is > in planing. If it's the default store then you don't need to explicitly add it. You'll only need this to add a non-standard ca path. > > > [...] > > > + tls_config_free(config); > > > + return ctx; > > > +fail: > > > + tls_close(ctx); > > > + tls_free(ctx); > > > + tls_config_free(config); > > > > these close/free calls needs to be guarded since ctx and config may be > > NULL when here and these functions will deference the argument. > > Only for the tls_close, tls_free() and tls_config_free() behave like free() > and are noops with a NULL as argument. yeah, i was thinking about close and extended the though to the others. > > > + return NULL; > > > +} > > > + > > > static struct aldap * > > > ldap_connect(const char *addr) > > > { > > > @@ -121,13 +150,25 @@ ldap_connect(const char *addr) > > > > ouch... ldap_connect is really a bit over-complicate. I'd prefer if we > > simplify this a bit first. I'll soon send a diff for this. > > Nice, I have rebased my patch on the cleanup. A few more comments on the diff. by the way, this partially failed to apply on top of my getaddrinfo() diff. If you prefer, I can push it so you can rebase against it. also, depending on how you prefer, I don't want you to rebase commits too much, but we could commit the other bunch of diffs first, and then get to this one. > --- a/extras/tables/table-ldap/aldap.c > +++ b/extras/tables/table-ldap/aldap.c > [...] > @@ -55,6 +56,12 @@ void ldap_debug_elements(struct > ber_element *); > int > aldap_close(struct aldap *al) > { > + if (al->ber.tls_ctx) { > + if (tls_close(al->ber.tls_ctx) == -1) > + return (-1); I forgot that tls_close() also has the same behaviour of handshake, read and write and to properly close the connection should be called in a loop (this because I think it needs to do I/O for the close_notify thingy.) btw, for now it's fine but we shouldn't leak the context nor the file descriptor just because of a failure. The behaviour is already here, so it's fine to leave this like it is and look at it later. > + tls_free(al->ber.tls_ctx); > + } > + > if (close(al->ber.fd) == -1) > return (-1); > > @@ -65,13 +72,14 @@ aldap_close(struct aldap *al) > } > > struct aldap * > -aldap_init(int fd) > +aldap_init(int fd, struct tls *ctx) > { > struct aldap *a; > > if ((a = calloc(1, sizeof(*a))) == NULL) > return NULL; > a->ber.fd = fd; > + a->ber.tls_ctx = ctx; > > return a; > } > @@ -574,10 +582,15 @@ aldap_parse_url(char *url, struct aldap_url *lu) > p = lu->buffer; > > /* protocol */ > - if (strncasecmp(LDAP_URL, p, strlen(LDAP_URL)) != 0) > + if (strncasecmp(LDAP_URL, p, strlen(LDAP_URL)) == 0) { > + lu->protocol = LDAP; > + p += strlen(LDAP_URL); > + } else if (strncasecmp(LDAPS_URL, p, strlen(LDAP_URL)) == 0) { > + lu->protocol = LDAPS; > + p += strlen(LDAPS_URL); > + } else { > goto fail; > - lu->protocol = LDAP; > - p += strlen(LDAP_URL); > + } > > /* host and optional port */ > if ((forward = strchr(p, '/')) != NULL) > @@ -594,6 +607,9 @@ aldap_parse_url(char *url, struct aldap_url *lu) > } > } else { > lu->port = LDAP_PORT; > + if (lu->protocol == LDAPS) { > + lu->port = LDAPS_PORT; > + } > } > /* fail if no host is given */ > if (strlen(p) == 0) > diff --git a/extras/tables/table-ldap/aldap.h > b/extras/tables/table-ldap/aldap.h > index 7cfd637..fec106b 100644 > --- a/extras/tables/table-ldap/aldap.h > +++ b/extras/tables/table-ldap/aldap.h > @@ -20,6 +20,8 @@ > > #define LDAP_URL "ldap://" > #define LDAP_PORT 389 > +#define LDAPS_URL "ldaps://" > +#define LDAPS_PORT 636 > #define LDAP_PAGED_OID "1.2.840.113556.1.4.319" > > struct aldap { > @@ -69,15 +71,15 @@ enum aldap_protocol { > }; > > struct aldap_url { > - int protocol; > - char *host; > - in_port_t port; > - char *dn; > + enum aldap_protocol protocol; > + char *host; > + in_port_t port; > + char *dn; > #define MAXATTR 1024 > - char *attributes[MAXATTR]; > - int scope; > - char *filter; > - char *buffer; > + char *attributes[MAXATTR]; > + int scope; > + char *filter; > + char *buffer; > }; > > enum protocol_op { > @@ -186,7 +188,7 @@ enum ldap_subfilter { > LDAP_FILT_SUBS_FIN = 2, > }; > > -struct aldap *aldap_init(int fd); > +struct aldap *aldap_init(int fd, struct tls *ctx); > int aldap_close(struct aldap *); > struct aldap_message *aldap_parse(struct aldap *); > void aldap_freemsg(struct aldap_message *); > diff --git a/extras/tables/table-ldap/ber.c b/extras/tables/table-ldap/ber.c > index 0f92bb2..21a1924 100644 > --- a/extras/tables/table-ldap/ber.c > +++ b/extras/tables/table-ldap/ber.c > @@ -24,12 +24,14 @@ > #include <limits.h> > #include <stdlib.h> > #include <err.h> /* XXX for debug output */ > +#include <stdarg.h> > #include <stdio.h> /* XXX for debug output */ > #include <string.h> > +#include <tls.h> > #include <unistd.h> > -#include <stdarg.h> > > #include "ber.h" > +#include "tlswrapper.h" > > #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) > > @@ -784,8 +786,12 @@ ber_write_elements(struct ber *ber, struct ber_element > *root) > return -1; > > /* XXX this should be moved to a different function */ > - if (ber->fd != -1) > + if (ber->fd != -1) { > + if (ber->tls_ctx) { > + return tls_write_wrapper(ber->tls_ctx, ber->br_wbuf, > len); nit: we're not always consistent, but I'd prefer for new code to stay under 80 columns. > + } > return write(ber->fd, ber->br_wbuf, len); > + } > > return (len); > } > @@ -1233,8 +1239,12 @@ ber_getc(struct ber *b, unsigned char *c) > */ > if (b->fd == -1) > r = ber_readbuf(b, c, 1); > - else > - r = read(b->fd, c, 1); > + else { > + if (b->tls_ctx) > + r = tls_read_wrapper(b->tls_ctx, c, 1); > + else > + r = read(b->fd, c, 1); nit: no need to nest the ifs, this is equivalent and easier to read IMHO. if (b->fd == -1) ... else if (b->tls_ctx) ... else ... > + } > return r; > } > > @@ -1253,8 +1263,12 @@ ber_read(struct ber *ber, void *buf, size_t len) > while (remain > 0) { > if (ber->fd == -1) > r = ber_readbuf(ber, b, remain); > - else > - r = read(ber->fd, b, remain); > + else { > + if (ber->tls_ctx) > + r = tls_read_wrapper(ber->tls_ctx, b, remain); > + else > + r = read(ber->fd, b, remain); > + } same here > if (r == -1) { > if (errno == EINTR || errno == EAGAIN) > continue; > diff --git a/extras/tables/table-ldap/ber.h b/extras/tables/table-ldap/ber.h > index d656508..4987fc2 100644 > --- a/extras/tables/table-ldap/ber.h > +++ b/extras/tables/table-ldap/ber.h > @@ -34,6 +34,7 @@ struct ber_element { > > struct ber { > int fd; > + struct tls *tls_ctx; > unsigned char *br_wbuf; > unsigned char *br_wptr; > unsigned char *br_wend; > diff --git a/extras/tables/table-ldap/table_ldap.c > b/extras/tables/table-ldap/table_ldap.c > index 79a26a8..3ea17a0 100644 > --- a/extras/tables/table-ldap/table_ldap.c > +++ b/extras/tables/table-ldap/table_ldap.c > @@ -26,10 +26,12 @@ > #include <stdlib.h> > #include <stdio.h> > #include <string.h> > +#include <tls.h> > #include <unistd.h> > > #include <smtpd-api.h> > #include "aldap.h" > +#include "tlswrapper.h" > > #define MAX_LDAP_IDENTIFIER 32 > #define MAX_LDAP_URL 256 > @@ -80,6 +82,36 @@ table_ldap_fetch(int service, struct dict *params, char > *dst, size_t sz) > return -1; > } > > +static struct tls * > +ldaps_connect(int fd, char *hostname) > +{ > + /* XXX log */ > + struct tls *ctx = NULL; > + struct tls_config *config = tls_config_new(); > + if (!config) > + goto fail; > + /* XXX fix needed, build time config and run time for ca_file */ > + if (tls_config_set_ca_path(config, "/etc/ssl/certs") == -1) > + goto fail; > + if ((ctx = tls_client()) == NULL) > + goto fail; > + if (tls_configure(ctx, config) == -1) > + goto fail; > + if (tls_connect_socket(ctx, fd, hostname) == -1) > + goto fail; > + if (tls_handshake_wrapper(ctx, fd) == -1) > + goto fail; > + > + tls_config_free(config); > + return ctx; > +fail: > + if (ctx) > + tls_close(ctx); I don't think that we need to wrap tls_close() in a loop here, being it in a error path, so my earlier comment doesn't apply here :) > + tls_free(ctx); > + tls_config_free(config); > + return NULL; > +} > + > static struct aldap * > ldap_connect(const char *addr) > { > @@ -123,8 +155,16 @@ ldap_connect(const char *addr) > continue; > > if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) { > + struct tls *ctx = NULL; > + if (lu.protocol == LDAPS) { > + if ((ctx = ldaps_connect(fd, lu.host)) == NULL) > { > + close(fd); > + fd = -1; > + continue; I'm a bit unsure about this. Shouldn't we consider a failure in setting up the TLS layer fatal? > + } > + } > aldap_free_url(&lu); > - return aldap_init(fd); > + return aldap_init(fd, ctx); > } > > close(fd); > diff --git a/extras/tables/table-ldap/tlswrapper.c > b/extras/tables/table-ldap/tlswrapper.c > new file mode 100644 > index 0000000..03487ec > --- /dev/null > +++ b/extras/tables/table-ldap/tlswrapper.c > @@ -0,0 +1,60 @@ I don't think we need to introduce a new file for this. handshake and close are used only once (where it matters for looping), read and write three times. I would just inline these where needed. (and in any case, when adding a new file it should have a copyright and license block on top like the others.) > [...] > +int > +tls_handshake_wrapper(struct tls *ctx, int fd) > +{ > + struct pollfd pfd[1]; > + pfd[0].fd = fd; > + > + while (1) { > + switch (tls_handshake(ctx)) { > + case -1: > + return -1; > + case TLS_WANT_POLLIN: > + pfd[0].events = POLLIN; > + break; > + case TLS_WANT_POLLOUT: > + pfd[0].events = POLLOUT; > + break; > + default: > + return 0; > + } > + poll(pfd, 1, -1); as said earlier, poll() is not needed, just continue looping: switch (tls_handshake(ctx)) { case 0: break; case -1: return; default: continue; } or even just do { ret = tls_handshake(ctx); } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); > [...] > +ssize_t > +tls_read_wrapper(struct tls *ctx, void *buf, size_t buflen) > +{ > + ssize_t rlen = 0; > + ssize_t ret; > + while (rlen <= 0) { here it should be buflen, not rlen, and it should be just > 0 while (buflen > 0) { > + ret = tls_read(ctx, buf, buflen); > + if (ret == -1) > + return -1; > + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) > + continue; > + rlen += ret; here we should also do buf += ret; buflen -= ret; > + } > + return rlen; > +}