commit ef7ba2e681103def2e0a154d177050cc4f3aab70 Author: Ben Klang Date: Fri Dec 2 04:09:15 2005 +0000 Back importing ldap2dns from Jacob Rief - 0.2.0 git-svn-id: https://svn.alkaloid.net/gpl/ldap2dns/trunk@1 06cd67b6-e706-0410-b29e-9de616bca6e9 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..056db05 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# ldap2dns Makefile +VERSION=0.2.0 +RELEASE=1 +WITHTINYDNS=-DWITH_TINYDNS +CC=gcc -O2 +DJBDNSDIR=../djbdns-1.02 +INC=-I$(DJBDNSDIR) +CFLAGS=$(INC) $(WITHTINYDNS) -DVERSION='"$(VERSION)"' +OBJS=ldap2dns.o +LIBS=-lldap -llber -lresolv +LD=gcc +LDFLAGS= +ALIBS=$(DJBDNSDIR)/dns.a $(DJBDNSDIR)/env.a $(DJBDNSDIR)/libtai.a \ + $(DJBDNSDIR)/cdb.a $(DJBDNSDIR)/alloc.a $(DJBDNSDIR)/buffer.a \ + $(DJBDNSDIR)/unix.a $(DJBDNSDIR)/byte.a +INSTALL_PREFIX= +PREFIXDIR=$(INSTALL_PREFIX)/usr +LDAPCONFDIR=$(INSTALL_PREFIX)/etc/openldap +TARFILE=/usr/src/redhat/SOURCES/ldap2dns-$(VERSION).tar.gz +SPECFILE=ldap2dns.spec + +all: ldap2dns + +ldap2dns: $(OBJS) $(LIBS) $(ALIBS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) $(ALIBS) $(LIBS) + ln -f ldap2dns ldap2dnsd + +ldap2dns.o: ldap2dns.c + $(CC) $(CFLAGS) -c $< + +install: all + mkdir -p $(PREFIXDIR)/bin + mkdir -p $(LDAPCONFDIR) + install -s -o root -g root -m 755 ldap2dns $(PREFIXDIR)/bin/ + ln -f $(PREFIXDIR)/bin/ldap2dns $(PREFIXDIR)/bin/ldap2dnsd + install -o root -g root -m 755 ldap2tinydns-conf $(PREFIXDIR)/bin/ + install -o root -g root -m 644 dns.at.conf $(LDAPCONFDIR)/ + install -o root -g root -m 644 dns.oc.conf $(LDAPCONFDIR)/ + +clean: + rm -f $(OBJS) ldap2dns ldap2dnsd data* $(SPECFILE) + +tar: clean + cd ..; \ + tar czf $(TARFILE) ldap2dns-$(VERSION) --exclude CVS + +$(SPECFILE): Specfile + sed -e 's#%VERSION%#$(VERSION)#g' \ + -e 's#%RELEASE%#$(RELEASE)#g' \ + < $< > $@ + +rpm: tar $(SPECFILE) + rpm -ba $(SPECFILE) + + diff --git a/README.html b/README.html new file mode 100644 index 0000000..da7807e --- /dev/null +++ b/README.html @@ -0,0 +1,332 @@ +

LDAP to DNS gateway

+

+ldap2dns is a program to create DNS records directly from a LDAP directory. +It can and should be be used to replace the secondary name-server by a second +primary one.
+ldap2dns helps to reduce all kind of administration overhead. +No more flat file editing, no more zone file editing. After having installed +ldap2dns, the administrator only has to access the LDAP directory.
+If he desires he can add access control for each zone, create a webbased GUI +and add all other kind of zone and resource record information without +interfering with the DNS server.
+ldap2dns is designed to write binary data.cdb files used by tinydns, but +also may be used to write .db-files used by named.
+

+ +

1. Introduction

+Often it is desirable to store DNS information in a database rather than +in flat text files. This can greatly help to reduce administration overhead +since associate information such as billing contact, account management, etc. +can be stored and processed inside the same database. Also due to the nature of +DNS, information must be stored redundantly on two or more hosts. +The classical data replication through zone transfer is unreliable, insecure +and difficult to administer.
+To solve this problem some proprietary attempts have been proposed to +store DNS information in relational databases. The nature of DNS however +is hierarchical and such should the database be. Using a relational database +to store DNS information is undesirable, because it becomes difficult +to store free form information. Within a hierachical data scheme, the +administrator might define more than one IP-address for each canonical name. +To implement such a feature in a relational database without breaking the +normalization rules, one would have to add another table.
+One of the most widely spread hierarchical database protocols is LDAP. +ldap2dns retrieves DNS information stored in an LDAP directory service +and generates a file suitable for name-servers.
+Actually the most widely spread name-servers +named and +tinydns are +supported. ldap2dns has been specially designed to work with +tinydns and is the favored name server daemon for the author of this program. +ldap2dns can also generate files suitable for named, but this feature +is not well supported. +There is a + +RFC for a format description how to store DNS information in LDAP. +This paper however is a draft RFC and expired in February 1999. The scheme this RFC +describes, looks as if it has been designed to be used only by 'named'. This scheme +does not have strict attribute-value-pair mapping, making it difficult to be used by +user interfaces. It also lacks of an implementation (I have never heard of any).
+Since tinydns is going another descriptive way, I implemented a similar object-scheme +more suitable for tinydns. Two objectclasses have been defined. DNSzone stores +all the information to define a DNS zone, such as the SOA (Start Of Authority), serial +numbers etc. DNSrrset is used to store the information for a single resource record, +such as the domain name, IP-addresses, class and type.
+Here are the tables: +

+

DNSzone

+This object-class represents a DNS zone. It is the container for all the resource records +within a zone. Zones can be primary or secondary, if used in conjunction with +tinydns zones are always primary. Secondary zones don't make sense anyway! +In addition to being a container, the zone object has attributes related to +the management of the zone. These include the zone's SOA information. Each zone-object +can have none to many children of class DNSrrset.
+ + + + + + + + + + + + + + + + + +
ATTRIBUTEVALUEComment
objectclassDNSzonerequired
cncommon namerequired
DNSzonenameName of the zonerequired, multivalued
DNSserialSerial number of SOAoptional
DNSrefreshRefresh time of SOAoptional, only used for zone transfers
DNSretryRetry time of SOAoptional, only used for zone transfers
DNSexpireExpire time of SOAoptional, only used for zone transfers
DNSminimumMinimum time to liveoptional, only used for zone transfers
DNSadminmailboxHostmaster's contact addressoptional
DNSzonemasterPrimary nameserver for this zoneoptional
DNStypeSOAmust be SOA
DNSclassINmust be IN
DNSttltime to liveoptional, only used with tinydns
DNStimestamptimestampoptional, only used with tinydns
+ +

+ +

DNSrrset

+The Resource Record Set represents all of the resource records for +a given host name within a zone. It must be a child of a DNSzone object.
+ + + + + + + + + + + + + +
ATTRIBUTEVALUEComment
objectclassDNSrrsetrequired
cncommon namerequired
DNSdomainnameName of this recordoptional, relative to zonename
DNSipaddrIP addressoptional, mutivalued
DNScnameCanonical nameoptional, without ending dot relative to zonename
DNSpreferenceintegeroptional, only used for MX records
DNStypeA, CNAME, NS, MX, PTR or TXTmust be any valid record type
DNSclassINmust be IN
DNSttltime to liveoptional, only used with tinydns
DNStimestamptimestampoptional, only used with tinydns
+

+ +

+

+ +

2. Installation

+ +

+ +

3. Running ldap2dns

+If You are a tinydns user, run ldap2dns in /services/tinydns/root.
+If You are an openldap user, the command line switches are the same as for ldapsearch +or ldapadd. +
+$ ldap2dns -D "binddn" [ -w passwd ] -b "searchbase" -o 1
+
+This generates a data.cdb file which is automatically updated by tinydns. The password +is required if You restrict read queries to authenticated users only. Now test with +
+$ dnsq any corp.local ipaddr
+
+Replace ipaddr with whatever You configured tinydns to listen to. +

+ +If You are a BIND user, run ldap2dns in /var/named with +

+$ ldap2dns -D "binddn" -w passwd -b "searchbase" -o 4
+
+Do not forget to add You primary definition to Your named.boot file and +do not forget to restart named with +
+# kill -HUP PID
+
+Now run +
+$ nslookup - localhost
+> ns1.corp.local
+
+Note that nslookup only works with tinydns if Your nameserver resolves its IP-address +backwards. +

+ +

4. Running ldap2dnsd

+ldap2dnsd is a hard link onto ldap2dns. If invoked the program +starts as backgound-daemon and contineously checks for modifications in the LDAP directory. +If the the daemon sees a modification in the DNSserial numbers it updates the data.cdb +file. This check is done about once a minute.
+The command-line options for ldap2dnsd are the same as for ldap2dns. +Use the -u option to modify the update intervall. You may also use -u on ldap2dns +to start as a foreground daemon. This is useful if You want to run ldap2dns from +daemontools. To do this run ldap2tinydns-conf +in /service/tinydns and link /service/ldap2dns onto /service/tinydns/ldap2dns. +
+# ln -s /service/tinydns/ldap2dns /service/ldap2dns
+
+After a few seconds daemontools starts ldap2dns which itself generates data.cdb +files whenever a modification is commited into the LDAP directory. +

+ldap2dns and ldap2dnsd recognize the following options: +

+-D binddn specify the distinguished name to bind to the LDAP directory
+-w bindpasswd use bindpasswd as the password for simple authentication
+-b searchbase use searchbase as the starting point for the search instead of the default
+-o 1|2|4 output format number or any binary or-ed combination. Defaults to 1
+	1: generate a binary file named 'data.cdb' to be used directly by tinydns
+	2: generate a text file named 'data' to be parsed by tinydns-data
+	4: for each zone generate a file named '.db' to be used by named
+-L[filename] print output in LDIF format for reimport, defaults to stdout if filename is omitted 
+-h host specify the hostname of LDAP directory, defaults to localhost
+-p port portnumber to connect to LDAP directory, defaults to 389
+-v run in verbose mode
+-vv even more verbose
+-V print version and exit
+-u numsecs update DNS data every numsecs. If started as ldap2dnsd this defaults to 59.
+
+

+ +

5. Importing DNS data from Your named

+A perl-script 'import.pl' is contained in this package. Edit the first +lines of the script to conform to Your configuration. +If You have installed the Perl packages Net::LDAP and Net::DNS +skip the following lines, otherwise do +
+# perl -MCPAN -e 'shell'
+(...snip...)
+> install Net::DNS
+> install Net::LDAP
+
+Now check that Your nameserver allows zone transfers to Your host and run the import script: +
+$ echo 'primary mydomain.org ' | ./import.pl
+
+for a single domain or +
+# cat named.boot | ./import.pl
+
+to populate Your LDAP directory. +

+ +

5. To Do

+ +

+ +

6. Copyright

+This program is licensed under the GPL version 2 or at Your choice any later +version.
+It was written in autumn 2000 by +Jacob Rief +in the hope it may be useful. + diff --git a/Specfile b/Specfile new file mode 100644 index 0000000..f66e78f --- /dev/null +++ b/Specfile @@ -0,0 +1,53 @@ +%define djbdns djbdns-1.02 + +Summary: LDAP to DNS gateway. +Name: ldap2dns +Version: %VERSION% +Release: %RELEASE% +Copyright: GPL +Group: Daemons/DNS +Source: ldap2dns-%{version}.tar.gz +Source1: http://cr.yp.to/djbdns/%{djbdns}.tar.gz +BuildRoot: /var/tmp/%{name}-root +Requires: openldap + +%description +ldap2dns is a program to create DNS records directly from an LDAP database. It can +be be used to replace the secondary name-server by a second primary one. +ldap2dns helps to reduce all kind of administration overhead. No more flat file editing, +no more zone file editing. After having installed ldap2dns, the administrator only has +to access the LDAP database. +If he desires he can add access control for each zone, create a webbased GUI and add +all other kind of zone and resource record information without interfering with the DNS +server. +ldap2dns is designed to write binary data.cdb files used by tinydns, but also may be +used to write .db-files used by named. + +%prep +%setup -a1 -q + +%build +make -C %{djbdns} +make DJBDNSDIR="%{djbdns}" VERSION=%{version} RPM_OPT_FLAGS="$RPM_OPT_FLAGS" + +%install +[ -n "%{buildroot}" -a "%{buildroot}" != / ] && rm -rf %{buildroot} +make DJBDNSDIR="%{djbdns}" INSTALL_PREFIX=$RPM_BUILD_ROOT install + +%clean +[ -n "%{buildroot}" -a "%{buildroot}" != / ] && rm -rf %{buildroot} + +%files +%defattr(-,root,root) +/usr/bin/ldap2dns +/usr/bin/ldap2dnsd +/usr/bin/ldap2tinydns-conf +%doc README.html +%doc import.pl +%config /etc/openldap/dns.at.conf +%config /etc/openldap/dns.oc.conf + +%changelog +* Wed Dec 06 2000 Jacob Rief +- initial revision for version 0.2.0 + diff --git a/dns.at.conf b/dns.at.conf new file mode 100644 index 0000000..30e42d0 --- /dev/null +++ b/dns.at.conf @@ -0,0 +1,24 @@ +# include this file into Your slapd.conf for openldap-1.2.x +# $Id: dns.at.conf,v 1.5 2000/12/04 12:59:57 jrief Exp $ +attribute DNSzonename cis +attribute DNSserial cis +attribute DNSrefresh cis +attribute DNSretry cis +attribute DNSexpire cis +attribute DNSminimum cis +attribute DNSadminmailbox cis +attribute DNSzonemaster cis +attribute DNStype cis +attribute DNSclass cis +attribute DNSdomainname cis +attribute DNSipaddr cis +attribute DNScname cis +attribute DNSpreference cis +attribute DNSttl cis +attribute DNStimestamp cis + +#attribute DNSaliasedobjectname cis +#attribute DNSrr cis +#attribute DNSrrcount cis +#attribute DNSmacaddress cis + diff --git a/dns.oc.conf b/dns.oc.conf new file mode 100644 index 0000000..6a6359c --- /dev/null +++ b/dns.oc.conf @@ -0,0 +1,45 @@ +# include this file into Your slapd.conf for openldap-1.2.x +# $Id: dns.oc.conf,v 1.4 2000/12/01 14:48:25 jrief Exp $ + +# child of anybody +# information to setup a DNS zone +objectclass DNSzone + requires + objectclass, + cn + allows + DNSzonename, + DNSserial, + DNSrefresh, + DNSretry, + DNSexpire, + DNSminimum, + DNSadminmailbox, + DNSzonemaster, + DNStype, + DNSclass, + DNSttl, + DNStimestamp + + #DNSrrcount, + +# child of a DNSzone +# information to setup a resource record +objectclass DNSrrset + requires + objectclass, + cn + allows + DNSdomainname, + DNSclass, + DNStype, + DNSipaddr, + DNScname, + DNSpreference, + DNSttl, + DNStimestamp + + #DNSaliasedobjectname, + #DNSrr, + #DNSmacaddress, + diff --git a/dns.schema b/dns.schema new file mode 100644 index 0000000..383a205 --- /dev/null +++ b/dns.schema @@ -0,0 +1,111 @@ +# schema for DNS data +# include this file into Your slapd.conf for openldap-2.0.x +# $Id: dns.schema,v 1.3 2000/12/01 14:48:25 jrief Exp $ + +attributetype ( 1.2.840.113556.1.17.1 + NAME 'DNSzonename' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.2 + NAME 'DNSserial' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.3 + NAME 'DNSrefresh' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.4 + NAME 'DNSretry' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.5 + NAME 'DNSexpire' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.6 + NAME 'DNSminimum' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.7 + NAME 'DNSadminmailbox' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.8 + NAME 'DNSzonemaster' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.9 + NAME 'DNStype' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.10 + NAME 'DNSclass' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.11 + NAME 'DNSdomainname' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.12 + NAME 'DNSipaddr' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 ) + +#attributetype ( 1.2.840.113556.1.17.13 +# NAME 'DNSaliasedobjectname' +# SUP name ) + +#attributetype ( 1.2.840.113556.1.17.14 +# NAME 'DNSrrcount' +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 +# SINGLE-VALUE ) + +#attributetype ( 1.2.840.113556.1.17.15 +# NAME 'DNSmacaddress' +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 +# SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.16 + NAME 'DNScname' + SUP name ) + +attributetype ( 1.2.840.113556.1.17.17 + NAME 'DNSpreference' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.18 + NAME 'DNSrr' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.19 + NAME 'DNSttl' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.2.840.113556.1.17.20 + NAME 'DNStimestamp' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE ) + +objectclass ( 1.2.840.113556.1.17.21 + NAME 'DNSzone' + MUST ( objectclass $ cn ) + MAY ( zonedomainname $ serial $ refresh $ retry $ expire $ minimum + $ adminmailbox $ zonemaster $ zonetype $ zoneclass $ rrcount + $ ttl $ timestamp ) ) + +objectclass ( 1.2.840.113556.1.17.22 + NAME 'DNSrrset' + SUP DNSzone + MUST ( objectclass $ cn ) + MAY ( dnsdomainname $ aliasedobjectname $ rr $ macaddress $ zoneclass + $ zonetype $ ipaddr $ cname $ preference $ ttl $ timestamp ) ) + diff --git a/example.ldif b/example.ldif new file mode 100644 index 0000000..8e3f594 --- /dev/null +++ b/example.ldif @@ -0,0 +1,45 @@ +dn: ou=dns,o=myorg +objectclass: organizationalUnit +ou: dns +userpassword: dnsadmin + +dn: cn=corp.local,ou=dns,o=myorg +objectclass: DNSzone +cn: corp.local +DNSzonename: corp.local +DNSserial: 12345 +DNSrefresh: 10800 +DNSretry: 3600 +DNSexpire: 3600000 +DNSminimum: 86400 +DNSadminmailbox: domainmaster.myorg +DNSzonemaster: ldap.myorg + +dn: cn=mail,cn=corp.local,ou=dns,o=myorg +objectclass: DNSrrset +cn: mail +DNScname: mail +DNStype: mx +DNSipaddr: 17.19.21.23 + +dn: cn=ns1,cn=corp.local,ou=dns,o=myorg +objectclass: DNSrrset +cn: ns1 +DNScname: ns1 +DNStype: ns +DNSipaddr: 17.19.23.24 + +dn: cn=ns2,cn=corp.local,ou=dns,o=myorg +objectclass: DNSrrset +cn: ns2 +DNScname: ns2 +DNStype: ns +DNSipaddr: 17.19.23.25 + +dn: cn=www,cn=corp.local,ou=dns,o=myorg +objectclass: DNSrrset +cn: www +DNSdomainname: www +DNStype: a +DNSipaddr: 17.19.23.30 + diff --git a/import.pl b/import.pl new file mode 100755 index 0000000..b1f1d74 --- /dev/null +++ b/import.pl @@ -0,0 +1,241 @@ +#!/usr/bin/perl +# Script to import data from DNS into LDAP +# Copyright 2000, Jacob Rief +# $Id: import.pl,v 1.24 2000/12/14 12:44:29 jrief Exp $ + +###### configure this ###### +# remember to allow zone transfers from Your nameserver +$LDAPHOST = "ldap.myorg.com"; +$LDAPBINDDN = "ou=dns,o=myorg"; +$LDAPPASSWD = "secret"; +$NAMESERVER = "ns1.myorg.com"; +$BASEDN = "ou=dns,o=myorg"; +$FULL_QUALIFIED_NAME = 0; + +###### don't edit below this line ###### +use Net::DNS; +use Net::LDAP; + +$ldap = Net::LDAP->new($LDAPHOST) or die "Can't connect to LDAP server"; +$mesg = $ldap->bind( dn => $LDAPBINDDN, password => $LDAPPASSWD ); +die "Unable to bind to LDAP ", $mesg->error if ($mesg->code); + +@domains; +while (<>) { + chomp; + $_ = lc; + if (/primary\s+([0-9A-Za-z._+-]+)\s+/) { + push(@domains, $1); + } +} +if ($#domains>=0) { + @domains = sort(@domains); + for ($i = 1; $i<=$#domains; $i++) { + if ($domains[$i-1] eq $domains[$i]) { + print "Warning: removing double entry for zone: $domains[$i]\n"; + splice(@domains, $i, 1); + } + } + print "Adding ". ($#domains+1) ." zones to LDAP server\n"; + foreach(@domains) { + read_zone($_); + } +} else { + print "No domain added to LDAP server\n"; +} + + +sub add_attrs +{ + my ($attr, $zonename) = @_; + + # correct DNScname + if (defined $$attr{'DNScname'}) { + # check if DNScname is a real name + if ($$attr{'DNScname'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { + $$attr{'DNSipaddr'} = "$1.$2.$3.$4"; + undef $$attr{'DNScname'}; + } + } + + my ($tail); + if ($$attr{'DNSdomainname'} eq $zonename) { + $tail = ""; + } else { + split /\.$zonename/, $$attr{'DNSdomainname'}; + die "Corrupt DNSdomainname" unless (defined @_[0]); + $tail = @_[0]; + } + + if ($FULL_QUALIFIED_NAME) { + $$attr{'DNSdomainname'} = "$zonename." if ($tail eq ""); + $$attr{'DNSdomainname'} = "$tail.$zonename." unless ($tail eq ""); + $$attr{'DNScname'} .= "." if (defined $$attr{'DNScname'}); + } else { + $$attr{'DNSdomainname'} = "$tail"; + if (defined $$attr{'DNScname'}) { + split /\.$zonename/, $$attr{'DNScname'}; + $$attr{'DNScname'} = @_[0] if (defined @_[0]); + } + } + + my $rrdn; + if ($$attr{'DNStype'} eq "A") { + # A records are multivalued, use one rrset for all ipaddresses + $$attr{'cn'} = "A:$tail"; + $rrdn = "cn=$$attr{'cn'},cn=$zonename,$BASEDN"; + $mesg = $ldap->search(base=>$rrdn, scope=>"base", filter => "(objectclass=DNSrrset)"); + if ($mesg->count==0) { + $mesg = $ldap->add(dn=>$rrdn, attr=>list_attrs($attr)); + die "Failed to add entry:", $rrdn, " ", $mesg->error if ($mesg->code); + } else { + $mesg = $ldap->modify(dn=>$rrdn, add=>{ 'DNSipaddr'=>$$attr{'DNSipaddr'} }); + die "Failed to modify entry:", $rrdn, " ", $mesg->error if ($mesg->code); + } + } else { + # All other records are siglevalued, use one rrset for each entry + my $i = 0; + do { + $i++; + $$attr{'cn'} = "$$attr{'DNStype'}$i:$tail"; + $rrdn = "cn=$$attr{'cn'},cn=$zonename,$BASEDN"; + + $mesg = $ldap->search(base=>$rrdn, scope=>"base", filter=>"(objectclass=DNSrrset)"); + } while ($mesg->count>0); + if ($FULL_QUALIFIED_NAME) { + $$attr{'DNScname'} = "$$attr{'DNStype'}$i.$zonename." unless defined $$attr{'DNScname'}; + } else { + $$attr{'DNScname'} = "$$attr{'DNStype'}$i" unless defined $$attr{'DNScname'}; + } + $mesg = $ldap->add(dn=>$rrdn, attr=>list_attrs($attr)); + die "Failed to add entry:", $rrdn, " ", $mesg->error if ($mesg->code); + } +} + + +sub list_attrs +{ + my $attr = shift; + my (@list, $key, $value); + while (($key, $value) = each %$attr) { + push(@list, $key => $value); + } + return \@list; +} + + +sub read_zone +{ + my $zonename = shift; + + $res = new Net::DNS::Resolver; + $res->nameservers($NAMESERVER); + @zone = $res->axfr($zonename); + while (!@zone) { + print "Query failed for $zonename: ", $res->errorstring, ".\n"; + if ($res->errorstring eq "couldn't connect") { + print "Trying to reconnect\n"; + sleep(10); + @zone = $res->axfr($zonename); + } else { + return; + } + } + print "---------- reading zone $zonename ----------\n"; + foreach $rr (@zone) { + $rr->print; + if ($rr->type eq "SOA") { + die "Invalid SOA record for ", $rr->name, " " unless ($rr->string =~ /^([0-9a-zA-Z_.+-]+)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+([0-9a-zA-Z_.+-]+)\s+([0-9a-zA-Z_.+-]+)\s+\((.*)\)/s); + die "Corrupt SOA record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type); + + my %attr; + $attr{'objectclass'} = "DNSzone"; + $attr{'DNSzonename'} = lc $1; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNSzonemaster'} = lc $5; + $attr{'DNSadminmailbox'} = lc $6; + my $soa = $7; + die "Invalid SOA fields for ", $zonename, " " unless ($soa =~ /\s*(\d+)\D*(\d+)\D*(\d+)\D*(\d+)\D*(\d+)\s*/s); + $attr{'DNSserial'} = $1; + $attr{'DNSrefresh'} = $2; + $attr{'DNSretry'} = $3; + $attr{'DNSexpire'} = $4; + $attr{'DNSminimum'} = $5; + $attr{'cn'} = $zonename; + + $mesg = $ldap->add(dn=>"cn=$zonename,$BASEDN", attr=>list_attrs(\%attr)); + die "Failed to add entry:", $zonename, " ", $mesg->error if ($mesg->code); + } elsif ($rr->type eq "A") { + die "Invalid A record for ", $rr->name, " " unless ($rr->string =~ /^([0-9a-zA-Z_.+-]+)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+([0-9.]+)/); + die "Corrupt A record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type && $5 eq $rr->address); + + next if $1 eq "localhost.$zonename"; + my %attr; + $attr{'objectclass'} = "DNSrrset"; + $attr{'DNSdomainname'} = lc $1; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNSipaddr'} = $5; + add_attrs(\%attr, $zonename); + } elsif ($rr->type eq "MX") { + die "Invalid MX record for ", $rr->name, " " unless ($rr->string =~ /^([0-9a-zA-Z_.+-]+)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+([0-9a-zA-Z_.+-]+)/); + die "Corrupt MX record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type); + + my %attr; + $attr{'objectclass'} = "DNSrrset"; + $attr{'DNSdomainname'} = lc $1; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNSpreference'} = $5; + $attr{'DNScname'} = lc $6; + add_attrs(\%attr, $zonename); + } elsif ($rr->type eq "NS") { + die "Invalid NS record for ", $rr->name, " " unless ($rr->string =~ /^([0-9a-zA-Z_.+-]+)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+([0-9a-zA-Z_.+-]+)/); + die "Corrupt NS record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type); + + my %attr; + $attr{'objectclass'} = "DNSrrset"; + $attr{'DNSdomainname'} = lc $1; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNScname'} = lc $5; + add_attrs(\%attr, $zonename); + } elsif ($rr->type eq "CNAME" || $rr->type eq "TXT") { + die "Invalid ", $rr->type, " record for ", $rr->name, " " unless ($rr->string =~ /^([0-9a-zA-Z_.+-]+)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+([0-9a-zA-Z_.+-]+)/); + die "Corrupt ", $rr->type, " record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type); + + my %attr; + $attr{'objectclass'} = "DNSrrset"; + $attr{'DNSdomainname'} = $1; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNScname'} = $5; + add_attrs(\%attr, $zonename); + } elsif ($rr->type eq "PTR") { + die "Invalid PTR record for ", $rr->name, " " unless ($rr->string =~ /^([0-9.]+\.in-addr\.arpa)\.\s+(\d+)\s+(\w+)\s+(\w+)\s+([0-9a-zA-Z_.+-]+)/); + die "Corrupt PTR record for ", $rr->name, " " unless ($1 eq $rr->name && $2 eq $rr->ttl && $3 eq $rr->class && $4 eq $rr->type); + + my %attr; + $attr{'objectclass'} = "DNSrrset"; + $attr{'DNSdomainname'} = "$1."; + $attr{'DNSttl'} = $2; + $attr{'DNSclass'} = $3; + $attr{'DNStype'} = $4; + $attr{'DNScname'} = $5; + if ($attr{'DNSdomainname'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { + $attr{'DNSipaddr'} = "$4.$3.$2.$1"; + $attr{'cn'} = "PTR:$1"; # Only for C-level domains yet + } else { die "Corrupt IP address for", $rr->name; } + my $rrdn = "cn=$attr{'cn'},cn=$zonename,$BASEDN"; + $mesg = $ldap->add(dn=>$rrdn, attr=>list_attrs(\%attr)); + die "Failed to add entry:", $rrdn, " ", $mesg->error if ($mesg->code); + } + } +} + diff --git a/index.html b/index.html new file mode 100644 index 0000000..efea686 --- /dev/null +++ b/index.html @@ -0,0 +1,41 @@ + + + + LDAP to DNS gateway + + + + +
+

+ +

+

Download

+ + + + + + + +
Versiontar.gzrpmsrpmReleased
0.1.1ldap2dns2000-Sep-19
0.1.2ldap2dns2000-Sep-22
0.1.3ldap2dns2000-Sep-28
0.1.4ldap2dns2000-Oct-04
0.2.0ldap2dnsldap2dnsldap2dns2000-Dec-14
+ +
+ + + diff --git a/ldap2dns.c b/ldap2dns.c new file mode 100644 index 0000000..d633569 --- /dev/null +++ b/ldap2dns.c @@ -0,0 +1,915 @@ +/* + * Create data from an LDAP directory service to be used for tinydns + * $Id: ldap2dns.c,v 1.20 2000/12/12 09:48:07 jrief Exp $ + * Copyright 2000 by Jacob Rief + * License: GPL version 2 or later. See http://www.fsf.org for details + */ + +#include +#include +#include +#include +#include +#include +#include + +#define UPDATE_INTERVALL 59 +#define LDAP_CONF "/etc/openldap/ldap.conf" + +#if defined WITH_TINYDNS +# include "uint16.h" +# include "uint32.h" +# include "str.h" +# include "byte.h" +# include "fmt.h" +# include "ip4.h" +# include "exit.h" +# include "readwrite.h" +# include "buffer.h" +# include "strerr.h" +# include "getln.h" +# include "cdb_make.h" +# include "stralloc.h" +# include "open.h" +# include "dns.h" + +int fdcdb; +struct cdb_make cdb; +buffer b; +char bspace[1024]; +static stralloc key; +static stralloc result; +static char* dottemp1; +static char* dottemp2; +static char tinydns_datafile[256]; +static char tinydns_tempfile[256]; + +#endif + +static char tinydns_textfile[256]; +static LDAP* ldap_con; +static FILE* bindfile; +static FILE* tinyfile; +static FILE* ldifout; +static time_t time_now; +static int autoreverse; +static char* const* main_argv; +static int main_argc; + + +static void print_version(void) +{ + printf("ldap2dns, version %s\n", VERSION); + printf(" Copyright 2000 by Jacob Rief \n\n"); +} + + +static void die_ldap(int err) +{ + fprintf(stderr, "Fatal error: %s\n", ldap_err2string(err)); + exit(1); +} + + +static struct +{ + char domainname[64]; + char zonemaster[64]; + char adminmailbox[64]; + unsigned long serial; + unsigned long refresh; + unsigned long retry; + unsigned long expire; + unsigned long minimum; + int ttl; + char timestamp[16]; +} zone; + +struct resourcerecord +{ + char cn[64]; + char dnsdomainname[64]; + char class[16]; + char type[16]; + char ipaddr[256][32]; + char cname[64]; + int ttl; + char timestamp[16]; + int preference; +#if defined DRAFT_RFC + char rr[1024]; + char aliasedobjectname[256]; + char macaddress[32]; +#endif +}; + + +static struct +{ + char searchbase[128]; + char binddn[128]; + char hostname[128]; + char password[128]; + int is_daemon; + int update_iv; + int port; + unsigned int output; + int verbose; + char ldifname[128]; +} options; + + +static void die_exit(const char* message) +{ + if (message) + fprintf(stderr, "Fatal error: %s\n", message); + else + fprintf(stderr, "Fatal memory error\n"); + exit(1); +} + + +#if defined WITH_TINYDNS + +static void rr_add(char *buf, unsigned int len) +{ + if (!stralloc_catb(&result, buf, len)) die_exit(0); +} + +static void rr_addname(char *d) +{ + rr_add(d,dns_domain_length(d)); +} + +static void rr_start(char type[2], unsigned long ttl, char ttd[8]) +{ + char buf[4]; + if (!stralloc_copyb(&result, type,2)) die_exit(0); + rr_add("=",1); + uint32_pack_big(buf, ttl); + rr_add(buf,4); + rr_add(ttd,8); +} + +static void rr_finish(char *owner) +{ + if (byte_equal(owner,2,"\1*")) { + owner += 2; + result.s[2] = '*'; + } + if (!stralloc_copyb(&key, owner, dns_domain_length(owner))) die_exit(0); + case_lowerb(key.s, key.len); + if (cdb_make_add(&cdb, key.s, key.len, result.s, result.len) == -1) + die_exit("Unable to create 'data.tmp'"); +} + + +#endif + +static void set_datadir(void) +{ + char* ev = getenv("TINYDNSDIR"); + int len; + +#if defined WITH_TINYDNS + tinydns_datafile[0] = 0; + tinydns_tempfile[0] = 0; +#endif + tinydns_textfile[0] = 0; + if (ev && (len = strlen(ev))<240) { +#if defined WITH_TINYDNS + strncpy(tinydns_datafile, ev, 240); + strncpy(tinydns_tempfile, ev, 240); +#endif + strncpy(tinydns_textfile, ev, 240); + if (ev[len-1]!='/') { +#if defined WITH_TINYDNS + tinydns_datafile[len] = '/'; + tinydns_tempfile[len] = '/'; +#endif + tinydns_textfile[len] = '/'; + } + } +#if defined WITH_TINYDNS + strcat(tinydns_datafile, "data.cdb"); + strcat(tinydns_tempfile, "data.tmp"); +#endif + strcat(tinydns_textfile, "data"); +} + + +static void print_usage(void) +{ + print_version(); + printf("usage: ldap2dns[d] [-D binddn] [-b searchbase] [-o 0|1|2|4] [-h host] [-p port] [-w password] [-L[filename]] [-u numsecs] [-v[v]] [-V]\n\n"); + printf("ldap2dns connects to an LDAP server reads the DNS information stored in objectclasses\n" + "\t\tDNSzone and DNSrrset and writes a file to be used by tinydns or named.\n" + "\t\tldap2dnsd starts as background-job and continouesly updates DNS information.\n"); + printf("options:\n"); + printf(" -D binddn\tUse the distinguished name binddn to bind to the LDAP directory\n"); + printf(" -w bindpasswd\tUse bindpasswd as the password for simple authentication\n"); + printf(" -b use searchbase as the starting point for the search instead of the default\n"); + printf(" -o 1|2|4\toutput format number or any binary or-ed combination. Defaults to 1\n"); + printf("\t1: generate a binary file named 'data.cdb' to be used directly by tinydns\n"); + printf("\t2: generate a text file named 'data' to be parsed by tinydns-data\n"); + printf("\t4: for each zone generate a file named '.db' to be used by named\n"); + printf(" -L[filename] print output in LDIF format for reimport\n"); + printf(" -h host\thostname of LDAP server, defaults to localhost\n"); + printf(" -p port\tportnumber to connect to LDAP server, defaults to %d\n", LDAP_PORT); + printf(" -u numsecs\tUpdate DNS data after numsecs. Defaults to %d if started as daemon.\n\t\t" + "Important notice: data.cdb is rewritten only after DNSserial in DNSzone is increased.\n", + UPDATE_INTERVALL); + printf(" -v\t\trun in verbose mode\n"); + printf(" -vv\t\teven more verbose\n"); + printf(" -V\t\tprint version and exit\n\n"); +} + +static int parse_options() +{ + extern char* optarg; + extern int optind, opterr, optopt; + char buf[256], value[128]; + int c; + FILE* ldap_conf; + + strcpy(options.searchbase, ""); + strcpy(options.hostname, "localhost"); + options.port = LDAP_PORT; + if (ldap_conf = fopen(LDAP_CONF, "r")) { + while(fgets(buf, 256, ldap_conf)!=0) { + if (sscanf(buf, "BASE %128s", value)==1) + strcpy(options.searchbase, value); + if (sscanf(buf, "HOST %128s:%d", value, &c)==2) { + strcpy(options.hostname, value); + options.port = c; + } else if (sscanf(buf, "HOST %128s", value)==1) + strcpy(options.hostname, value); + if (sscanf(buf, "PORT %d", &c)==1) + options.port = c; + } + fclose(ldap_conf); + } + strcpy(options.binddn, ""); + options.output = 1; + options.verbose = 0; + options.ldifname[0] = '\0'; + c = strlen(main_argv[0]); + if (strcmp(main_argv[0]+c-9, "ldap2dnsd")==0) + options.is_daemon = 1; + else + options.is_daemon = 0; + options.update_iv = 59; + strcpy(options.password, ""); + while ( (c = getopt(main_argc, main_argv, "b:D:h:o:p:u:Vw:v::L::"))>0 ) { + if (optarg && strlen(optarg)>127) { + fprintf(stderr, "argument %s too long\n", optarg); + continue; + } + switch (c) { + case 'b': + strcpy(options.searchbase, optarg); + break; + case 'u': + if (sscanf(optarg, "%d", &options.update_iv)!=1) + options.update_iv = UPDATE_INTERVALL; + if (options.update_iv<=0) options.update_iv = 1; + if (options.is_daemon==0) options.is_daemon = 2; /* foreground daemon */ + break; + case 'D': + strcpy(options.binddn, optarg); + break; + case 'h': + strcpy(options.hostname, optarg); + break; + case 'L': + if (optarg==NULL) + strcpy(options.ldifname, "-"); + else + strcpy(options.ldifname, optarg); + break; + case 'o': + if (sscanf(optarg, "%d", &options.output)!=1) + options.output = 0; + break; + case 'p': + if (sscanf(optarg, "%d", &options.port)!=1) + options.port = LDAP_PORT; + break; + case 'v': + if (optarg && optarg[0]=='v') + options.verbose = 3; + else + options.verbose = 1; + break; + case 'V': + print_version(); + exit(0); + case 'w': + strcpy(options.password, optarg); + break; + default: + print_usage(); + exit(1); + } + } +} + + +static int expand_domainname(char target[64], const char* source, int slen) +{ + if (slen>64) + return 0; + if (source[slen-1]=='.') { + strncpy(target, source, slen-1); + target[slen-1] = '\0'; + return 1; + } + strncpy(target, source, slen); + target[slen] = '\0'; + if (zone.domainname[0]) { + if (zone.domainname[0]!='.') + strcat(target, "."); + strcat(target, zone.domainname); + return 1; + } + return 0; +} + + +static int expand_reverse(char target[64], const char* source) +{ +} + + +static void write_rr(struct resourcerecord* rr, int ipdx) +{ + char ip[4]; + char buf[4]; + + if (strcasecmp(rr->class, "IN")) + return; + +#if defined WITH_TINYDNS + if (options.output&1) { + int dnsdn_len = strlen(rr->dnsdomainname); + int cname_len = strlen(rr->cname); + if (!dns_domain_fromdot(&dottemp1, rr->dnsdomainname, dnsdn_len)) die_exit(0); + if (!dns_domain_fromdot(&dottemp2, rr->cname, cname_len)) die_exit(0); + } +#endif + + if (strcasecmp(rr->type, "NS")==0) { + if (tinyfile) + fprintf(tinyfile, "&%s:%s:%s:%d:%s\n", rr->dnsdomainname, (ipdx>=0 ? rr->ipaddr[ipdx] : ""), rr->cname, rr->ttl, rr->timestamp); + if (bindfile) { + fprintf(bindfile, "%s.\tIN NS\t%s.\n", rr->dnsdomainname, rr->cname); + if (ipdx>=0) + fprintf(bindfile, "%s.\tIN A\t%s\n", rr->cname, rr->ipaddr[ipdx]); + } +#if defined WITH_TINYDNS + if (options.output&1) { + rr_start(DNS_T_NS, rr->ttl, rr->timestamp); + rr_addname(dottemp2); + rr_finish(dottemp1); + if (ipdx>=0 && ip4_scan(rr->ipaddr[ipdx], ip)) { + rr_start(DNS_T_A, rr->ttl, rr->timestamp); + rr_add(ip, 4); + rr_finish(dottemp2); + } + } +#endif + } else if (strcasecmp(rr->type, "MX")==0) { + if (tinyfile) + fprintf(tinyfile, "@%s:%s:%s:%d:%d:%s\n", rr->dnsdomainname, (ipdx>=0 ? rr->ipaddr[ipdx] : ""), rr->cname, rr->preference, rr->ttl, rr->timestamp); + if (bindfile) { + fprintf(bindfile, "%s.\tIN MX\t%d %s.\n", rr->dnsdomainname, rr->preference, rr->cname); + if (ipdx>=0) + fprintf(bindfile, "%s.\tIN A\t%s\n", rr->cname, rr->ipaddr[ipdx]); + } +#if defined WITH_TINYDNS + if (options.output&1) { + rr_start(DNS_T_MX, rr->ttl, rr->timestamp); + uint16_pack_big(buf, rr->preference); + rr_add(buf, 2); + rr_addname(dottemp2); + rr_finish(dottemp1); + if (ipdx>=0 && ip4_scan(rr->ipaddr[ipdx], ip)) { + rr_start(DNS_T_A, rr->ttl, rr->timestamp); + rr_add(ip, 4); + rr_finish(dottemp2); + } + } +#endif + } else if ( strcasecmp(rr->type, "A")==0) { + if (tinyfile) + fprintf(tinyfile, "%s%s:%s:%d:%s\n", (autoreverse ? "=" : "+"), rr->dnsdomainname, (ipdx>=0 ? rr->ipaddr[ipdx] : ""), rr->ttl, rr->timestamp); + if (bindfile && ipdx>=0) + fprintf(bindfile, "%s.\tIN A\t%s\n", rr->dnsdomainname, rr->ipaddr[ipdx]); +#if defined WITH_TINYDNS + if (options.output&1) { + char dptr[DNS_NAME4_DOMAIN]; + if (ipdx>=0 && ip4_scan(rr->ipaddr[ipdx], ip)) { + rr_start(DNS_T_A, rr->ttl, rr->timestamp); + rr_add(ip, 4); + rr_finish(dottemp1); + } + if (autoreverse) { + dns_name4_domain(dptr, ip); + rr_start(DNS_T_PTR, rr->ttl, rr->timestamp); + rr_addname(dottemp1); + rr_finish(dptr); + } + } +#endif + } else if (strcasecmp(rr->type, "PTR")==0) { + int ip[4] = {0, 0, 0, 0}; + char buf[64]; + if (ipdx>0) { + /* does not make to have more than one IPaddr for a PTR record */ + return; + } + if (ipdx==0 && sscanf(rr->ipaddr[0], "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3])==4) { + /* lazy user, used DNSipaddr for reverse lookup */ + sprintf(buf, "%d.%d.%d.%d.in-addr.arpa", ip[3], ip[2], ip[1], ip[0]); + } else { + strcpy(buf, rr->dnsdomainname); + } + if (tinyfile) + fprintf(tinyfile, "^%s:%s:%d:%s\n", buf, rr->cname, rr->ttl, rr->timestamp); + if (bindfile) + fprintf(bindfile, "%s.\tIN PTR\t%s.\n", buf, rr->cname); +#if defined WITH_TINYDNS + if (options.output&1) { + int dnsdn_len = strlen(buf); + if (!dns_domain_fromdot(&dottemp1, buf, dnsdn_len)) die_exit(0); + rr_start(DNS_T_PTR, rr->ttl, rr->timestamp); + rr_addname(dottemp2); + rr_finish(dottemp1); + } +#endif + } else if (strcasecmp(rr->type, "CNAME")==0) { + if (tinyfile) + fprintf(tinyfile, "C%s:%s:%d:%s\n", rr->dnsdomainname, rr->cname, rr->ttl, rr->timestamp); + if (bindfile) + fprintf(bindfile, "%s.\tIN CNAME\t%s.\n", rr->dnsdomainname, rr->cname); +#if defined WITH_TINYDNS + if (options.output&1) { + rr_start(DNS_T_CNAME, rr->ttl, rr->timestamp); + rr_addname(dottemp2); + rr_finish(dottemp1); + } +#endif + } else if (strcasecmp(rr->type, "TXT")==0) { + if (tinyfile) + fprintf(tinyfile, "'%s:%s:%d:%s\n", rr->dnsdomainname, rr->cname, rr->ttl, rr->timestamp); + if (bindfile) + fprintf(bindfile, "%s.\tIN TXT\t%s.\n", rr->dnsdomainname, rr->cname); +#if defined WITH_TINYDNS + if (options.output&1) { + rr_start(DNS_T_TXT, rr->ttl, rr->timestamp); + rr_addname(dottemp2); + rr_finish(dottemp1); + } +#endif + } +} + + +#if defined DRAFT_RFC +static void parse_rr(struct resourcerecord* rr) +{ + char word1[64]; + char word2[64]; + int ip[4]; + + sscanf(rr->rr, "%16s %16s %64s %64s", rr->class, rr->type, word1, word2); + if (strcasecmp(rr->type, "NS")==0) { + if (sscanf(word1, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3])==4) { + sprintf(rr->ipaddr[0], "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + } else { + int len = strlen(word1); + expand_domainname(rr->cname, word1, len); + } + } else if (strcasecmp(rr->type, "MX")==0) { + if (sscanf(word1, "%d", &rr->preference)!=1) + rr->preference = 0; + if (sscanf(word2, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3])==4) { + sprintf(rr->ipaddr[0], "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + } else { + int len = strlen(word2); + expand_domainname(rr->cname, word2, len); + } + } else if (strcasecmp(rr->type, "A")==0) { + if (sscanf(word1, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3])==4) + sprintf(rr->ipaddr[0], "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + else + rr->ipaddr[0][0] = '\0'; + } else if (strcasecmp(rr->type, "PTR")==0) { + expand_reverse(rr->dnsdomainname, word1); + } else if (strcasecmp(rr->type, "CNAME")==0) { + int len = strlen(word1); + expand_reverse(rr->cname, word1); + } else if (strcasecmp(rr->type, "TXT")==0) { + strncpy(rr->cname, word1, 64); + } +} +#endif + + +static void read_resourcerecords(char* dn) +{ + LDAPMessage* res = NULL; + LDAPMessage* m; + int ldaperr; + + if ( (ldaperr = ldap_search_s(ldap_con, dn, LDAP_SCOPE_ONELEVEL, "objectclass=DNSrrset", NULL, 0, &res))!=LDAP_SUCCESS ) + die_ldap(ldaperr); + for (m = ldap_first_entry(ldap_con, res); m; m = ldap_next_entry(ldap_con, m)) { + BerElement* ber = NULL; + char* attr; + char* dn = ldap_get_dn(ldap_con, m); + struct resourcerecord rr; + int ipaddresses = 0; + + if (options.ldifname[0]) + fprintf(ldifout, "dn: %s\n", dn); + rr.cn[0] = '\0'; + strncpy(rr.dnsdomainname, zone.domainname, 64); + strcpy(rr.class, "IN"); + rr.type[0] = '\0'; + rr.cname[0] = '\0'; + rr.ttl = time_now; + rr.timestamp[0] = '\0'; + rr.preference = 10; +#if defined DRAFT_RFC + rr.aliasedobjectname[0] = '\0'; + rr.rr[0] = '\0'; +#endif + for (attr = ldap_first_attribute(ldap_con, m, &ber); attr; attr = ldap_next_attribute(ldap_con, m, ber)) { + int len = strlen(attr); + struct berval** bvals; + char* dnsnname = ""; + + if ( (bvals = ldap_get_values_len(ldap_con, m, attr))!=NULL ) { + if (bvals[0] && bvals[0]->bv_len>0) { + if (strcasecmp(attr, "objectclass")==0) { + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, bvals[0]->bv_val); + } else if (strcasecmp(attr, "cn")==0) { + strncpy(rr.cn, bvals[0]->bv_val, 64); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.cn); + } else if (strcasecmp(attr, "DNSdomainname")==0) { + if (!expand_domainname(rr.dnsdomainname, bvals[0]->bv_val, bvals[0]->bv_len)) + rr.dnsdomainname[0] = '\0';; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, bvals[0]->bv_val); + } else if (strcasecmp(attr, "DNSclass")==0) { + if (sscanf(bvals[0]->bv_val, "%16s", &rr.class)!=1) + rr.class[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.class); + } else if (strcasecmp(attr, "DNStype")==0) { + if (sscanf(bvals[0]->bv_val, "%16s", &rr.type)!=1) + rr.type[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.type); + } else if (strcasecmp(attr, "DNSipaddr")==0) { + int ip[4]; + for (ipaddresses = 0; bvals[ipaddresses] && ipaddresses<256; ipaddresses++) { + rr.ipaddr[ipaddresses][0] = '\0'; + if (sscanf(bvals[ipaddresses]->bv_val, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3])==4) + sprintf(rr.ipaddr[ipaddresses], "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.ipaddr[ipaddresses]); + } + } else if (strcasecmp(attr, "DNScname")==0) { + if (!expand_domainname(rr.cname, bvals[0]->bv_val, bvals[0]->bv_len)) + rr.cname[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, bvals[0]->bv_val); + } else if (strcasecmp(attr, "DNSttl")==0) { + if (sscanf(bvals[0]->bv_val, "%d", &rr.ttl)!=1) + rr.ttl = time_now; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, rr.ttl); + } else if (strcasecmp(attr, "DNStimestamp")==0) { + if (sscanf(bvals[0]->bv_val, "%16s", &rr.timestamp)!=1) + rr.timestamp[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.timestamp); + } else if (strcasecmp(attr, "DNSpreference")==0) { + if (sscanf(bvals[0]->bv_val, "%d", &rr.preference)!=1) + rr.preference = 10; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, bvals[0]->bv_val); + } +#if defined DRAFT_RFC + else if (strcasecmp(attr, "DNSrr")==0) { + strncpy(rr.rr, bvals[0]->bv_val, 1024); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.rr); + } else if (strcasecmp(attr, "DNSaliasedobjectname")==0) { + if (sscanf(bvals[0]->bv_val, "%256s", rr.aliasedobjectname)!=1) + rr.aliasedobjectname[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, rr.aliasedobjectname); + } else if (strcasecmp(attr, "DNSmacaddress")==0) { + } +#endif + } + ldap_value_free_len(bvals); + } + } +#if defined DRAFT_RFC + if (rr.rr[0]) { + parse_rr(&rr); + } +#endif + do { + ipaddresses--; + write_rr(&rr, ipaddresses); + } while (ipaddresses>0); +#if defined DRAFT_RFC + if (rr.aliasedobjectname[0]) + read_resourcerecords(rr.aliasedobjectname); +#endif + if (options.ldifname[0]) + fprintf(ldifout, "\n"); + if (options.verbose&2) + printf("\trr: %s %s %s\n", rr.class, rr.type, rr.dnsdomainname); + free(dn); + } + ldap_msgfree(res); +} + + +static void write_zone(void) +{ + int len; + char soa[20]; + + if (tinyfile) { + fprintf(tinyfile, "Z%s:%s:%s:%d:%d:%d:%d:%d:%d:%s\n", zone.domainname, + zone.zonemaster, zone.adminmailbox, zone.serial, zone.refresh, zone.retry, + zone.expire, zone.minimum, zone.ttl, zone.timestamp); + } + + if (bindfile) { + fprintf(bindfile, "; Automatically generated by ldap2dns - DO NOT EDIT!\n"); + fprintf(bindfile, "%s. IN SOA %s. %s. ", zone.domainname, zone.zonemaster, zone.adminmailbox); + fprintf(bindfile, "(\n\t%d\t; Serial\n\t%d\t; Refresh\n\t%d\t; Retry\n\t%d\t; Expire\n\t%d )\t; Minimum\n", zone.serial, zone.refresh, zone.retry, zone.expire, zone.minimum); + } + +#if defined WITH_TINYDNS + if (options.output&1) { + byte_zero(zone.timestamp, 8); + len = strlen(zone.domainname); + if (!dns_domain_fromdot(&dottemp1, zone.domainname, len)) die_exit(0); + uint32_pack_big(soa, zone.serial); + uint32_pack_big(soa+4, zone.refresh); + uint32_pack_big(soa+8, zone.retry); + uint32_pack_big(soa+12, zone.expire); + uint32_pack_big(soa+16, zone.minimum); + rr_start(DNS_T_SOA, zone.ttl, zone.timestamp); + len = strlen(zone.zonemaster); + if (!dns_domain_fromdot(&dottemp2, zone.zonemaster, len)) die_exit(0); + rr_addname(dottemp2); + len = strlen(zone.adminmailbox); + if (!dns_domain_fromdot(&dottemp2, zone.adminmailbox, len)) die_exit(0); + rr_addname(dottemp2); + rr_add(soa, 20); + rr_finish(dottemp1); + } +#endif + if (options.ldifname[0]) + fprintf(ldifout, "\n"); +} + + +static void calc_checksum(int* num, int* sum) +{ + LDAPMessage* res = NULL; + LDAPMessage* m; + int ldaperr; + char* attr_list[2] = { "DNSserial", NULL }; + + *num = *sum = 0; + if ( ldaperr = ldap_search_s(ldap_con, options.searchbase[0] ? options.searchbase : NULL, LDAP_SCOPE_SUBTREE, "objectclass=DNSzone", attr_list, 0, &res)!=LDAP_SUCCESS ) + die_ldap(ldaperr); + for (m = ldap_first_entry(ldap_con, res); m; m = ldap_next_entry(ldap_con, m)) { + BerElement* ber = NULL; + char* attr = ldap_first_attribute(ldap_con, m, &ber); + if (attr) { + struct berval** bvals = ldap_get_values_len(ldap_con, m, attr); + if (bvals!=NULL) { + unsigned tmp; + if (sscanf(bvals[0]->bv_val, "%u", &tmp)==1) { + (*num)++; + *sum += tmp; + } + ldap_value_free_len(bvals); + } + } + ber_free(ber, 0); + } + ldap_msgfree(res); +} + + +static void read_dnszones(void) +{ + LDAPMessage* res = NULL; + LDAPMessage* m; + int ldaperr; + + if (tinyfile) + fprintf(tinyfile, "# Automatically generated by ldap2dns - DO NOT EDIT!\n"); + if ( (ldaperr = ldap_search_s(ldap_con, options.searchbase[0] ? options.searchbase : NULL, LDAP_SCOPE_SUBTREE, "objectclass=DNSzone", NULL, 0, &res))!=LDAP_SUCCESS ) + die_ldap(ldaperr); + for (m = ldap_first_entry(ldap_con, res); m; m = ldap_next_entry(ldap_con, m)) { + BerElement* ber = NULL; + char* attr; + char* dn; + int i, zonenames = 0; + char zdn[256][64]; + char ldif0; + + zone.serial = time_now; + zone.refresh = 10800; + zone.retry = 3600; + zone.expire = 604800; + zone.minimum = 86400; + zone.ttl = time_now; + zone.timestamp[0] = '\0'; + dn = ldap_get_dn(ldap_con, m); + if (options.ldifname[0]) + fprintf(ldifout, "dn: %s\n", dn); + for (attr = ldap_first_attribute(ldap_con, m, &ber); attr; attr = ldap_next_attribute(ldap_con, m, ber)) { + struct berval** bvals = ldap_get_values_len(ldap_con, m, attr); + if (bvals!=NULL) { + if (bvals[0] && bvals[0]->bv_len>0) { + if (strcasecmp(attr, "objectclass")==0 + || strcasecmp(attr, "DNSclass")==0 + || strcasecmp(attr, "DNStype")==0 + || strcasecmp(attr, "cn")==0) { + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, bvals[0]->bv_val); + } else if (strcasecmp(attr, "DNSzonename")==0) { + for (zonenames = 0; bvals[zonenames] && zonenames<256; zonenames++) { + if (sscanf(bvals[zonenames]->bv_val, "%64s", &zdn[zonenames])!=1) + zdn[zonenames][0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, zdn[zonenames]); + } + } else if (strcasecmp(attr, "DNSserial")==0) { + sscanf(bvals[0]->bv_val, "%u", &zone.serial); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.serial); + } else if (strcasecmp(attr, "DNSrefresh")==0) { + sscanf(bvals[0]->bv_val, "%u", &zone.refresh); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.refresh); + } else if (strcasecmp(attr, "DNSretry")==0) { + sscanf(bvals[0]->bv_val, "%u", &zone.retry); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.retry); + } else if (strcasecmp(attr, "DNSexpire")==0) { + sscanf(bvals[0]->bv_val, "%u", &zone.expire); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.expire); + } else if (strcasecmp(attr, "DNSminimum")==0) { + sscanf(bvals[0]->bv_val, "%u", &zone.minimum); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.minimum); + } else if (strcasecmp(attr, "DNSadminmailbox")==0) { + sscanf(bvals[0]->bv_val, "%64s", zone.adminmailbox); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, zone.adminmailbox); + } else if (strcasecmp(attr, "DNSzonemaster")==0) { + sscanf(bvals[0]->bv_val, "%64s", zone.zonemaster); + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, zone.zonemaster); + } else if (strcasecmp(attr, "DNSttl")==0) { + if (sscanf(bvals[0]->bv_val, "%d", &zone.ttl)!=1) + zone.ttl = time_now; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %d\n", attr, zone.ttl); + } else if (strcasecmp(attr, "DNStimestamp")==0) { + if (sscanf(bvals[0]->bv_val, "%16s", &zone.timestamp)!=1) + zone.timestamp[0] = '\0'; + if (options.ldifname[0]) + fprintf(ldifout, "%s: %s\n", attr, zone.timestamp); + } + } + ldap_value_free_len(bvals); + } + } + ldif0 = options.ldifname[0]; + for (i = 0; i0) + options.ldifname[0] = '\0'; + if (options.verbose&1) + printf("zonename: %s\n", zone.domainname); + if (options.output&4) { + char bindfilename[128]; + sprintf(bindfilename, "%s.db", zone.domainname); + if ( !(bindfile = fopen(bindfilename, "w")) ) + die_exit("Unable to open db-file for writing"); + } + write_zone(); + read_resourcerecords(dn); + if (bindfile) + fclose(bindfile); + if (options.verbose&2) + printf("\n"); + if (options.ldifname[0]) + fprintf(ldifout, "\n"); + } + options.ldifname[0] = ldif0; + free(dn); + } + ldap_msgfree(res); +} + + +int main(int argc, char** argv) +{ + int soa_numzones; + int soa_checksum; + + umask(022); + main_argc = argc; + main_argv = argv; + parse_options(); + if (options.is_daemon) { + if (options.is_daemon==1 && fork()) + exit(0); + /* lowest priority */ + nice(19); + } + set_datadir(); + for (;;) { + int ldaperr; + if ( !(ldap_con = ldap_init(options.hostname, options.port)) ) + die_exit("Unable to initialize connection to LDAP server"); + ldaperr = ldap_simple_bind_s(ldap_con, options.binddn, options.password); + if (ldaperr!=LDAP_SUCCESS) { + fprintf(stderr, "Warning - Could not connect to LDAP server %s:%d as '%s'\n", options.hostname, options.port, options.binddn); + sleep(options.update_iv); + continue; + } + if (options.is_daemon) { + int num, sum; + calc_checksum(&num, &sum); + if (num!=soa_numzones || sum!=soa_checksum) { + if (options.verbose&1) + printf("DNSserial has changed in LDAP zone(s)\n"); + soa_numzones = num; + soa_checksum = sum; + } else { + goto skip; + } + } +#if defined WITH_TINYDNS + if (options.output&1) { + fdcdb = open_trunc(tinydns_tempfile); + if (fdcdb == -1) die_exit("Unable to create 'data.tmp'"); + if (cdb_make_start(&cdb, fdcdb) == -1) die_exit("Unable to create 'data.tmp'"); + } +#endif + if (options.ldifname[0]) { + if (options.ldifname[0]=='-') + ldifout = stdout; + else + ldifout = fopen(options.ldifname, "w"); + if (!ldifout) + die_exit("Unable to open LDIF-file for writing"); + } + time(&time_now); + if ( options.output&2 && !(tinyfile = fopen(tinydns_textfile, "w")) ) + die_exit("Unable to open file 'data' for writing"); + read_dnszones(); + if (tinyfile) + fclose(tinyfile); + if (options.ldifname[0] && ldifout) + fclose(ldifout); +#if defined WITH_TINYDNS + if (options.output&1) { + if (cdb_make_finish(&cdb)==-1 || fsync(fdcdb)==-1 || close(fdcdb)==-1) + die_exit("Unable to create 'data.tmp'"); + if (rename(tinydns_tempfile, tinydns_datafile)==-1) + die_exit("Unable to move 'data.tmp' to 'data.cdb'"); + } +#endif + skip: + if ( (ldaperr = ldap_unbind_s(ldap_con))!=LDAP_SUCCESS ) + die_ldap(ldaperr); + if (options.is_daemon==0) + break; + sleep(options.update_iv); + } + return 0; +} + diff --git a/ldap2tinydns-conf b/ldap2tinydns-conf new file mode 100755 index 0000000..bdebb0c --- /dev/null +++ b/ldap2tinydns-conf @@ -0,0 +1,9 @@ +#!/bin/sh +mkdir ldap2tinydns +mkdir ldap2tinydns/env +echo "#!/bin/sh" > ldap2tinydns/run +echo "exec 2>&1" >> ldap2tinydns/run +echo "exec envdir ./env softlimit -d250000 /usr/bin/ldap2dns -u 59" >> ldap2tinydns/run +chmod 755 ldap2tinydns/run +echo "/var/tinydns/root" > ldap2tinydns/env/TINYDNSDIR +