#!/bin/bash
# ldapsync -- Synchronize the flat UNIX directory files with LDAP tables.
# Copyright (c) 2010 by The Regents of the University of California
# Author: Jim Carter <jimc@math.ucla.edu> -- 2010-07-21.  Contains bash-isms.

#   -v		Verbose output, announces tables being stuffed, and outcomes.
#   -a		Ignores the "last pushed" file and works on every table.
#		Without -a, only flat files modified after the "last pushed"
#		file are pushed out.  Filename: $opt_m/ldap.push
#   -B		Bail out after the first update failure.
#   -m "d1 d2"	Directory where the flat files are.  Can be a space separated
#		list; the first directory holding the flat file is used.  
#		Default: "/etc/master /etc"
#   -t "t1 t2"	Space separated list of table basenames to work on.  See below
#		for the default value (all of them).
#   -I file	Input file, overrides the auto-selected input file for the
#		first -t argument only.  (For debugging only.)
#   -p dir	Directory where persistent files are kept 
#		(/var/lib/ldap/sync.d).
#   -D uid=...	Distinguished Name of the root user.  
#		Default: uid=ldaproot,dc=math,dc=ucla,dc=edu
#   -y file	File containing the password of the -D user.  
#		Default: /etc/openldap/root.secret
#   -n		LDAP tables are not changed (for debugging).
#   -d 054	Debug option (in octal) to pass to ldaputil.  Default: 0.

# Test command line: 
#   ./ldapsync -v -D uid=ldaproot,dc=cft,dc=ca,dc=us

# $Header: /src/math/etc/ldap/RCS/ldapsync,v 1.2 2011/03/05 05:49:36 jimc Exp $

# $Log: ldapsync,v $
# Revision 1.2  2011/03/05 05:49:36  jimc
# Added -I switch to override input file.
# Added -n switch (passed to ldaputil) to not actually update anything.
# Added tables udata, hostgroup, netgroup, auto_home.
# The realm in root's distinguished name is computed from the host's FQDN,
#     not inferred through a case statement over predefined realms.
# Comments improved.
#
# Revision 1.1  2010/08/16 18:07:24  jimc
# Initial revision
#

# Directory where the flat files are:
opt_m="/etc/master /etc"
# List of tables to be synced (flat file basename is same for most of them):
opt_t="passwd shadow udata group aliases rpc services protocols networks hosts hostgroup netgroup auto_home"

# These tables are not used on typical Linux distros:
#	netmasks

# Location of persistent files (push timestamps) (must already exist)
opt_p=/var/lib/ldap/sync.d
# File containing root's password
opt_y=/etc/openldap/root.secret
# Preset additional flags
opt_v=0
opt_a=0

# Debug flags in octal, 0 if not debugging.  054 gives a good debug output.
opt_d=0

# DEBUG DEBUG DEBUG glonch the path, rape security!
# (not debugging)  export PATH=.:$PATH

# Root's Distinguished Name.  /etc/ldap.conf should have a paramater ROOTBINDDN.
# Fallback: It's assumed to be ldaproot@REALM converted to 
# the syntax of a D.N. e.g. uid=ldaproot,dc=math,dc=ucla,dc=edu
while [ 0 -eq 0 ] ; do
    if [ -r /etc/ldap.conf ] ; then
	opt_D=`awk 'BEGIN {IGNORECASE=1} $1 == "rootbinddn" {print $2}' /etc/ldap.conf`
	if [ -n "$opt_D" ] ; then break ; fi
    fi
		# Fallback if rootbinddn is not configured
    HOSTNAME=`uname -n`
    myip=`dig +short +search $HOSTNAME A | head -1`
    if [ -z "$myip" ] ; then break ; fi
    fqdn=`dig +short -x $myip`
    if [ -z "$fqdn" ] ; then break ; fi
    fqdn=${fqdn%.}
    uid_realm="uid=ldaproot.${fqdn#*.}"
    opt_D=`echo $uid_realm | sed -e 's/\./,dc=/g'`
    break
done

# Prints its arguments on stderr and exits.
function barf () {
    echo "$*" 1>&2
    exit 4
}

# Interpret command line args.  Very forgiving about switch letters...
while [ $# -gt 0 ] ; do
    case "X$1" in
    X-- )	break ; ;;
    X-? )	: ; ;;
    * )		break ; ;;
    esac
    case "X$2" in
    X- )	eval "opt_${1#-}='$2'" ; shift ; ;;	# Single - is a filename
							# Switch or -- follows
    X | X-* )	eval "opt_${1#-}=1" ; eval "txt_${1#-}=$1" ; ;; 
    * )		eval "opt_${1#-}='$2'" ; shift ; ;;	# Normal argument given
    esac
    shift
done

if [ ! -d $opt_p ] ; then
    echo "$opt_p does not exist, need to create" 1>&2
    exit 4
elif [ ! -w $opt_p ] ; then
    echo "$opt_p write permission denied" 1>&2
    exit 4
fi

for table in $opt_t ; do
		# Locate the input file.  
    tpath=''
		# -I can override the input filename (first table only).
    if [ -n "$opt_I" ] ; then
	tpath=$opt_I
	opt_I=''
    else
		# Special case input files for a few tables.
	case $table in
	    hostgroup )	tpath1=hostgroup.db ; ;;
	    auto_home )	tpath1=passwd ; ;;
	    * )		tpath1=$table ; ;;
	esac
		# Hunt for the file in the -m directories.
	for t in $opt_m ; do
	    tpath=$t/$tpath1
	    if [ -r $tpath ] ; then break ; fi
	done
    fi
    if [ ! -r $tpath ] ; then
	echo "Can't read $tpath, skipped" 1>&2
	continue
    fi
		# Bypass table if input hasn't changed since the last update.
    ppath=$opt_p/$table.push
    if [ -r $ppath -a ! $tpath -nt $ppath -a $opt_a = '0' ] ; then
	if [ $opt_v != '0' ] ; then echo "Don't need to push $table" ; fi
	continue
    fi
		# Update this table.
    if [ $opt_v != '0' ] ; then echo "Updating $table" ; fi
		# Command line args for ldaputil:
		#   -d	Debug output desired
		#   -D	Distinguished Name of root user
		#   -y	Filename containing root's special LDAP password
		#   -Z	Use TLS
		#   -k	Remove table entries not in the -i file
		#   -a	Check for and create realm container objects
		#   -f	Type of table
		#   -i	Flat file with table content
		#   -v	Verbose output (show statistics)
		#   -B	Bail after the first error is found
		#   -n	LDAP tables are not changed (for debugging)
    ldaputil $txt_v $txt_B $txt_n -d $opt_d -x -D $opt_D -y $opt_y -Z -k -a -f $table -i $tpath
    rc=$?
    if [ $rc != 0 ] ; then
	echo "ldaputil -f $table returns $rc, stopping here"
	break
    else
	if [ $opt_v != '0' ] ; then echo "    $table updated OK" ; fi
	echo "ldapsync -t $table at `date`" > $ppath
    fi
done

exit $rc
