Hack #9. Sync LDAP Data with NIS

Run a script out of cron to help with a graceful transformation to LDAP.

An NIS-to-LDAP migration is a nontrivial event in any environment. If the switch were as simple as moving data from one place to another, most organizations would've done it by now. The reality in many production environments, large and small, is that some applications (and even appliances) do not yet support LDAP or don't support LDAP to the extent that we would like. Eventually, most places come to terms with LDAP's limitations and implement a "phase in" approach, which involves using LDAP where it is fully supported but keeping NIS around for those things that require it.

In those environments where the authentication source will be NIS for some legacy systems and LDAP for those newer systems that support it, the challenge becomes keeping the data synchronized between NIS and LDAP. Over the past couple of years, I have found several tools that attempt to solve this problem. One is a C program that, though it is amazingly generic, requires a whole bunch of flags that will look quite cryptic to some system administrators. Another solution consisted of a suite of tools that attempted to do too much and weren't very configurable. I was unable to make friends with these tools, as they seemed to make assumptions about my environment that would never be true.

In the end, I did find a Perl script online that had a very elementary structure that anyone could understand. It was clearly written and well commented, but unfortunately it wasn't actually written to complete the job it claimed to do. Rather than continuing my search, I broke down and decided that, by using this Perl script as a "good enough" skeleton, I could get it to work for my needs. Here is my Perl hack for taking data residing in LDAP and creating NIS maps.

The Code

	#!/usr/bin/perl
	use Net::LDAP;

	## CONFIG
	my $server = "ldap-server";
	my $base = "dc=example,dc=com";
	my $bind = "uid=ldap2nis,ou=People,dc=example,dc=com";
	my $bindpw = 'password';
	my $groupf = "group";
	my $passwf = "passwd";
	my $buildyp = "false";
	## CONNECT
	my $ldap = Net::LDAP->new($server, onerror => 'die' );
	$ldaps = $ldap->start_tls(verify=>'none') or die "Couldn't start tls: $@\n";
	$ldap->bind( dn => $bind, password => $bindpw) or die "Bind failed: $@\n";

	## PRINT PASSWORD FILE[1]

	my $res = $ldap->search(
				base => $base,
				scope => 'sub', # entire tree
				timelimit => 600,
				filter => '(&(objectClass=posixAccount))',
				attrs => ['uid', 'uidNumber', 'gidNumber', 'gecos',
	'homeDirectory', 'loginShell', 'userPassword'],
	);

	open(PASSWORD, ">$passwf");
	while (my $entry = $res->shift_entry) {
		  (my $uid = $entry->get_value('uid')) =~ s/:/./g;
		  (my $uidnum = $entry->get_value('uidNumber')) =~ s/:/./g;
		  (my $gidnum = $entry->get_value('gidNumber')) =~ s/:/./g;
		  (my $gecos = $entry->get_value('gecos')) =~ s/:/./g;
		  (my $homedir = $entry->get_value('homeDirectory')) =~ s/:/./g;
		  (my $shell = $entry->get_value('loginShell')) =~ s/:/./g;
		  (my $up = $entry->get_value('userPassword')) =~ s/:/./g;
		  if (index($up, "{crypt}") != -1) {
			  $up = substr($up, 7);
		  }else{
		  	  $up = crypt($up, "bR"); 
		  } 
		  $passrecord = join(':',$uid,$up,$uidnum,$gidnum,$gecos,$homedir,$shell); 
		  print PASSWORD "$passrecord\n";
	}
	close(PASSWORD);
	chmod(0600, $passwf);

	## PRINT GROUP FILE
	my $res = $ldap->search(
		base => $base,
		scope => 'sub', # entire tree
		timelimit => 600,
		filter => '(&(objectClass=posixGroup))',
		attrs => ['cn', 'gidNumber', 'memberuid'],
		
	);

	open(GROUP, ">$groupf");
	while (my $entry = $res->shift_entry) {
	(my $grname = $entry->get_value('cn')) =~ s/:/./g;
	my $grpass = "*";
	(my $grnum = $entry->get_value('gidNumber')) =~ s/:/./g;
	(@members = $entry->get_value('memberuid')) =~ s/:/./g;
	
	if($#members >= 0) {
		$memusers = join(',',@members);
	}else{
		$memusers = "";
	}
	
	$grprecord = join(':', $grname,$grpass,$grnum,$memusers);
	print GROUP "$grprecord\n";
  }
  close(GROUP);
  chmod(0600, $groupf);

Running the Code

Assuming you're storing encrypted password strings in your NIS passwd map, this script, which I call dap2nis, should be configured using the variables near the top to bind as an account that has read access to the userPassword attribute for the user entries. Otherwise, you'll get nothing back for that attribute, and your resulting NIS maps won't be useful as authentication sources when they're pushed out.

You can test the code by first making a test directory and making the script executable. Next, be sure to configure it to talk to your LDAP server using the config variables near the top of the script. Once that's all done, running the program should produce passwd and group files in the test directory. These should be valid NIS maps, ready to be pushed out. However, before taking that step, you should run a diff against the current NIS maps to check for any anomalies that reflect errors in the map generation rather than simple changes that have occurred in LDAP but are not yet reflected in NIS. Here are a few commands from a hypothetical test session:

	# ./dap2nis
	# ypcat passwd > yppass.out
	# ypcat group > ypgrp.out
	# diff yppass.out passwd
	# diff ypgrp.out group

The only output you should see from the diff commands should be valid changes that have not yet been propagated to NIS. Once you've tested thoroughly, you can put the script in root's crontab file, with an entry like this:

	*/7 * * * * /var/adm/bin/dap2nis

This entry says to run the script every seven minutes, all the time, every day.

The only thing the dap2nis script does not do in its current incarnation is actually perform a cd var/yp/; make, which would normally push out the NIS maps. Depending on your environment, you may not want this in this particular script. Instead, you might put in another cron job that pushes out NIS maps every four minutes, which would allow for changes to be pushed out automatically to reflect changes that were made to maps not covered by this script. Creating a separate cron job to push out the NIS maps also ensures that if this script is ever retired or pulled out of production, your maps will still get pushed out in an automated fashion.

See Also



[1] Deep Thought, the computer that determined that the meaning of life, the universe, and everything else is indeed 42, was at the top at the top of the Galactic Supercomputer Rankings for seven million years and may have run Linux.

Get Linux Server Hacks, Volume Two now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.