#!/bin/bash
#
# @(#)$Id$
#
# mkgroup-dnlist - create a list of DNs in OpenSSL ONE_LINE format for use with
#   the OpenVPN verify-cn script for access control
#
# relies on the nikPerson schema and nikCertificateSubjectDN to be in RFC2253!
# AND enforced a Nikhef based certificate (with O=Nikhef) as an additional
# control - so this is Nikhef specific now
#

TARGET=/etc/openvpn/verify-cn.allowed

binddn="cn=agent-mortal-schaap,ou=Managers,dc=farmnet,dc=nikhef,dc=nl"
bindpwfile=/etc/openvpn/ldap.passwd
ldapurl="ldaps://ldap.nikhef.nl"
ldapbase="dc=farmnet,dc=nikhef,dc=nl"
minsize=500
minlines=8
attrDN=nikCertificateSubjectDN
attruid=uid
quoteduid=0
requiredaccess="
  schacUserStatus=urn:mace:terena.org:schac:userStatus:nikhef.nl:affiliation:active
"
groupaccess="
      cn=nikhefCSIRT,ou=DirectoryGroups,dc=farmnet,dc=nikhef,dc=nl
"

help() {
  cat <<EOF
$0 - create a list of DNs in OpenSSL ONE_LINE format for use with
  the OpenVPN verify-cn script for access control

Usage: $0 [-h|--help] [-Q] [-G role [-G ...]] [-u uid [-u ...]] 
  [-f targetfile] [-D binddn] [-P bindpwfile] [-H ldapurl]
  [-b ldapbase] [-R requiredaccessfilter] [--minlines n] [--minsize n]
  [--attrDN nikCertificateSubjectDN] [--attruid uid]

  Defaults:
    rolefilter = $groupaccess
    userfilter = empty
    targetfile = $TARGET
    ldapurl    = $ldapurl
    ldapbase   = $ldapbase
    binddn     = $binddn
    bindpwfile = $bindpwfile
    minlines   = $minlines
    minsize    = $minsize

  Options
    -Q         quoted-uid mode

  Example (for schaap):
  ./mkgroup-dnlist -G nDPFPrivilegedUsers -G cTbIdMPrivilegedUsers -u msalle -u jouker

EOF
}

# ############################################################################
#
while [ "$#" -gt 0 ]; do
  case "$1" in
  -G ) cmdgroupaccess="$cmdgroupaccess cn=$2,ou=DirectoryGroups,$ldapbase" ; shift 2 ;;
  -u ) cmduseraccess="$cmduseraccess uid=$2" ; shift 2 ;;
  -f ) TARGET="$2" ; shift 2 ;;
  -b ) ldapbase="$2" ; shift 2 ;;
  -P ) bindpwfile="$2" ; shift 2 ;;
  -D ) binddn="$2" ; shift 2 ;;
  -H ) ldapurl="$2" ; shift 2 ;;
  -R ) requiredaccess="$2" ; shift 2 ;;
  -Q ) quoteduid=1 ; shift ;;
  --minlines ) minlines="$2" ; shift 2 ;;
  --minsize )  minsize="$2" ; shift 2 ;;
  --attrDN )   attrDN="$2" ; shift 2 ;;
  --attruid )  attruid="$2" ; shift 2 ;;
  -h | --help ) help ; exit 0 ;;
  -* ) echo "Unknown option $1" >&2 ; exit 1 ;;
  -- ) break ;;
  esac
done

groupaccess=${cmdgroupaccess:-$groupaccess}
useraccess=${cmduseraccess:-""}

# ############################################################################
#
#
DATE=`date '+%Y%m%d-%H%M%S'`
for i in $groupaccess ; do filter="$filter(memberOf=$i)" ; done
for i in $useraccess ; do filter="$filter($i)" ; done
if [ "$filter" = "" ]; then
  echo "Nobody in access filter - this is wrong" >&2 
  exit 1
fi

for i in $requiredaccess ; do accfilter="$accfilter($i)" ; done
ldapfilter="(&($attrDN=*)$accfilter(|$filter))"

if [ ! -s "${bindpwfile}" ]; then
  echo "No or empty password file $bindpwfile, exiting" >&2
  exit 1
fi
output=`ldapsearch -x -LLL -o ldif-wrap=no -y "${bindpwfile}" -D "${binddn}" -W -H "${ldapurl}" -b "${ldapbase}" "${ldapfilter}" $attruid $attrDN | egrep -v "^dn: "`

if [ $? -ne 0 ]
then
    echo "Error while retrieving LDAP info. Aborting" 1>&2
    exit 1
fi

# ############################################################################
#
size=`echo -ne "$output" | wc -c`
lines=`echo -ne "$output" | wc -l`

if [ $size -lt $minsize ]; then
  echo "ERROR - size of output is too small ($size), aborting" >&2
  exit 1
fi
if [ $lines -lt $minlines ]; then
  echo "ERROR - number of entries is too small ($lines), aborting" >&2
  exit 1
fi

tempdir=`dirname "${TARGET}"`
tempfile=`mktemp -p "${tempdir}" allowed.XXXXXX`

if [ $quoteduid -eq 0 ]; then
  echo "${output}" | grep -v '^#' | awk -F, '
  BEGIN   {
    dnlen=length("'$attrDN':  ");
  }
  /^'$attrDN':/ && /,O=[Nn]ikhef/ {
    for (i=NF; i>1; i--) printf("/%s", $i);
    printf("/%s", substr($1,dnlen,255));
    printf("\n", user);
  }
  ' | sort > "${tempfile}"
else
  echo "${output}" | grep -v '^#' | awk -F, '
  BEGIN   {
    uidlen=length("'$attruid':  ");
    dnlen=length("'$attrDN':  ");
  }
  /^'$attruid':/ {
    user=substr($0,uidlen,255);
  }
  /^'$attrDN':/ && /,O=[Nn]ikhef/ {
    printf("\"", user);
    for (i=NF; i>1; i--) printf("/%s", $i);
    printf("/%s", substr($1,dnlen,255));
    printf("\" %s\n", user);
  }
  ' | sort > "${tempfile}"
fi

chmod 0644 "${tempfile}"

# ############################################################################
#
if [ ! -s "${tempfile}" ]; then
  echo "Empty $tempfile, exiting" >&2
  exit 1
fi

lines=`cat "${tempfile}" | wc -l `
size=`cat "${tempfile}" | wc -c`
if [ $lines -lt $minlines ]; then
  echo "ERROR - number of entries is too small (in $tempfile), aborting" >&2
  exit 1
fi
if [ $size -lt $minsize ]; then
  echo "ERROR - size of output is too small (in $tempfile), aborting" >&2
  exit 1
fi

# ############################################################################
# only update/rewrite the target file if it has changes, to keep mtime 
#
cmp "${tempfile}" "${TARGET}" >/dev/null 2>&1
if [ $? -eq 1 -o ! -e "${TARGET}" ]; then
  # file changed, and no trouble
  [ -e "${TARGET}" ] && cp -p "${TARGET}" "${TARGET}".backup.$DATE
  cat "${tempfile}" > "${TARGET}"
  chmod 0644 "${TARGET}"
  lines=`wc -l "${TARGET}"`
  echo "Updated $TARGET on $DATE to have $lines lines"
fi

# ############################################################################
#
rm "${tempfile}"
exit 0
