ypldap currently can't do SRV lookups to locate the directory servers for
a domain, which makes it slightly harder than it should be to connect it to
an AD domain. The diff below lets you specify a directory service like this:
directory "eait.uq.edu.au" srv {
...
}
which will make ypldap do an SRV lookup every time it refreshes, then try
each of the resulting hostnames in order (according to priority and weighting)
until one works.
Index: ldapclient.c
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ldapclient.c,v
retrieving revision 1.36
diff -u -p -u -p -r1.36 ldapclient.c
--- ldapclient.c 10 Apr 2016 09:59:21 -0000 1.36
+++ ldapclient.c 1 May 2016 23:47:33 -0000
@@ -54,7 +54,9 @@ int client_search_idm(struct env *, stru
char **, char *, int, int, enum imsg_type);
int client_try_idm(struct env *, struct idm *);
int client_addr_init(struct idm *);
+int client_srv_next(struct idm *);
int client_addr_free(struct idm *);
+int client_srv_free(struct idm *);
struct aldap *client_aldap_open(struct ypldap_addr_list *);
@@ -122,6 +124,37 @@ client_addr_init(struct idm *idm)
}
int
+client_srv_next(struct idm *idm)
+{
+ struct ypldap_srv *s, *sel;
+ int bestprio, weight;
+
+ bestprio = 0xffff;
+ weight = 0;
+ sel = NULL;
+ TAILQ_FOREACH(s, &idm->idm_srv, next) {
+ if (s->priority < bestprio) {
+ bestprio = s->priority;
+ weight = s->weight;
+ sel = s;
+ } else if (s->priority == bestprio) {
+ weight += s->weight;
+ if (arc4random_uniform(weight) < s->weight)
+ sel = s;
+ }
+ }
+
+ if (sel == NULL)
+ fatalx("couldn't pick a srv record to try next?");
+
+ client_addr_free(idm);
+
+ TAILQ_REMOVE(&idm->idm_srv, sel, next);
+ strlcpy(idm->idm_srv_name, sel->hostname, sizeof(idm->idm_srv_name));
+ return (0);
+}
+
+int
client_addr_free(struct idm *idm)
{
struct ypldap_addr *h;
@@ -131,7 +164,19 @@ client_addr_free(struct idm *idm)
TAILQ_REMOVE(&idm->idm_addr, h, next);
free(h);
}
+ return (0);
+}
+
+int
+client_srv_free(struct idm *idm)
+{
+ struct ypldap_srv *s;
+ while (!TAILQ_EMPTY(&idm->idm_srv)) {
+ s = TAILQ_FIRST(&idm->idm_srv);
+ TAILQ_REMOVE(&idm->idm_srv, s, next);
+ free(s);
+ }
return (0);
}
@@ -155,6 +200,7 @@ client_dispatch_dns(int fd, short events
u_int16_t dlen;
u_char *data;
struct ypldap_addr *h;
+ struct ypldap_srv *s;
int n, wait_cnt = 0;
struct idm *idm;
int shut = 0;
@@ -186,26 +232,29 @@ client_dispatch_dns(int fd, short events
if (n == 0)
break;
- switch (imsg.hdr.type) {
- case IMSG_HOST_DNS:
- TAILQ_FOREACH(idm, &env->sc_idms, idm_entry)
- if (idm->idm_id == imsg.hdr.peerid)
- break;
- if (idm == NULL) {
- log_warnx("IMSG_HOST_DNS with invalid peerID");
- break;
- }
- if (!TAILQ_EMPTY(&idm->idm_addr)) {
- log_warnx("IMSG_HOST_DNS but addrs set!");
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry)
+ if (idm->idm_id == imsg.hdr.peerid)
break;
- }
+ if (idm == NULL) {
+ log_warnx("dns message with invalid peerID");
+ imsg_free(&imsg);
+ continue;
+ }
- dlen = imsg.hdr.len - IMSG_HEADER_SIZE;
- if (dlen == 0) { /* no data -> temp error */
- idm->idm_state = STATE_DNS_TEMPFAIL;
- break;
- }
+ if (!TAILQ_EMPTY(&idm->idm_addr)) {
+ log_warnx("got dns message, but we already have addrs");
+ break;
+ }
+
+ dlen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (dlen == 0) { /* no data -> temp error */
+ idm->idm_state = STATE_DNS_TEMPFAIL;
+ imsg_free(&imsg);
+ continue;
+ }
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DNS:
data = (u_char *)imsg.data;
while (dlen >= sizeof(struct sockaddr_storage)) {
if ((h = calloc(1, sizeof(*h))) == NULL)
@@ -220,8 +269,25 @@ client_dispatch_dns(int fd, short events
fatalx("IMSG_HOST_DNS: dlen != 0");
client_addr_init(idm);
+ break;
+ case IMSG_SRV_DNS:
+ data = (u_char *)imsg.data;
+ while (dlen >= sizeof(struct ypldap_srv)) {
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+ memcpy(s, data, sizeof(*s));
+ TAILQ_INSERT_HEAD(&idm->idm_srv, s, next);
+
+ data += sizeof(*s);
+ dlen -= sizeof(*s);
+ }
+ if (dlen != 0)
+ fatalx("IMSG_SRV_DNS: dlen != 0");
+
+ idm->idm_state = STATE_DNS_SRV;
break;
+
default:
break;
}
@@ -229,11 +295,21 @@ client_dispatch_dns(int fd, short events
}
TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
- if (client_try_idm(env, idm) == -1)
- idm->idm_state = STATE_LDAP_FAIL;
+ if (idm->idm_state == STATE_DNS_DONE) {
+ if (client_try_idm(env, idm) != 0)
+ idm->idm_state = STATE_LDAP_FAIL;
+ }
- if (idm->idm_state < STATE_LDAP_DONE)
+ if (idm->idm_state < STATE_LDAP_DONE) {
wait_cnt++;
+ if (!TAILQ_EMPTY(&idm->idm_srv)) {
+ client_srv_next(idm);
+ imsg_compose_event(env->sc_iev_dns,
+ IMSG_HOST_DNS, idm->idm_id, 0, -1,
+ idm->idm_srv_name,
+ strlen(idm->idm_srv_name) + 1);
+ }
+ }
}
if (wait_cnt == 0)
imsg_compose_event(env->sc_iev, IMSG_END_UPDATE, 0, 0, -1,
@@ -661,6 +737,7 @@ client_periodic_update(int fd, short eve
idm->idm_state = STATE_NONE;
client_addr_free(idm);
+ client_srv_free(idm);
}
if (fail_cnt > 0) {
log_debug("trash the update");
@@ -677,6 +754,7 @@ client_configure(struct env *env)
struct timeval tv;
struct idm *idm;
u_int16_t dlen;
+ int msg;
log_debug("connecting to directories");
@@ -685,7 +763,14 @@ client_configure(struct env *env)
/* Start the DNS lookups */
TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
dlen = strlen(idm->idm_name) + 1;
- imsg_compose_event(env->sc_iev_dns, IMSG_HOST_DNS, idm->idm_id,
+ if (idm->idm_flags & F_SRV_LOOKUP) {
+ msg = IMSG_SRV_DNS;
+ } else {
+ msg = IMSG_HOST_DNS;
+ strlcpy(idm->idm_srv_name, idm->idm_name,
+ sizeof(idm->idm_srv_name));
+ }
+ imsg_compose_event(env->sc_iev_dns, msg, idm->idm_id,
0, -1, idm->idm_name, dlen);
}
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/parse.y,v
retrieving revision 1.18
diff -u -p -u -p -r1.18 parse.y
--- parse.y 16 Jan 2015 06:40:22 -0000 1.18
+++ parse.y 1 May 2016 23:47:34 -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 SRV
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.number> opcode attribute
%type <v.string> port
+%type <v.number> srv
%%
@@ -259,8 +260,11 @@ diropt : BINDDN STRING
{
free($5);
}
;
+srv : /* empty */ { $$ = 0; }
+ | SRV { $$ = F_SRV_LOOKUP; }
+ ;
-directory : DIRECTORY STRING port {
+directory : DIRECTORY STRING port srv {
if ((idm = calloc(1, sizeof(*idm))) == NULL)
fatal(NULL);
idm->idm_id = conf->sc_maxid++;
@@ -274,6 +278,10 @@ directory : DIRECTORY STRING port {
}
free($2);
+
+ if ($4 != 0)
+ idm->idm_flags |= F_SRV_LOOKUP;
+
} '{' optnl diropts '}' {
TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
idm = NULL;
@@ -387,6 +395,7 @@ lookup(char *s)
{ "provide", PROVIDE },
{ "server", SERVER },
{ "shell", SHELL },
+ { "srv", SRV },
{ "to", TO },
{ "uid", UID },
{ "user", USER },
Index: ypldap.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ypldap.conf.5,v
retrieving revision 1.19
diff -u -p -u -p -r1.19 ypldap.conf.5
--- ypldap.conf.5 30 Apr 2012 11:28:25 -0000 1.19
+++ ypldap.conf.5 1 May 2016 23:47:34 -0000
@@ -83,12 +83,19 @@ convert LDAP entries to
and
.Xr group 5
lines.
-A directory declaration is of the following form:
+.Bl -tag -width Ds
+.It Ic directory Ar server Oo Ic srv Oc Ic
+Specify a directory server to connect to.
.Bd -literal -offset indent
-directory "some.host" {
+directory "some.host" srv {
# directives
}
+.El
.Ed
+.Pp
+If 'srv' is specified, the daemon will use DNS SRV records to identify the
+directory servers for the specified domain, rather than performing a normal
+hostname lookup.
.Pp
Valid directives for directories are:
.Bl -tag -width Ds
Index: ypldap.h
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ypldap.h,v
retrieving revision 1.17
diff -u -p -u -p -r1.17 ypldap.h
--- ypldap.h 10 Apr 2016 09:59:21 -0000 1.17
+++ ypldap.h 1 May 2016 23:47:34 -0000
@@ -17,6 +17,7 @@
*/
#include <imsg.h>
+#include <netdb.h>
#define YPLDAP_USER "_ypldap"
#define YPLDAP_CONF_FILE "/etc/ypldap.conf"
@@ -37,7 +38,8 @@ enum imsg_type {
IMSG_TRASH_UPDATE,
IMSG_PW_ENTRY,
IMSG_GRP_ENTRY,
- IMSG_HOST_DNS
+ IMSG_HOST_DNS,
+ IMSG_SRV_DNS
};
struct ypldap_addr {
@@ -46,6 +48,15 @@ struct ypldap_addr {
};
TAILQ_HEAD(ypldap_addr_list, ypldap_addr);
+struct ypldap_srv {
+ TAILQ_ENTRY(ypldap_srv) next;
+ char hostname[NI_MAXHOST];
+ in_port_t port;
+ uint16_t priority;
+ uint16_t weight;
+};
+TAILQ_HEAD(ypldap_srv_list, ypldap_srv);
+
enum {
PROC_MAIN,
PROC_CLIENT
@@ -69,7 +80,7 @@ struct groupent {
enum client_state {
STATE_NONE,
- STATE_DNS_INPROGRESS,
+ STATE_DNS_SRV,
STATE_DNS_TEMPFAIL,
STATE_DNS_DONE,
STATE_LDAP_FAIL,
@@ -86,12 +97,15 @@ struct idm {
#define F_SSL 0x00100000
#define F_CONFIGURING 0x00200000
#define F_NEEDAUTH 0x00400000
+#define F_SRV_LOOKUP 0x00800000
#define F_FIXED_ATTR(n) (1<<n)
#define F_LIST(n) (1<<n)
enum client_state idm_state;
u_int32_t idm_flags; /* lower 20 reserved */
u_int32_t idm_list;
struct ypldap_addr_list idm_addr;
+ struct ypldap_srv_list idm_srv;
+ char idm_srv_name[HOST_NAME_MAX+1];
in_port_t idm_port;
char idm_binddn[LINE_WIDTH];
char idm_bindcred[LINE_WIDTH];
Index: ypldap_dns.c
===================================================================
RCS file: /cvs/src/usr.sbin/ypldap/ypldap_dns.c,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 ypldap_dns.c
--- ypldap_dns.c 10 Apr 2016 09:59:21 -0000 1.11
+++ ypldap_dns.c 1 May 2016 23:47:34 -0000
@@ -47,6 +47,7 @@ void dns_dispatch_imsg(int, short, void
void dns_sig_handler(int, short, void *);
void dns_shutdown(void);
int host_dns(const char *, struct ypldap_addr_list *);
+int srv_dns(const char *, struct ypldap_srv_list *);
void
dns_sig_handler(int sig, short event, void *p)
@@ -132,6 +133,8 @@ dns_dispatch_imsg(int fd, short events,
char *name;
struct ypldap_addr_list hn = TAILQ_HEAD_INITIALIZER(hn);
struct ypldap_addr *h;
+ struct ypldap_srv_list sn = TAILQ_HEAD_INITIALIZER(sn);
+ struct ypldap_srv *s;
struct ibuf *buf;
struct env *env = p;
struct imsgev *iev = env->sc_iev;
@@ -161,15 +164,16 @@ dns_dispatch_imsg(int fd, short events,
if (n == 0)
break;
+ name = imsg.data;
+ if (imsg.hdr.len < 1 + IMSG_HEADER_SIZE)
+ fatalx("invalid dns message received");
+ imsg.hdr.len -= 1 + IMSG_HEADER_SIZE;
+ if (name[imsg.hdr.len] != '\0' ||
+ strlen(name) != imsg.hdr.len)
+ fatalx("invalid dns message received");
+
switch (imsg.hdr.type) {
case IMSG_HOST_DNS:
- name = imsg.data;
- if (imsg.hdr.len < 1 + IMSG_HEADER_SIZE)
- fatalx("invalid IMSG_HOST_DNS received");
- imsg.hdr.len -= 1 + IMSG_HEADER_SIZE;
- if (name[imsg.hdr.len] != '\0' ||
- strlen(name) != imsg.hdr.len)
- fatalx("invalid IMSG_HOST_DNS received");
if ((cnt = host_dns(name, &hn)) == -1)
break;
buf = imsg_create(ibuf, IMSG_HOST_DNS,
@@ -188,6 +192,29 @@ dns_dispatch_imsg(int fd, short events,
imsg_close(ibuf, buf);
break;
+
+ case IMSG_SRV_DNS:
+ if ((cnt = srv_dns(name, &sn)) == -1)
+ break;
+
+ buf = imsg_create(ibuf, IMSG_SRV_DNS,
+ imsg.hdr.peerid, 0,
+ cnt * (NI_MAXHOST + sizeof(uint16_t)));
+ if (buf == NULL)
+ break;
+
+ if (cnt > 0) {
+ while (!TAILQ_EMPTY(&sn)) {
+ s = TAILQ_FIRST(&sn);
+ TAILQ_REMOVE(&sn, s, next);
+ imsg_add(buf, s, sizeof(*s));
+ free(s);
+ }
+ }
+
+ imsg_close(ibuf, buf);
+ break;
+
default:
break;
}
@@ -248,5 +275,62 @@ host_dns(const char *s, struct ypldap_ad
cnt++;
}
freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+srv_dns(const char *s, struct ypldap_srv_list *sn)
+{
+ struct rrsetinfo *res;
+ struct rdatainfo *rrdata;
+ struct ypldap_srv *srv;
+ uint16_t *sdata;
+ unsigned int i;
+ int error, cnt = 0;
+ char rr[NI_MAXHOST];
+
+ if (snprintf(rr, NI_MAXHOST, "_ldap._tcp.%s", s) >= NI_MAXHOST) {
+ log_warnx("srv domain name %s is too long", s);
+ return (-1);
+ }
+
+ error = getrrsetbyname(rr, C_IN, T_SRV, 0, &res);
+ switch (error) {
+ case ERRSET_NOMEMORY:
+ case ERRSET_NODATA:
+ case ERRSET_NONAME:
+ return (0);
+ case 0:
+ break;
+ default:
+ log_warnx("could not parse \"%s\"", s);
+ return (-1);
+ }
+
+ for (i = 0; i < res->rri_nrdatas; i++) {
+ rrdata = &res->rri_rdatas[i];
+ if (rrdata->rdi_length < 7) {
+ log_warnx("short srv record for \"%s\"", s);
+ continue;
+ }
+
+ srv = calloc(1, sizeof(*srv));
+ if (srv == NULL)
+ break;
+
+ sdata = (uint16_t *)rrdata->rdi_data;
+ srv->priority = ntohs(sdata[0]);
+ srv->weight = ntohs(sdata[1]);
+ srv->port = ntohs(sdata[2]);
+
+ dn_expand(rrdata->rdi_data,
+ rrdata->rdi_data + rrdata->rdi_length,
+ rrdata->rdi_data + (3 * sizeof(uint16_t)),
+ srv->hostname, NI_MAXHOST);
+
+ TAILQ_INSERT_HEAD(sn, srv, next);
+ cnt++;
+ }
+ freerrset(res);
return (cnt);
}