ldap2zone/0000755000175000017500000000000011570145234012300 5ustar benoitbenoitldap2zone/Makefile0000644000175000017500000000013111570144774013743 0ustar benoitbenoitall: ldap2zone ldap2zone: ldap2zone.c gcc -Wall ldap2zone.c -o ldap2zone -lldap -llber ldap2zone/ldap2bind.10000644000175000017500000000213711564423735014234 0ustar benoitbenoit.TH LDAP2BIND 1 .SH NAME ldap2bind \- update bind's DNS zones from LDAP .SH SYNOPSIS .B ldap2bind .SH DESCRIPTION The .BR ldap2bind program converts DNS zones from LDAP to bind zone configuration files and reloads these zones. It uses .BR ldap2zone (1) to do the conversion. .PP The script takes no options, but some variables are defined and can be modified in .I /etc/defaults/ldap2zone. .PP Usually .BR ldap2bind is called from a cron job that updates the zones regularly from LDAP. .SH Notes The file .I named.conf.ldap2zone must exist in bind's configuration directory and has to be included in bind's configuration. This is usually done by including it in .I named.conf.local. .SH "EXIT STATUS" .BR ldap2bind returns 0 on success, 1 on failures. .SH FILES .nf .ta \w'/etc/bind/named.conf.ldap2zone\ 'u \fI/etc/default/ldap2zone\fR configuration variables \fI/etc/bind/named.conf.ldap2zone\fR zones extracted from LDAP .SH AUTHOR This manual page has been written by Andreas B. Mundt for the Debian Project (but may be used by others). .SH "SEE ALSO" .BR ldap2zone (1). ldap2zone/changelog0000644000175000017500000000014411520367707014157 0ustar benoitbenoitldap2zone-0.1 2005-04-24 This is the very first release and consists of just the file ldap2zone.c ldap2zone/ldap2zone.10000644000175000017500000000141611346132401014253 0ustar benoitbenoit.TH ldap2zone 1 .SH NAME ldap2zone \- convert zones saved inside the LDAP to bind9 zone files .SH SYNOPSIS .PP .B ldap2zone zone-name LDAP-URL default-ttl [serial] .SH DESCRIPTION Convert zones saved in LDAP to ordinary zones files via .B ldap2zone in order to avoid dependencies from LDAP to bind. .SH OPTIONS .TP .BR zone-name name of the zone to be extracted from LDAP. This can be a forward or a reverse zone. .TP .BR LDAP-URL URL to access the LDAP server in the format ldap[s]://server-ip/base. .TP .BR default-ttl Zone TTL in seconds .TP .BR serial Zone serial number. .SH "RETURN VALUE" The script return zero values on success. .SH EXAMPLES .B ldap2zone example.net ldap://ldap.example.net/dc=example,dc=net 1200 200708291528 .SH AUTHOR Cajus Pollmeier ldap2zone/ldap2zone.c0000644000175000017500000002327111570145213014344 0ustar benoitbenoit/* * Copyright (C) 2004, 2005 Stig Venaas * $Id: ldap2zone.c,v 0.1 2005/04/23 21:30:12 venaas Exp $ * * 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. */ #include #include #include #include #include #include #include struct string { void *data; size_t len; }; struct assstack_entry { struct string key; struct string val; struct assstack_entry *next; }; struct assstack_entry *assstack_find(struct assstack_entry *stack, struct string *key) { for (; stack; stack = stack->next) if (stack->key.len == key->len && !memcmp(stack->key.data, key->data, key->len)) return stack; return NULL; } void assstack_push(struct assstack_entry **stack, struct assstack_entry *item) { item->next = *stack; *stack = item; } void assstack_insertbottom(struct assstack_entry **stack, struct assstack_entry *item) { struct assstack_entry *p; item->next = NULL; if (!*stack) { *stack = item; return; } /* find end, should keep track of end somewhere */ /* really a queue, not a stack */ p = *stack; while (p->next) p = p->next; p->next = item; } void printsoa(struct string *soa) { char *s; int i; s = (char *)soa->data; i = 0; while (i < soa->len) { putchar(s[i]); if (s[i++] == ' ') break; } while (i < soa->len) { putchar(s[i]); if (s[i++] == ' ') break; } printf("(\n\t\t\t\t"); while (i < soa->len) { putchar(s[i]); if (s[i++] == ' ') break; } printf("; Serialnumber\n\t\t\t\t"); while (i < soa->len) { if (s[i] == ' ') break; putchar(s[i++]); } i++; printf("\t; Refresh\n\t\t\t\t"); while (i < soa->len) { if (s[i] == ' ') break; putchar(s[i++]); } i++; printf("\t; Retry\n\t\t\t\t"); while (i < soa->len) { if (s[i] == ' ') break; putchar(s[i++]); } i++; printf("\t; Expire\n\t\t\t\t"); while (i < soa->len) { putchar(s[i++]); } printf(" )\t; Minimum TTL\n"); } void printrrs(char *defaultttl, struct assstack_entry *item) { struct assstack_entry *stack; char *s; int first; int i; char *ttl, *type; int top; s = (char *)item->key.data; if (item->key.len == 1 && *s == '@') { top = 1; printf("@\t"); } else { top = 0; for (i = 0; i < item->key.len; i++) putchar(s[i]); if (item->key.len < 8) putchar('\t'); putchar('\t'); } first = 1; for (stack = (struct assstack_entry *) item->val.data; stack; stack = stack->next) { ttl = (char *)stack->key.data; s = strchr(ttl, ' '); *s++ = '\0'; type = s; if (first) first = 0; else printf("\t\t"); if (strcmp(defaultttl, ttl)) printf("%s", ttl); putchar('\t'); if (top) { top = 0; printf("IN\t%s\t", type); /* Should always be SOA here */ if (!strcmp(type, "SOA")) { printsoa(&stack->val); continue; } } else printf("%s\t", type); s = (char *)stack->val.data; for (i = 0; i < stack->val.len; i++) putchar(s[i]); putchar('\n'); } } void print_zone(char *defaultttl, struct assstack_entry *stack) { printf("$TTL %s\n", defaultttl); for (; stack; stack = stack->next) printrrs(defaultttl, stack); }; void usage(char *name) { fprintf(stderr, "Usage: %s zone-name LDAP-URL default-ttl [serial]\n", basename(name)); exit(1); }; void err(char *name, char *msg) { fprintf(stderr, "%s: %s\n", name, msg); exit(1); }; int putrr(struct assstack_entry **stack, struct berval *name, char *type, char *ttl, struct berval *val) { struct string key; struct assstack_entry *rr, *rrdata; /* Do nothing if name or value have 0 length */ if (!name->bv_len || !val->bv_len) return 0; /* see if already have an entry for this name */ key.len = name->bv_len; key.data = name->bv_val; rr = assstack_find(*stack, &key); if (!rr) { /* Not found, create and push new entry */ rr = (struct assstack_entry *) malloc(sizeof(struct assstack_entry)); if (!rr) return -1; rr->key.len = name->bv_len; rr->key.data = (void *) malloc(rr->key.len); if (!rr->key.data) { free(rr); return -1; } memcpy(rr->key.data, name->bv_val, name->bv_len); rr->val.len = sizeof(void *); rr->val.data = NULL; if (name->bv_len == 1 && *(char *)name->bv_val == '@') assstack_push(stack, rr); else assstack_insertbottom(stack, rr); } rrdata = (struct assstack_entry *) malloc(sizeof(struct assstack_entry)); if (!rrdata) { free(rr->key.data); free(rr); return -1; } rrdata->key.len = strlen(type) + strlen(ttl) + 1; rrdata->key.data = (void *) malloc(rrdata->key.len); if (!rrdata->key.data) { free(rrdata); free(rr->key.data); free(rr); return -1; } sprintf((char *)rrdata->key.data, "%s %s", ttl, type); rrdata->val.len = val->bv_len; rrdata->val.data = (void *) malloc(val->bv_len); if (!rrdata->val.data) { free(rrdata->key.data); free(rrdata); free(rr->key.data); free(rr); return -1; } memcpy(rrdata->val.data, val->bv_val, val->bv_len); if (!strcmp(type, "SOA")) assstack_push((struct assstack_entry **) &(rr->val.data), rrdata); else assstack_insertbottom((struct assstack_entry **) &(rr->val.data), rrdata); return 0; } int main(int argc, char **argv) { char *s, *hostporturl, *base = NULL; char *ttl, *defaultttl; LDAP *ld; char *fltr = NULL; LDAPMessage *res, *e; char *a, *serial; struct berval **vals, **names, **ttlvals, **soavals; char type[64]; BerElement *ptr; int i, j, rc, msgid,msgidp,sizelimit = 0; struct assstack_entry *zone = NULL; LDAPControl **server = NULL, **client = NULL; struct timeval timeout; if (argc < 4 || argc > 5) usage(argv[0]); hostporturl = argv[2]; if (hostporturl != strstr( hostporturl, "ldap")) err(argv[0], "Not an LDAP URL"); s = strchr(hostporturl, ':'); if (!s || strlen(s) < 3 || s[1] != '/' || s[2] != '/') err(argv[0], "Not an LDAP URL"); s = strchr(s+3, '/'); if (s) { *s++ = '\0'; base = s; s = strchr(base, '?'); if (s) err(argv[0], "LDAP URL can only contain host, port and base"); } defaultttl = argv[3]; rc = ldap_initialize(&ld, hostporturl); if (rc != LDAP_SUCCESS) err(argv[0], "ldap_initialize() failed"); if (argc == 5) { /* serial number specified, check if different from one in SOA */ fltr = (char *)malloc(strlen(argv[1]) + strlen("(&(relativeDomainName=@)(zoneName=))") + 1); sprintf(fltr, "(&(relativeDomainName=@)(zoneName=%s))", argv[1]); msgid = ldap_search_ext(ld, base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0, server, client, NULL, 0, &msgidp); if (msgid == -1) err(argv[0], "ldap_search() failed"); while ((rc = ldap_result(ld, msgid, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) { /* not supporting continuation references at present */ if (rc != LDAP_RES_SEARCH_ENTRY) err(argv[0], "ldap_result() returned cont.ref? Exiting"); /* only one entry per result message */ e = ldap_first_entry(ld, res); if (e == NULL) { ldap_msgfree(res); err(argv[0], "ldap_first_entry() failed"); } soavals = ldap_get_values_len(ld, e, "SOARecord"); if (soavals) break; } ldap_msgfree(res); if (!soavals) { err(argv[0], "No SOA Record found"); } /* We have a SOA, compare serial numbers */ /* Only checking first value, should be only one */ /* chat changed to struct see where is problem */ s = strchr(soavals[0]->bv_val, ' '); s++; s = strchr(s, ' '); s++; serial = s; s = strchr(s, ' '); *s = '\0'; if (!strcmp(serial, argv[4])) { ldap_value_free_len(soavals); err(argv[0], "serial numbers match"); } ldap_value_free_len(soavals); } if (!fltr) fltr = (char *)malloc(strlen(argv[1]) + strlen("(zoneName=)") + 1); if (!fltr) err(argv[0], "Malloc failed"); sprintf(fltr, "(zoneName=%s)", argv[1]); msgid = ldap_search_ext(ld, base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0, server, client, 0, 0, &msgidp); if (msgid == -1) err(argv[0], "ldap_search() failed"); while ((rc = ldap_result(ld, msgidp, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) { /* not supporting continuation references at present */ if (rc != LDAP_RES_SEARCH_ENTRY) err(argv[0], "ldap_result() returned cont.ref? Exiting"); /* only one entry per result message */ e = ldap_first_entry(ld, res); if (e == NULL) { ldap_msgfree(res); err(argv[0], "ldap_first_entry() failed"); } names = ldap_get_values_len(ld, e, "relativeDomainName"); if (!names) continue; ttlvals = ldap_get_values_len(ld, e, "dNSTTL"); ttl = ttlvals ? ttlvals[0]->bv_val : defaultttl; for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) { char *s; for (s = a; *s; s++) *s = toupper(*s); s = strstr(a, "RECORD"); if ((s == NULL) || (s == a) || (s - a >= (signed int)sizeof(type))) { ldap_memfree(a); continue; } strncpy(type, a, s - a); type[s - a] = '\0'; vals = ldap_get_values_len(ld, e, a); if (vals) { for (i = 0; vals[i]; i++) for (j = 0; names[j]; j++) if (putrr(&zone, names[j], type, ttl, vals[i])) err(argv[0], "malloc failed"); ldap_value_free_len(vals); } ldap_memfree(a); } if (ptr) ber_free(ptr, 0); if (ttlvals) ldap_value_free_len(ttlvals); ldap_value_free_len(names); /* free this result */ ldap_msgfree(res); } /* free final result */ ldap_msgfree(res); print_zone(defaultttl, zone); return 0; } ldap2zone/dnszonehowto.html0000644000175000017500000002003611520367707015736 0ustar benoitbenoit How to use dnsZone with the BIND 9 sdb back-end

How to use dnsZone with the BIND 9 sdb back-end

Record types

The dnsZone class does not contain attributes for all known record types. The following types are missing: MF, MB, MG, MR, NULL, WKS, RP, AFSDB, X25, ISDN, RT, NSAP, NSAP-PTR, PX and GPOS. Anything defined after this document was written is obviously missing. If you want to add some record types that are defined by IANA, please define it similar to what I've done for the existing ones. The name should be {TYPE}Record, and OID should be 1.3.6.1.4.1.2428.20.1.value. For instance the RR type LOC has value 29, so attribute name should be LocRecord (casing shouldn't matter), and the OID is 1.3.6.1.4.1.2428.20.1.29. If you follow this, you know that it will be compatible with what I and others use, and I guarantee that the OIDs are unique.

The dnsZone class has attributes for some basic record types like A, SOA, etc. which are defined in the cosine schema and not in this schema. This means that your LDAP server must use both the cosine schema and this one. If you're not you should get an error from your LDAP server.

Example 1

Let's look at the following simple zone file.
@       3600    IN      SOA     ns.my-domain.com. hostmaster.my-domain.com. (
		                2001030201 3600 1800 604800 86400 )
		        NS      ns.my-domain.com.
		        NS      ns.other-domain.com.
		        MX      10 mail.my-domain.com.
		        MX      20 mail.other-domain.com.

my-hosta                A       10.10.10.10
                        MX	10 mail.my-domain.com.
		        MX      20 mail.other-domain.com.
www	 1800	 	CNAME	my-hosta.my-domain.com.
my-hostb 3600           A       10.10.10.11
                        MX	10 mail.my-domain.com.
		        MX      20 mail.other-domain.com.
This can be represented by the following LDIF file:
dn: relativeDomainName=@, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: @
zoneName: my-domain.com
dNSTTL: 3600
dNSClass: IN
sOARecord: ns.my-domain.com. hostmaster.my-domain.com. 2001030201 3600 1800 604800 86400
nSRecord: ns.my-domain.com.
nSRecord: ns.other-domain.com.
mXRecord: 10 mail.my-domain.com.
mXRecord: 20 mail.other-domain.com.

dn: relativeDomainName=my-hosta, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: my-hosta
zoneName: my-domain.com
dNSTTL: 86400
dNSClass: IN
aRecord: 10.10.10.10
mXRecord: 10 mail.my-domain.com.
mXRecord: 20 mail.other-domain.com.

dn: relativeDomainName=www, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: www
zoneName: my-domain.com
dNSTTL: 1800
dNSClass: IN
cNAMERecord: my-hosta.my-domain.com.

dn: relativeDomainName=my-hostb, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: my-hostb
zoneName: my-domain.com
dNSTTL: 3600
dNSClass: IN
aRecord: 10.10.10.11

dn: relativeDomainName=my-hostb + dNSTTL=86400, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: my-hostb
zoneName: my-domain.com
dNSTTL: 86400
dNSClass: IN
mXRecord: 10 mail.my-domain.com.
mXRecord: 20 mail.other-domain.com.

zoneName is the name of the zone, i.e. the name of the node in the zone that is highest up in the DNS tree. relativeDomainName is the name of the nodes relative to this, just like relative names in zone files dNSClass is not used by the sdb back-end, so you can leave it out if you like. The dNSTTL can also be left out, it will then default to the TTL specified in named.conf.

If you want RRs with the same name to have different TTLs (like my-hostb in the example), you will have to store it as multiple entries all including relativeDomainName=my-hostb. You will need at least as many entries as there are different TTLs. The main difficulty is to make sure that they all have unique DNs. The way I've chosen is to have a multi-valued RDN including dNSTTL, and put all RRs with the same TTL together. This is a bit ugly, but I think this is better than having one entry per RR. Let me know if you have other ideas.

When storing multiple zones you must make sure of course that the dn's are unique. This can easily be done by including the zoneName attribute in the DN. This can be done in several ways, one example would be relativeDomainName=www, zoneName=my-zone, dc=my-domain, dc=com.

Example 2

The schema is quite flexible as I'll try to show in the next two examples. If you have say the same MX records for the three hosts hosta, hostb and hostc, you might have one entry for each host and have the same mXRecord values in all the entries, or you can have one common entry with the mXRecords like this:
dn: relativeDomainName=hosta, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: hosta
relativeDomainName: hostb
relativeDomainName: hostc
zoneName: my-domain.com
mXRecord: 10 mail.my-domain.com.
mXRecord: 20 mail.other-domain.com.

Example 3

Assume that we have one web server with one IP address that is a virtual web server for hundreds of domains. Rather than having hundreds of similar entries, one for each zone, you might do as follows:
dn: relativeDomainName=www, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: www
zoneName: customerdomain1.com
zoneName: customerdomain2.com
...
zoneName: customerdomain999.com
aRecord: 10.10.10.10
mXRecord: 10 mail.my-domain.com.
mXRecord: 20 mail.other-domain.com.
If you have a bunch of zones that are identical, except for the zone name, you might combine all their entries like above. Sort of like using the same zone file for multiple zones.

Example 4

Let's have a look at PTR records for reverse lookups for IPv4 addresses. Say you want to have a reverse for the address 1.2.3.4. You then need a PTR record for 4.3.2.1.in-addr.arpa. Using normal zone files you would typically have a file for the zone 3.2.1.in-addr.arpa and inside that put say
4 PTR my-hostc.my-domain.com.
Using this back-end, you change named.conf like above, and use an LDAP entry like this:
dn: relativeDomainName=4, zoneName=3.2.1.in-addr.arpa, dc=my-domain, dc=com
objectClass: dNSZone
relativeDomainName: 4
zoneName: 3.2.1.in-addr.arpa
pTRRecord: my-hostc.my-domain.com.
Note that the DN can be whatever you like. If you use a DN like the above, you can use zoneName=3.2.1.in-addr.arpa, dc=my-domain, dc=com as the search base in the URL in named.conf.

Example 5

As a final example, you might try to search below ldap://ldap.venaas.no/dc=venaas,dc=com,o=DNS,dc=venaas,dc=no. There you will find the zone data for venaas.com. You can also browse my directory data using web2ldap. The master server for venaas.com uses this server. The slave uses normal zone transfers.

Summary

As should be evident from the examples, you have great flexibility when using this schema with the BIND9 back-end. The tree structure, the dn's, doesn't matter at all, the back-end simply does a sub-tree search using the base specified in named.conf for all entries in the wanted zone with the wanted name. For example when someone tries to look up the node my-hosta.my-domain.com it simply does a sub-tree search at the base given in named.conf with the filter (&(zoneName=my-domain.com)(relativeDomainName=my-hosta)). If someone looks up my-domain.com it uses the filter (&(zoneName=my-domain.com)(relativeDomainName=@)), and finally if someone wants to get the entire zone, for example a secondary using AXFR, it uses the filter (zoneName=my-domain.com). Wildcards are not supported.
venaas@uninett.no
Last modified: 2002-02-03 ldap2zone/ldap2bind0000755000175000017500000000401411520367707014072 0ustar benoitbenoit#!/bin/sh [ -r /etc/default/ldap2zone ] && . /etc/default/ldap2zone case "$LDAP_URI" in ldap://*|ldaps://*) ;; *) LDAP_URI="ldap://${LDAP_URI}" ;; esac LDAPSEARCH=`which ldapsearch` if [ -z "${LDAPSEARCH}" ]; then echo "ldapsearch program not in $PATH. Exiting..." exit 1 fi LDAP_URI_PARAM=${LDAP_URI:+"-H $LDAP_URI"} if [ "$ALLOW_NOTIFY" ]; then ALLOW_NOTIFY="$ALLOW_NOTIFY"; else ALLOW_NOTIFY=; fi if [ "$ALLOW_UPDATE" ]; then ALLOW_UPDATE_PARAM="allow-update {$ALLOW_UPDATE};"; else ALLOW_UPDATE_PARAM=; fi if [ "$ALLOW_TRANSFER" ]; then ALLOW_TRANSFER_PARAM="allow-transfer {$ALLOW_TRANSFER};"; else ALLOW_TRANSFER_PARAM=; fi ZONES=`ldapsearch -LLL $LDAP_HOST_PARAM -x "(objectClass=dNSZone)" zoneName | grep zoneName: | sort | uniq | awk '{print $2}'` ldap2zone=`which ldap2zone` rndc=`which rndc` if [ -z "${ZONES}" ]; then echo "No domains configured. Exiting..." exit 0 fi if [ -z "${rndc}" ]; then echo "rndc program not in $PATH. Exiting..." exit 1 fi if [ -z "${ldap2zone}" ]; then echo "ldap2zone program not in $PATH. Exiting..." exit 1 fi if [ ! -d $BIND_DIR ]; then echo "The directory specified as $BIND_DIR does not exist. Exiting..." exit 1 fi if [ ! -d $BIND_DATA ]; then echo "The directory specified as $BIND_DATA does not exist. Exiting..." exit 1 fi if [ -w $BIND_DIR/named.conf.ldap2zone ]; then >${BIND_DIR}/named.conf.ldap2zone for domain in $ZONES; do cat << EOF >> ${BIND_DIR}/named.conf.ldap2zone zone "${domain}" { type master; $ALLOW_NOTIFY file "${BIND_DATA}/${PREFIX}${domain}"; $ALLOW_UPDATE_PARAM $ALLOW_TRANSFER_PARAM }; EOF done $rndc reconfig fi for domain in $ZONES; do if $ldap2zone $domain $LDAP_URI $TTL > /tmp/$domain; then lines=$(cat /tmp/$domain | wc -l) [ $lines -gt 1 ] && mv /tmp/$domain $BIND_DATA/${PREFIX}${domain} fi result=$($rndc reload $domain 2>&1) if [ $? -ne 0 ]; then printf "Reloading the zone '$domain' failed: $result\n" 1>&2 else printf "Reloading the zone '$domain' was successful\n" 1>&2 fi done