# load some configuration variables
use vars qw(%root $base);

use Net::LDAP;
BEGIN {
	local $^W = 0;
	no strict;
	require Net::SSLeay;
	require Net::LDAPS;
};

if ($ENV{ROOT}) {
	$base = $ENV{ROOT};
} else {
	# try to bootstrap
	sub try_bootstrap_dir {
		my ($x)=(@_);
		return ((-d "$x/env" || -f "$x/env") && (-d "$x/root")) ? 1 : undef;
	}
	my $s = $ENV{SESSION} || 'ldapdns';
	if ($ENV{HOME} && &try_bootstrap_dir($ENV{HOME} . '/.ldapdns')) {
		# home directory is best
		$base = $ENV{HOME} . "/.$s";
	} elsif (&try_bootstrap_dir("/.$s")) {
		# root is good too (ick)
		$base = '/.ldapdns';
	} else {
		my $x = $0;
		if ($x !~ /^\//) {
			# need to relate from here
			my $cwd = `pwd`;
			chomp $cwd;
			$x = "$cwd/$x";
		}

		# remove last element
		$x =~ s/\/+[^\/]+$//;
		if (&try_bootstrap_dir($x)) {
			$base = $x;
		} elsif (&try_bootstrap_dir("$x/.config")) {
			$base = "$x/.config";
		} elsif (&try_bootstrap_dir("$x/config")) {
			$base = "$x/config";
		} else {
			# require the ENVironment variable
			die '$ROOT is unset';
		}
	}
}

if (-d "$base/env" && opendir(DIR, "$base/env")) {
	foreach my $i (readdir(DIR)) {
		next unless (-f "$base/env/$i" && open(FI, "<$base/env/$i"));
		$ENV{$i} = join('', <FI>);
		$ENV{$i} =~ s/[\r\n\t]+$//;
		$ENV{$i} =~ s/[\r\n\t]/ /g;
		close(FI);
	}
	closedir(DIR);
}

if (-d "$base/root" && opendir(DIR, "$base/root")) {
	foreach my $i (readdir(DIR)) {
		next unless (-f "$base/root/$i" && open(FI, "<$base/root/$i"));
		$root{$i} = join('', <FI>);
		$root{$i} =~ s/[\r\n\t]+$//;
		$root{$i} =~ s/[\r\n\t]/ /g;
		close(FI);
	}
	closedir(DIR);
}

sub get_ldap_conn
{
	die '$LDAP_HOST is unset' unless ($ENV{LDAP_HOST} || $ENV{LDAP_HOSTS});
	die '$LDAP_BINDDN is unset' unless ($ENV{LDAP_BINDDN});
	die 'root/password was unreachable/nonpresent' unless ($root{password});

	my $ldap;
	foreach my $i (split /[\ ;,]/, join(",", $ENV{LDAP_HOST}, $ENV{LDAP_HOSTS})) {
		my ($a, $p) = split /:/, $i;
		if (!defined($p) || $p < 1) {
			$ldap = Net::LDAPS->new($a);
		} else {
			$ldap = Net::LDAPS->new($a, port => $p);
		}
		if (!defined($ldap)) {
			if (!defined($p) || $p < 1) {
				$ldap = Net::LDAP->new($a);
			} else {
				$ldap = Net::LDAP->new($a, port => $p);
			}
			next unless (defined($ldap));

			my $ev = $ENV{"TRUSTED_NETWORK"};
			if ($ev) {
				print STDERR "TLS/SSL is not available... connecting anyway\n";
			} else {
				die 'set $TRUSTED_NETWORK to connect to LDAP server without using SSL/TLS';
			}
		}

		if ($ldap->bind(dn => $ENV{LDAP_BINDDN},
				password => $root{password})) {
			return $ldap;
		}

		undef $ldap;
	}
	die "Unable to reach any LDAP servers";
}
sub dc_add_prefix
{
	my ($ldap, $domain)=(@_);
	my @p = split /\./, lc($domain);
	my @dc = ();

	shift @p; # leave off last element...

	foreach my $i (reverse @p) {
		push(@dc, 'dc=' . $i);
		my $cur_dn = join(', ', reverse @dc);
		if ($ENV{LDAP_SUFFIX}) {
			$cur_dn .= ', ' . $ENV{LDAP_SUFFIX};
		}
		my $ldc = substr($dc[$#dc], 3);
		my $m = $ldap->add($cur_dn,
			attr => [
				dc => $ldc,
				objectClass => 'dnsDomain',
				objectClass => 'dcObject',
			]);

		if ($m->code) {
			next if ($m->error =~ /Already exists/);
			die("Failed to add suffix: $cur_dn    ", $m->error);
		}
	}

}
sub dc_domain
{
	my ($domain)=(@_);
	my @p = split /\./, lc($domain);
	return $p[0];
}
sub dn_domain
{
	my ($domain)=(@_);

	my @p = split /\./, lc($domain);

	my $dc = 'dc=' . join(', dc=', @p);
	if ($ENV{LDAP_SUFFIX}) {
		$dc .= ', ' . $ENV{LDAP_SUFFIX};
	}
	return $dc;
}
sub add_record
{
	my ($ldap, $dn, $attr, $add)=(@_);

	&dc_add_prefix($ldap, $dn);

	# search to see if the attribute exists
	if (defined($add)) {
		my $search = '';
		while (my ($k,$v)=each(%$add)) {
			$search .= "($k=$v)";
		}
		my $mesg = $ldap->search(base => &dn_domain($dn),
					scope=>'base',
					filter => '(&' . $search . ')');
		if ($mesg->count != 0) {
			return 1;
		}
	}

	$mesg = $ldap->add(&dn_domain($dn), attr => $attr);
	if ($mesg->code) {
		if ($mesg->error =~ /Already Exists/i && defined($add)) {
			$mesg = $ldap->modify(&dn_domain($dn), add => $add);
			die "Failed to modify entry: $dn    " . $mesg->error
				if ($mesg->code);
		} else {
			die "Failed to add entry: ", &dn_domain($dn),
			"    " . $mesg->error;
		}
	}
	return 1;
}
sub set_record
{
	my ($ldap, $dn, $attr, $modify)=(@_);

	my $mesg = $ldap->modify(&dn_domain($dn), replace => $modify);
	if ($mesg->code) {
		if ($mesg->error =~ /No such object/i) {
			return add_record($ldap, $dn, $attr);
		}
		die "Failed to modify entry: $dn    " . $mesg->error;
	}
	return 1;
}
sub dnsencode
{
	my ($z)=(@_);

	$z =~ s/\.$//;
	my $local = undef;
	if ($z =~ s/^([^@]+)\@//) {
		$local = pack('C', length($1)) . $1;
	}
	$z =~ s/\.([^\.]+)/pack('C', length($1)) . $1/sg;
	$z =~ s/^([^\.]+)/pack('C', length($1)) . $1/sg;
	$z .= pack('C', 0);
	if (defined($local)) {
		$z = $local . $z;
	}
	return $z;
}
sub pack_generic_record
{
	my ($rr, $format, @vars)=(@_);
	# this takes a specification in the form similar to perl's own
	# pack statement. it is very stupid, so go easy on it.

	# n short
	# N long
	# A dns-packed string (compressed)
	# a dns-encoded string

	my $outstr = '';
	while (length($format) > 0) {
		$format =~ s/^\s*//;
		if ($format =~ s/^\s*([nNaA])\s*([0-9]*)//) {
			my ($k, $v)=($1, $2);
			$v ||= 1;

			my @arg = ();
			for (my $i = 0; $i < $v; $i++) {
				my $x = shift @vars;
				push(@arg, $x);
			}

			if ($k eq 'a' || $k eq 'A') {
				if ($k eq 'A') {
					$outstr .= pack('C', 0xFF);
				}
				foreach my $j (@arg) {
					my $qs = dnsencode($j);
					if ($k eq 'a') {
						$qs =~ s/\xFF/\xFF\x00/sg;
					}
					$outstr .= $qs;
				}
			} else {
				my $qs = pack($k . $v, @arg);
				$qs =~ s/\xFF/\xFF\x00/sg;
				$outstr .= $qs;
			}
		}
	}
	if (!defined($rr) || $rr =~ /\D/) {
		die("rr must be numeric");
	}
	return pack('n', $rr) . length($outstr) . $outstr;
}

1;


syntax highlighted by Code2HTML, v. 0.9.1