This adds TLS to ypldap(8), both 'ldaps' (TLS on port 636) and 'tls' (on port
389, after a 'starttls' extended operation) variants.  I've tried this against
one of our AD servers and against ldapd.


Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/Makefile,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 Makefile
--- Makefile    9 Sep 2015 15:33:18 -0000       1.8
+++ Makefile    24 Jan 2017 03:27:47 -0000
@@ -9,7 +9,7 @@ SRCS=           parse.y ypldap.c log.c  \
 MAN=           ypldap.8 ypldap.conf.5
 
 DPADD=         ${LIBEVENT} ${LIBUTIL} ${LIBRPCSVC}
-LDADD=         -levent -lutil -lrpcsvc
+LDADD=         -ltls -levent -lutil -lrpcsvc
 CFLAGS+=       -I${.CURDIR}
 CFLAGS+=       -Wall
 CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
Index: aldap.c
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/aldap.c,v
retrieving revision 1.34
diff -u -p -u -p -r1.34 aldap.c
--- aldap.c     22 Oct 2016 03:37:13 -0000      1.34
+++ aldap.c     24 Jan 2017 03:27:47 -0000
@@ -25,6 +25,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <event.h>
+
 #include "aldap.h"
 
 #if 0
@@ -42,6 +44,9 @@ static int                     isu8cont(unsigned char);
 char                           *parseval(char *, size_t);
 int                            aldap_create_page_control(struct ber_element *,
                                    int, struct aldap_page_control *);
+int                            aldap_send(struct aldap *,
+                                   struct ber_element *);
+unsigned long                  aldap_application(struct ber_element *);
 
 #ifdef DEBUG
 void                    ldap_debug_elements(struct ber_element *);
@@ -55,13 +60,21 @@ void                         ldap_debug_elements(struct 
ber_e
 #define LDAP_DEBUG(x, y)       do { } while (0)
 #endif
 
+unsigned long
+aldap_application(struct ber_element *elm)
+{
+       return BER_TYPE_OCTETSTRING;
+}
+
 int
 aldap_close(struct aldap *al)
 {
-       if (close(al->ber.fd) == -1)
-               return (-1);
+       if (al->fd != -1)
+               if (close(al->ber.fd) == -1)
+                       return (-1);
 
        ber_free(&al->ber);
+       evbuffer_free(al->buf);
        free(al);
 
        return (0);
@@ -74,16 +87,109 @@ aldap_init(int fd)
 
        if ((a = calloc(1, sizeof(*a))) == NULL)
                return NULL;
-       a->ber.fd = fd;
+       a->buf = evbuffer_new();
+       a->fd = fd;
+       a->ber.fd = -1;
+       ber_set_application(&a->ber, aldap_application);
 
        return a;
 }
 
 int
+aldap_tls(struct aldap *ldap, struct tls_config *cfg, const char *name)
+{
+       ldap->tls = tls_client();
+       if (ldap->tls == NULL) {
+               ldap->err = ALDAP_ERR_OPERATION_FAILED;
+               return (-1);
+       }
+
+       if (tls_configure(ldap->tls, cfg)) {
+               ldap->err = ALDAP_ERR_TLS_ERROR;
+               return (-1);
+       }
+
+       if (tls_connect_socket(ldap->tls, ldap->fd, name)) {
+               ldap->err = ALDAP_ERR_TLS_ERROR;
+               return (-1);
+       }
+
+       if (tls_handshake(ldap->tls)) {
+               ldap->err = ALDAP_ERR_TLS_ERROR;
+               return (-1);
+       }
+
+       ldap->fd = -1;
+       return (0);
+}
+
+int
+aldap_send(struct aldap *ldap, struct ber_element *root)
+{
+       int error, wrote;
+       void *data;
+       size_t len, done;
+
+       len = ber_calc_len(root);
+       error = ber_write_elements(&ldap->ber, root);
+       ber_free_elements(root);
+       if (error == -1)
+               return -1;
+
+       ber_get_writebuf(&ldap->ber, &data);
+       done = 0;
+       while (len > 0) {
+               if (ldap->tls != NULL) {
+                       wrote = tls_write(ldap->tls, ((char *)data) + done,
+                           len);
+                       if (wrote == TLS_WANT_POLLIN ||
+                           wrote == TLS_WANT_POLLOUT)
+                               continue;
+               } else
+                       wrote = write(ldap->fd, ((char *)data) + done, len);
+
+               if (wrote == -1)
+                       return -1;
+
+               len -= wrote;
+               done += wrote;
+       }
+
+       return 0;
+}
+
+int
+aldap_req_starttls(struct aldap *ldap)
+{
+       struct ber_element *root = NULL, *ber;
+
+       if ((root = ber_add_sequence(NULL)) == NULL)
+               goto fail;
+
+       ber = ber_printf_elements(root, "d{tst", ++ldap->msgid, BER_CLASS_APP,
+           (unsigned long) LDAP_REQ_EXTENDED, LDAP_STARTTLS_OID,
+           BER_CLASS_CONTEXT, (unsigned long) 0);
+       if (ber == NULL) {
+               ldap->err = ALDAP_ERR_OPERATION_FAILED;
+               goto fail;
+       }
+
+       if (aldap_send(ldap, root) == -1)
+               goto fail;
+
+       return (ldap->msgid);
+fail:
+       if (root != NULL)
+               ber_free_elements(root);
+
+       ldap->err = ALDAP_ERR_OPERATION_FAILED;
+       return (-1);
+}
+
+int
 aldap_bind(struct aldap *ldap, char *binddn, char *bindcred)
 {
        struct ber_element *root = NULL, *elm;
-       int error;
 
        if (binddn == NULL)
                binddn = "";
@@ -101,12 +207,10 @@ aldap_bind(struct aldap *ldap, char *bin
 
        LDAP_DEBUG("aldap_bind", root);
 
-       error = ber_write_elements(&ldap->ber, root);
-       ber_free_elements(root);
-       root = NULL;
-       if (error == -1)
+       if (aldap_send(ldap, root) == -1) {
+               root = NULL;
                goto fail;
-
+       }
        return (ldap->msgid);
 fail:
        if (root != NULL)
@@ -120,7 +224,6 @@ int
 aldap_unbind(struct aldap *ldap)
 {
        struct ber_element *root = NULL, *elm;
-       int error;
 
        if ((root = ber_add_sequence(NULL)) == NULL)
                goto fail;
@@ -131,12 +234,10 @@ aldap_unbind(struct aldap *ldap)
 
        LDAP_DEBUG("aldap_unbind", root);
 
-       error = ber_write_elements(&ldap->ber, root);
-       ber_free_elements(root);
-       root = NULL;
-       if (error == -1)
+       if (aldap_send(ldap, root) == -1) {
+               root = NULL;
                goto fail;
-
+       }
        return (ldap->msgid);
 fail:
        if (root != NULL)
@@ -153,7 +254,7 @@ aldap_search(struct aldap *ldap, char *b
     struct aldap_page_control *page)
 {
        struct ber_element *root = NULL, *ber, *c;
-       int i, error;
+       int i;
 
        if ((root = ber_add_sequence(NULL)) == NULL)
                goto fail;
@@ -191,10 +292,8 @@ aldap_search(struct aldap *ldap, char *b
 
        LDAP_DEBUG("aldap_search", root);
 
-       error = ber_write_elements(&ldap->ber, root);
-       ber_free_elements(root);
-       root = NULL;
-       if (error == -1) {
+       if (aldap_send(ldap, root) == -1) {
+               root = NULL;
                ldap->err = ALDAP_ERR_OPERATION_FAILED;
                goto fail;
        }
@@ -255,12 +354,44 @@ aldap_parse(struct aldap *ldap)
        long long                msgid = 0;
        struct aldap_message    *m;
        struct ber_element      *a = NULL, *ep;
+       char                     rbuf[512];
+       int                      ret, retry;
 
        if ((m = calloc(1, sizeof(struct aldap_message))) == NULL)
                return NULL;
 
-       if ((m->msg = ber_read_elements(&ldap->ber, NULL)) == NULL)
-               goto parsefail;
+       retry = 0;
+       while (m->msg == NULL) {
+               if (retry || EVBUFFER_LENGTH(ldap->buf) == 0) {
+                       if (ldap->tls) {
+                               ret = tls_read(ldap->tls, rbuf, sizeof(rbuf));
+                               if (ret == TLS_WANT_POLLIN ||
+                                   ret == TLS_WANT_POLLOUT)
+                                       continue;
+                       } else
+                               ret = read(ldap->fd, rbuf, sizeof(rbuf));
+
+                       if (ret == -1) {
+                               goto parsefail;
+                       }
+
+                       evbuffer_add(ldap->buf, rbuf, ret);
+               }
+
+               if (EVBUFFER_LENGTH(ldap->buf) > 0) {
+                       ber_set_readbuf(&ldap->ber, EVBUFFER_DATA(ldap->buf),
+                           EVBUFFER_LENGTH(ldap->buf));
+                       errno = 0;
+                       m->msg = ber_read_elements(&ldap->ber, NULL);
+                       if (errno != 0 && errno != ECANCELED) {
+                               goto parsefail;
+                       }
+
+                       retry = 1;
+               }
+       }
+
+       evbuffer_drain(ldap->buf, ldap->ber.br_rptr - ldap->ber.br_rbuf);
 
        LDAP_DEBUG("message", m->msg);
 
@@ -303,10 +434,17 @@ aldap_parse(struct aldap *ldap)
                if (ber_scanf_elements(m->protocol_op, "{e", &m->references) != 
0)
                        goto parsefail;
                break;
+       case LDAP_RES_EXTENDED:
+               if (ber_scanf_elements(m->protocol_op, "{E",
+                   &m->body.res.rescode) != 0) {
+                       goto parsefail;
+               }
+               break;
        }
 
        return m;
 parsefail:
+       evbuffer_drain(ldap->buf, EVBUFFER_LENGTH(ldap->buf));
        ldap->err = ALDAP_ERR_PARSER_ERROR;
        aldap_freemsg(m);
        return NULL;
@@ -1250,6 +1388,9 @@ aldap_get_errno(struct aldap *a, const c
                break;
        case ALDAP_ERR_OPERATION_FAILED:
                *estr = "operation failed";
+               break;
+       case ALDAP_ERR_TLS_ERROR:
+               *estr = tls_error(a->tls);
                break;
        default:
                *estr = "unknown";
Index: aldap.h
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/aldap.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 aldap.h
--- aldap.h     30 Apr 2012 21:40:03 -0000      1.9
+++ aldap.h     24 Jan 2017 03:27:47 -0000
@@ -19,20 +19,31 @@
  */
 
 #include <stdio.h>
+
+#include <tls.h>
+
 #include "ber.h"
 
-#define LDAP_URL "ldap://";
-#define LDAP_PORT 389
-#define LDAP_PAGED_OID  "1.2.840.113556.1.4.319"
+#define LDAP_URL               "ldap://";
+#define LDAP_PORT              389
+#define LDAPS_PORT             636
+#define LDAP_PAGED_OID         "1.2.840.113556.1.4.319"
+#define LDAP_STARTTLS_OID      "1.3.6.1.4.1.1466.20037"
 
 struct aldap {
 #define ALDAP_ERR_SUCCESS              0
 #define ALDAP_ERR_PARSER_ERROR         1
 #define ALDAP_ERR_INVALID_FILTER       2
 #define ALDAP_ERR_OPERATION_FAILED     3
+#define ALDAP_ERR_TLS_ERROR            4
        u_int8_t        err;
        int             msgid;
        struct ber      ber;
+
+       int             fd;
+       struct tls      *tls;
+
+       struct evbuffer *buf;
 };
 
 struct aldap_page_control {
@@ -103,6 +114,9 @@ enum protocol_op {
        LDAP_REQ_ABANDON_30     = 16,
 
        LDAP_RES_SEARCH_REFERENCE = 19,
+
+       LDAP_REQ_EXTENDED       = 23,
+       LDAP_RES_EXTENDED       = 24
 };
 
 enum deref_aliases {
@@ -189,10 +203,14 @@ enum subfilter {
        LDAP_FILT_SUBS_FIN      = 2,
 };
 
-struct aldap           *aldap_init(int fd);
+struct aldap           *aldap_init(int);
+int                     aldap_tls(struct aldap *, struct tls_config *,
+                           const char *);
 int                     aldap_close(struct aldap *);
 struct aldap_message   *aldap_parse(struct aldap *);
 void                    aldap_freemsg(struct aldap_message *);
+
+int                     aldap_req_starttls(struct aldap *);
 
 int     aldap_bind(struct aldap *, char *, char *);
 int     aldap_unbind(struct aldap *);
Index: ldapclient.c
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ldapclient.c,v
retrieving revision 1.38
diff -u -p -u -p -r1.38 ldapclient.c
--- ldapclient.c        20 Jan 2017 12:39:36 -0000      1.38
+++ ldapclient.c        24 Jan 2017 03:27:47 -0000
@@ -97,20 +97,28 @@ client_addr_init(struct idm *idm)
 {
         struct sockaddr_in      *sa_in;
         struct sockaddr_in6     *sa_in6;
-        struct ypldap_addr         *h;
+        struct ypldap_addr      *h;
+       int                     defport;
+
+       if (idm->idm_port != 0)
+               defport = idm->idm_port;
+       else if (idm->idm_flags & F_SSL)
+               defport = LDAPS_PORT;
+       else
+               defport = LDAP_PORT;
 
        TAILQ_FOREACH(h, &idm->idm_addr, next) {
                 switch (h->ss.ss_family) {
                 case AF_INET:
                         sa_in = (struct sockaddr_in *)&h->ss;
                         if (ntohs(sa_in->sin_port) == 0)
-                                sa_in->sin_port = htons(LDAP_PORT);
+                                sa_in->sin_port = htons(defport);
                         idm->idm_state = STATE_DNS_DONE;
                         break;
                 case AF_INET6:
                         sa_in6 = (struct sockaddr_in6 *)&h->ss;
                         if (ntohs(sa_in6->sin6_port) == 0)
-                                sa_in6->sin6_port = htons(LDAP_PORT);
+                                sa_in6->sin6_port = htons(defport);
                         idm->idm_state = STATE_DNS_DONE;
                         break;
                 default:
@@ -583,6 +591,39 @@ client_try_idm(struct env *env, struct i
        where = "connect";
        if ((al = client_aldap_open(&idm->idm_addr)) == NULL)
                return (-1);
+
+       if (idm->idm_flags & F_STARTTLS) {
+               log_debug("requesting starttls");
+               where = "starttls";
+               if (aldap_req_starttls(al) == -1)
+                       goto bad;
+
+               where = "parsing";
+               if ((m = aldap_parse(al)) == NULL)
+                       goto bad;
+               where = "verifying msgid";
+               if (al->msgid != m->msgid) {
+                       aldap_freemsg(m);
+                       goto bad;
+               }
+               where = "starttls result";
+               if (aldap_get_resultcode(m) != LDAP_SUCCESS) {
+                       aldap_freemsg(m);
+                       goto bad;
+               }
+               aldap_freemsg(m);
+       }
+
+       if (idm->idm_flags & (F_STARTTLS | F_SSL)) {
+               log_debug("starting tls");
+               where = "enabling tls";
+               if (aldap_tls(al, idm->idm_tls_config, idm->idm_name) < 0) {
+                       const char *err;
+                       aldap_get_errno(al, &err);
+                       log_debug("tls failed: %s", err);
+                       goto bad;
+               }
+       }
 
        if (idm->idm_flags & F_NEEDAUTH) {
                where = "binding";
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/parse.y,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 parse.y
--- parse.y     5 Jan 2017 13:53:10 -0000       1.21
+++ parse.y     24 Jan 2017 03:27:47 -0000
@@ -101,11 +101,12 @@ typedef struct {
 %token SERVER FILTER ATTRIBUTE BASEDN BINDDN GROUPDN BINDCRED MAPS CHANGE 
DOMAIN PROVIDE
 %token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL
 %token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP
-%token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS
+%token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS LDAPS TLS CAFILE
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.number>      opcode attribute
-%type  <v.string>      port
+%type  <v.number>      port
+%type  <v.number>      ssl
 
 %%
 
@@ -157,8 +158,28 @@ varset             : STRING '=' STRING                     
{
                }
                ;
 
-port           : /* empty */   { $$ = NULL; }
-               | PORT STRING   { $$ = $2; }
+port           : PORT STRING                           {
+                       struct servent *servent;
+
+                       servent = getservbyname($2, "tcp");
+                       if (servent == NULL) {
+                               yyerror("port %s is invalid", $2);
+                               free($2);
+                               YYERROR;
+                       }
+                       $$ = ntohs(servent->s_port);
+                       free($2);
+               }
+               | PORT NUMBER                           {
+                       if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+                               yyerror("invalid port: %lld", $2);
+                               YYERROR;
+                       }
+                       $$ = $2;
+               }
+               | /* empty */                           {
+                       $$ = 0;
+               }
                ;
 
 opcode         : GROUP                                 { $$ = 0; }
@@ -268,7 +289,12 @@ diropt             : BINDDN STRING                         
{
                }
                ;
 
-directory      : DIRECTORY STRING port {
+ssl            : /* empty */                           { $$ = 0; }
+               | LDAPS                                 { $$ = F_SSL; }
+               | TLS                                   { $$ = F_STARTTLS; }
+               ;
+
+directory      : DIRECTORY STRING port ssl {
                        if ((idm = calloc(1, sizeof(*idm))) == NULL)
                                fatal(NULL);
                        idm->idm_id = conf->sc_maxid++;
@@ -280,8 +306,53 @@ directory  : DIRECTORY STRING port {
                                free($2);
                                YYERROR;
                        }
-
                        free($2);
+
+                       idm->idm_port = $3;
+
+                       if ($4 != 0) {
+                               if (tls_init()) {
+                                       yyerror("tls init failed");
+                                       YYERROR;
+                               }
+
+                               if (conf->sc_cert_data == NULL) {
+                                       conf->sc_cert_data =
+                                           tls_load_file(conf->sc_cafile,
+                                           &conf->sc_cert_len, NULL);
+                                       if (conf->sc_cert_data == NULL) {
+                                               yyerror("loading CA bundle "
+                                                   "failed");
+                                               YYERROR;
+                                       }
+                               }
+
+                               idm->idm_flags |= $4;
+                               idm->idm_tls_config = tls_config_new();
+                               if (idm->idm_tls_config == NULL) {
+                                       yyerror("tls config failed");
+                                       YYERROR;
+                               }
+
+                               tls_config_set_protocols(idm->idm_tls_config,
+                                   TLS_PROTOCOLS_ALL);
+                               if (tls_config_set_ciphers(idm->idm_tls_config,
+                                   "compat")) {
+                                       yyerror("tls set ciphers failed");
+                                       tls_config_free(idm->idm_tls_config);
+                                       idm->idm_tls_config = NULL;
+                                       YYERROR;
+                               }
+
+                               if (tls_config_set_ca_mem(idm->idm_tls_config,
+                                   conf->sc_cert_data, conf->sc_cert_len)) {
+                                       yyerror("tls set CA bundle failed");
+                                       tls_config_free(idm->idm_tls_config);
+                                       idm->idm_tls_config = NULL;
+                                       YYERROR;
+                               }
+                       }
+
                } '{' optnl diropts '}'                 {
                        TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
                        idm = NULL;
@@ -324,6 +395,10 @@ main               : INTERVAL NUMBER                       
{
                        }
                        free($3);
                }
+               | CAFILE STRING                         {
+                       free(conf->sc_cafile);
+                       conf->sc_cafile = $2;
+               } 
                ;
 
 diropts                : diropts diropt nl
@@ -368,6 +443,7 @@ lookup(char *s)
                { "basedn",             BASEDN },
                { "bindcred",           BINDCRED },
                { "binddn",             BINDDN },
+               { "cafile",             CAFILE },
                { "change",             CHANGE },
                { "class",              CLASS },
                { "directory",          DIRECTORY },
@@ -386,6 +462,7 @@ lookup(char *s)
                { "home",               HOME },
                { "include",            INCLUDE },
                { "interval",           INTERVAL },
+               { "ldaps",              LDAPS },
                { "list",               LIST },
                { "map",                MAP },
                { "maps",               MAPS },
@@ -395,6 +472,7 @@ lookup(char *s)
                { "provide",            PROVIDE },
                { "server",             SERVER },
                { "shell",              SHELL },
+               { "tls",                TLS },
                { "to",                 TO },
                { "uid",                UID },
                { "user",               USER },
@@ -731,6 +809,7 @@ parse_config(struct env *x_conf, const c
        TAILQ_INIT(&conf->sc_idms);
        conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
        conf->sc_conf_tv.tv_usec = 0;
+       conf->sc_cafile = strdup(YPLDAP_CERT_FILE);
 
        errors = 0;
 
Index: ypldap.h
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ypldap.h,v
retrieving revision 1.18
diff -u -p -u -p -r1.18 ypldap.h
--- ypldap.h    20 Jan 2017 12:39:36 -0000      1.18
+++ ypldap.h    24 Jan 2017 03:27:47 -0000
@@ -17,9 +17,11 @@
  */
 
 #include <imsg.h>
+#include <tls.h>
 
 #define YPLDAP_USER            "_ypldap"
 #define YPLDAP_CONF_FILE       "/etc/ypldap.conf"
+#define YPLDAP_CERT_FILE       "/etc/ssl/cert.pem"
 #define DEFAULT_INTERVAL       600
 #define LINE_WIDTH             1024
 #define FILTER_WIDTH           128
@@ -91,6 +93,7 @@ struct idm {
 #define F_SSL                           0x00100000
 #define F_CONFIGURING                   0x00200000
 #define F_NEEDAUTH                      0x00400000
+#define F_STARTTLS                      0x00800000
 #define F_FIXED_ATTR(n)                         (1<<n)
 #define F_LIST(n)                        (1<<n)
        enum client_state                idm_state;
@@ -124,10 +127,7 @@ struct idm {
 #define ATTR_GR_MAX                     14
        char                             idm_attrs[14][ATTR_WIDTH];
        struct env                      *idm_env;
-       struct event                     idm_ev;
-#ifdef SSL
-       struct ssl                      *idm_ssl;
-#endif
+       struct tls_config               *idm_tls_config;
 };
 
 struct idm_req {
@@ -164,6 +164,7 @@ struct env {
        char                             sc_domainname[HOST_NAME_MAX+1];
        struct timeval                   sc_conf_tv;
        struct event                     sc_conf_ev;
+       char                            *sc_cafile;
        TAILQ_HEAD(idm_list, idm)        sc_idms;
        struct imsgev                   *sc_iev;
        struct imsgev                   *sc_iev_dns;
@@ -178,6 +179,9 @@ struct env {
        size_t                           sc_group_line_len;
        char                            *sc_user_lines;
        char                            *sc_group_lines;
+
+       uint8_t                         *sc_cert_data;
+       size_t                           sc_cert_len;
 
        struct yp_data                  *sc_yp;
 

Reply via email to