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;