#!/bin/sh
######################################################################
#
# $Id: webjob-create-profile,v 1.89 2012/01/07 08:01:17 mavrik Exp $
#
######################################################################
#
# Copyright 2007-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Create a WebJob profile.
#
######################################################################

IFS=' 	
'

PROGRAM=`basename ${0}`

umask 027

######################################################################
#
# VerifyPassword
#
######################################################################

VerifyPassword()
{
  MY_ARG_CLIENT_ID=${1}
  MY_ARG_PASSWORD=${2}
  MY_ARG_HTUSERS_ENTRY=${3}

  MY_TARGET_PASSWORD_HASH=`echo "${MY_ARG_HTUSERS_ENTRY}" | sed "s/^${MY_ARG_CLIENT_ID}://; s/^LOCKED//;"`

  case "${MY_TARGET_PASSWORD_HASH}" in
  \$apr1\$*)
    MY_ACTUAL_PASSWORD_HASH=`perl -e 'use Crypt::PasswdMD5; print apache_md5_crypt(q('${MY_ARG_PASSWORD}'), q('${MY_TARGET_PASSWORD_HASH}')), "\n";'`
    ;;
  [0-9A-Za-z./][0-9A-Za-z./]*)
    MY_ACTUAL_PASSWORD_HASH=`perl -e 'print crypt(q('${MY_ARG_PASSWORD}'), q('${MY_TARGET_PASSWORD_HASH}')), "\n";'`
    ;;
  *)
    return 1
    ;;
  esac

  if [ X"${MY_ACTUAL_PASSWORD_HASH}" = X"${MY_TARGET_PASSWORD_HASH}" ] ; then
    return 0
  fi

  return 1
}

######################################################################
#
# VerifyPrograms
#
######################################################################

VerifyPrograms()
{
  MY_PROGRAMS="
htpasswd
perl
webjob-cfg-get-kvps
webjob-cfg-update-list
webjob-dsvtool
webjob-jqd-create-queue
webjob-jqd-list-members
webjob-jqd-update-group
webjob-mldbm-create-client
webjob-mldbm-get-config-kvps
webjob-mldbm-set-config-kvps
"
  MY_DIRS=`echo "${PATH}" | sed 's/:/ /g;'`

  for MY_PROGRAM in ${MY_PROGRAMS} ; do
    MY_PROGRAM_FOUND=0
    for MY_DIR in ${MY_DIRS} ; do
      MY_TEST_PATH="${MY_DIR}/${MY_PROGRAM}"
      if [ -x ${MY_TEST_PATH} ] ; then
        MY_PROGRAM_FOUND=1
        break;
      fi
    done
    if [ ${MY_PROGRAM_FOUND} -ne 1 ] ; then
      echo "${PROGRAM}: Error='Unable to locate an executable instance of ${MY_PROGRAM} in the current PATH (${PATH}).'" 1>&2
      exit 2
    fi
  done
}

######################################################################
#
# Usage
#
######################################################################

Usage()
{
  echo 1>&2
  echo "${PROGRAM} [-C webjob-client-home] [-e {plain|base64}] [-g jqd-group[,jqd-group[...]]] [-H webjob-home] [-h host] [-i ip] [-n webjob-server-cn] [-o option[,option[,...]]] [-P password] [-r registration-code] [-S webjob-server-home] [-s webjob-server] [-T timeslot] -p {unix|winx} -c client [key=value [...]]" 1>&2
  echo 1>&2
  exit 1
}

######################################################################
#
# Main
#
######################################################################

CLIENT_HOSTNAME=""

CLIENT_IP=""

ENCODING="base64"

JQD_GROUP_LIST=""

OPTIONS=""

PASSWORD=""

PASSWORD_SPECIFIED="0"

PLATFORM=""

TIMESLOT=""

WEBJOB_CLIENT_HOME=""

WEBJOB_REGISTRATION_CODE=""

WEBJOB_SERVER=""

WEBJOB_SERVER_CLIENT_HOME="/usr/local/webjob"

WEBJOB_SERVER_CN=""

WEBJOB_SERVER_HOME="/var/webjob"

while getopts "C:c:e:g:H:h:i:n:o:P:p:r:S:s:T:" OPTION ; do
  case "${OPTION}" in
  C)
    WEBJOB_CLIENT_HOME="${OPTARG}"
    ;;
  c)
    CLIENT_ID="${OPTARG}"
    ;;
  e)
    ENCODING="${OPTARG}"
    ;;
  g)
    JQD_GROUP_LIST="${OPTARG}"
    ;;
  H)
    WEBJOB_HOME="${OPTARG}"
    ;;
  h)
    CLIENT_HOSTNAME="${OPTARG}"
    ;;
  i)
    CLIENT_IP="${OPTARG}"
    ;;
  n)
    WEBJOB_SERVER_CN="${OPTARG}"
    ;;
  o)
    OPTIONS="${OPTARG}"
    ;;
  P)
    PASSWORD="${OPTARG}"
    PASSWORD_SPECIFIED="1"
    ;;
  p)
    PLATFORM="${OPTARG}"
    ;;
  r)
    WEBJOB_REGISTRATION_CODE="${OPTARG}"
    ;;
  S)
    WEBJOB_SERVER_HOME="${OPTARG}"
    ;;
  s)
    WEBJOB_SERVER="${OPTARG}"
    ;;
  T)
    TIMESLOT="${OPTARG}"
    ;;
  *)
    Usage
    ;;
  esac
done

shift `expr ${OPTIND} - 1`

if [ -z "${CLIENT_ID}" ] ; then
  Usage
fi

case "${ENCODING}" in
[Pp][Ll][Aa][Ii][Nn])
  ENCODING="plain"
  ;;
[Bb][Aa][Ss][Ee]64)
  ENCODING="base64"
  ;;
*)
  Usage
  ;;
esac

case "${PLATFORM}" in
[Uu][Nn][Ii][Xx])
  OS_CLASS="UNIX"
  PLATFORM="unix"
  if [ -z "${WEBJOB_CLIENT_HOME}" ] ; then
    WEBJOB_CLIENT_HOME='/usr/local/webjob'
  fi
  ;;
[Ww][Ii][Nn][Xx])
  OS_CLASS="WINX"
  PLATFORM="winx"
  if [ -z "${WEBJOB_CLIENT_HOME}" ] ; then
    WEBJOB_CLIENT_HOME='c:\\webjob'
  fi
  ;;
*)
  Usage
  ;;
esac

if [ -n "${TIMESLOT}" ] ; then
  echo "${TIMESLOT}" | egrep "^([0-9]|[1-2][0-9])$" > /dev/null 2>&1
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Timeslot (${TIMESLOT}) does not pass muster. A value in the range [0-29] is required.'" 1>&2
    exit 2
  fi
fi

if [ -n "${WEBJOB_REGISTRATION_CODE}" ] ; then
  perl -e 'exit(($ARGV[0] =~ /^[0-9A-Za-z]{5}(-[0-9A-Za-z]{5}){2,11}$/) ? 0 : 1);' "${WEBJOB_REGISTRATION_CODE}" > /dev/null 2>&1
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='The registration code (${WEBJOB_REGISTRATION_CODE}) not pass muster.'" 1>&2
    exit 2
  fi
fi

PATH=${WEBJOB_HOME=/usr/local/webjob}/bin:${PATH} ; export PATH

VerifyPrograms

if [ ! -d "${WEBJOB_SERVER_HOME}" ] ; then
  echo "${PROGRAM}: Error='Server home directory (${WEBJOB_SERVER_HOME}) does not exist.'" 1>&2
  exit 2
fi

WEBJOB_SERVER_CONFIG_FILE="${WEBJOB_SERVER_HOME}/config/server.cfg"
if [ ! -f "${WEBJOB_SERVER_CONFIG_FILE}" ] ; then
  echo "${PROGRAM}: Error='Server config file (${WEBJOB_SERVER_CONFIG_FILE}) does not exist.'" 1>&2
  exit 2
fi

HOSTNAME=`webjob-cfg-get-kvps -t webjob.server -f ${WEBJOB_SERVER_CONFIG_FILE} -o BeQuiet,ValuesOnly Hostname`
if [ -z "${HOSTNAME}" ] ; then
  HOSTNAME=`hostname`
fi

if [ -z "${WEBJOB_SERVER}" ] ; then
  WEBJOB_SERVER="${HOSTNAME}"
  if [ -z "${WEBJOB_SERVER}" ] ; then
    echo "${PROGRAM}: Error='Unable to determine hostname. Use the \"-s\" option to specify the server hostname or IP address.'" 1>&2
    exit 2
  fi
fi

if [ -z "${WEBJOB_SERVER_CN}" ] ; then
  WEBJOB_SERVER_CN="${HOSTNAME}"
  if [ -z "${WEBJOB_SERVER_CN}" ] ; then
    echo "${PROGRAM}: Error='Unable to determine hostname. Use the \"-n\" option to specify the server common name.'" 1>&2
    exit 2
  fi
fi

WWW_OWNER=`webjob-cfg-get-kvps -t webjob.server -f ${WEBJOB_SERVER_CONFIG_FILE} -o BeQuiet,ValuesOnly ApacheOwner`
if [ -z "${WWW_OWNER}" ] ; then
  WWW_OWNER="www"
fi
WWW_OWNER_TEST=`awk -F: '{print $1}' /etc/passwd | egrep "${WWW_OWNER}"`
if [ -z "${WWW_OWNER_TEST}" ] ; then
  echo "${PROGRAM}: Error='Unable to verify that \"${WWW_OWNER}\" is a valid owner. Make sure that ApacheOwner is set properly in ${WEBJOB_SERVER_CONFIG_FILE}.'" 1>&2
  exit 2
fi

WWW_GROUP=`webjob-cfg-get-kvps -t webjob.server -f ${WEBJOB_SERVER_CONFIG_FILE} -o BeQuiet,ValuesOnly ApacheGroup`
if [ -z "${WWW_GROUP}" ] ; then
  WWW_GROUP="www"
fi
WWW_GROUP_TEST=`awk -F: '{print $1}' /etc/group | egrep "${WWW_GROUP}"`
if [ -z "${WWW_GROUP_TEST}" ] ; then
  echo "${PROGRAM}: Error='Unable to verify that \"${WWW_GROUP}\" is a valid group. Make sure that ApacheGroup is set properly in ${WEBJOB_SERVER_CONFIG_FILE}.'" 1>&2
  exit 2
fi

if [ -z "${CLIENT_HOSTNAME}" ] ; then
  CLIENT_HOSTNAME=`echo "${CLIENT_ID}" | sed 's/^client_//;' | awk -F. '{print $1}'`
  if [ -z "${CLIENT_HOSTNAME}" ] ; then
    echo "${PROGRAM}: Error='Unable to derive hostname. Use the \"-h\" option to specify the client hostname.'" 1>&2
    exit 2
  fi
fi

######################################################################
#
# Ensure that all prerequisites exist. Any error is considered fatal
# since it implies that the server's configuration is not complete.
#
######################################################################

HTUSERS_FILE="${WEBJOB_SERVER_HOME}/config/apache/ht-client"
if [ ! -f "${HTUSERS_FILE}" ] ; then
  echo "${PROGRAM}: Error='Password file (${HTUSERS_FILE}) does not exist.'" 1>&2
  exit 2
fi

NPH_CONFIG_CONFIG_DIR="${WEBJOB_SERVER_HOME}/config/nph-config"
if [ ! -d "${NPH_CONFIG_CONFIG_DIR}" ] ; then
  echo "${PROGRAM}: Error='Override directory (${NPH_CONFIG_CONFIG_DIR}) does not exist.'" 1>&2
  exit 2
fi

NPH_CONFIG_HOST_ACCESS_FILE="${WEBJOB_SERVER_HOME}/config/nph-config/nph-config-hosts.access"
if [ ! -f "${NPH_CONFIG_HOST_ACCESS_FILE}" ] ; then
  echo "${PROGRAM}: Error='Host access file (${NPH_CONFIG_HOST_ACCESS_FILE}) for nph-config.cgi does not exist.'" 1>&2
  exit 2
fi

NPH_WEBJOB_CONFIG_DIR="${WEBJOB_SERVER_HOME}/config/nph-webjob"
if [ ! -d "${NPH_WEBJOB_CONFIG_DIR}" ] ; then
  echo "${PROGRAM}: Error='Override directory (${NPH_WEBJOB_CONFIG_DIR}) does not exist.'" 1>&2
  exit 2
fi

NPH_WEBJOB_HOST_ACCESS_FILE="${WEBJOB_SERVER_HOME}/config/nph-webjob/nph-webjob-hosts.access"
if [ ! -f "${NPH_WEBJOB_HOST_ACCESS_FILE}" ] ; then
  echo "${PROGRAM}: Error='Host access file (${NPH_WEBJOB_HOST_ACCESS_FILE}) for nph-webjob.cgi does not exist.'" 1>&2
  exit 2
fi

WEBJOB_CLIENT_DB="${WEBJOB_SERVER_HOME}/db/mldbm/client.db"
if [ ! -f "${WEBJOB_CLIENT_DB}" ] ; then
  echo "${PROGRAM}: Error='Client DB (${WEBJOB_CLIENT_DB}) does not exist.'" 1>&2
  exit 2
fi

WEBJOB_GROUP_FILE="${WEBJOB_SERVER_HOME}/config/jqd/groups"
if [ ! -f "${WEBJOB_GROUP_FILE}" ] ; then
  echo "${PROGRAM}: Error='Group file (${WEBJOB_GROUP_FILE}) does not exist.'" 1>&2
  exit 2
fi

WEBJOB_JQD_DIR="${WEBJOB_SERVER_HOME}/spool/jqd"
if [ ! -d "${WEBJOB_JQD_DIR}" ] ; then
  echo "${PROGRAM}: Error='JQD directory (${WEBJOB_JQD_DIR}) does not exist.'" 1>&2
  exit 2
fi

WEBJOB_PROFILES_DIR="${WEBJOB_SERVER_HOME}/profiles"
if [ ! -d "${WEBJOB_PROFILES_DIR}" ] ; then
  echo "${PROGRAM}: Error='Profiles directory (${WEBJOB_PROFILES_DIR}) does not exist.'" 1>&2
  exit 2
fi

WEBJOB_SKEL_DIR="${WEBJOB_SERVER_HOME}/config/skel"
if [ ! -d "${WEBJOB_SKEL_DIR}" ] ; then
  echo "${PROGRAM}: Error='Skeleton directory (${WEBJOB_SKEL_DIR}) does not exist.'" 1>&2
  exit 2
fi

  ####################################################################
  #
  # Parse the options list.
  #
  ####################################################################

  FORCE="0"

  FORCE_PASSWORD="0"

  LOCAL_PROFILE="0"

  LOCK_PROFILE="0"

  for OPTION in `echo "${OPTIONS}" | sed 's/,/ /g;' | tr '[A-Z]' '[a-z]'` ; do
    case "${OPTION}" in
    force)
      FORCE="1"
      ;;
    forcepassword)
      FORCE_PASSWORD="1"
      ;;
    localprofile)
      LOCAL_PROFILE="1"
      ;;
    lockprofile)
      LOCK_PROFILE="1"
      ;;
    esac
  done

######################################################################
#
# Do some work.
#
######################################################################

MY_ERROR_FLAG=0

  ####################################################################
  #
  # Do a sanity check on the client ID.
  #
  ####################################################################

  MY_CLIENT_ID_OK=`echo ${CLIENT_ID} | perl -n -e 'if ($_ =~ /^(?:[A-Za-z](?:(?:[0-9A-Za-z]|[_-](?=[^.]))){0,62})(?:[.][A-Za-z](?:(?:[0-9A-Za-z]|[_-](?=[^.]))){0,62}){0,127}$/) { print "pass\n"; } else { print "fail\n"; }'`

  if [ x"${MY_CLIENT_ID_OK}" != x"pass" ] ; then
    echo "${PROGRAM}: Error='Client ID (${CLIENT_ID}) does not pass muster. The profile will not be created.'" 1>&2
    exit 2
  fi

  ####################################################################
  #
  # Conditionally generate a password, and update the htusers file.
  # If the htusers file already has an entry for this client and no
  # password was specified and the force option is disabled, abort --
  # blindly updating the htusers file with a new password could lock
  # out existing clients. If a password was specified, then assume
  # that user wants the htusers file to be updated.
  #
  ####################################################################

  if [ ${PASSWORD_SPECIFIED} -eq 0 -a -n "${WEBJOB_PASSWORD}" ] ; then
    PASSWORD="${WEBJOB_PASSWORD}"
    PASSWORD_SPECIFIED="1"
  fi

  if [ ${PASSWORD_SPECIFIED} -eq 1 ] ; then
    if [ -n "${PASSWORD}" ] ; then
      echo "${PASSWORD}" | egrep "^0:[0-9A-Za-z/+]+={0,2}$" > /dev/null 2>&1
      if [ $? -eq 0 ] ; then
        MY_PASSWORD=`echo "${PASSWORD}" | perl -p -e 'use MIME::Base64; chomp; $_=~ s/^0://; $_=decode_base64($_);'`
      else
        MY_PASSWORD=${PASSWORD}
      fi
    else
      : # Empty passwords are not allowed. This error will be caught below.
    fi
  else
    MY_PASSWORD=`webjob-dsvtool -p`
  fi

  echo "${MY_PASSWORD}" | egrep "^[0-9A-Za-z/+]{8,}$" > /dev/null 2>&1
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='The password for ${CLIENT_ID} does not pass muster. The profile will not be created.'" 1>&2
    exit 2
  fi

  MY_HTUSERS_ENTRY=`egrep "^${CLIENT_ID}:" ${HTUSERS_FILE} 2> /dev/null`
  if [ -n "${MY_HTUSERS_ENTRY}" ] ; then
    if [ ${FORCE_PASSWORD} -eq 0 ] ; then
      if [ ${PASSWORD_SPECIFIED} -eq 1 ] ; then
        VerifyPassword "${CLIENT_ID}" "${MY_PASSWORD}" "${MY_HTUSERS_ENTRY}"
        if [ $? -ne 0 ] ; then
          echo "${PROGRAM}: Error='Client (${CLIENT_ID}) already has an htusers entry, and the supplied password is not correct.'" 1>&2
          exit 4 # XER_PasswordRequired
        fi
      else
        echo "${PROGRAM}: Error='Client (${CLIENT_ID}) already has an htusers entry. To continue, either supply the current password with the \"-P\" option or use the \"ForcePassword\" option to force a password update.'" 1>&2
        exit 4 # XER_PasswordRequired
      fi
    fi
  fi

  echo "Updating ${HTUSERS_FILE}"

  if [ ${LOCK_PROFILE} -eq 1 ] ; then
    MY_NEW_HTUSERS_ENTRY=`htpasswd -b -m -n ${CLIENT_ID} ${MY_PASSWORD} | sed "s/^${CLIENT_ID}:/${CLIENT_ID}:LOCKED/;"`
  else
    MY_NEW_HTUSERS_ENTRY=`htpasswd -b -m -n ${CLIENT_ID} ${MY_PASSWORD}`
  fi
  if [ -n "${MY_NEW_HTUSERS_ENTRY}" ] ; then
    webjob-cfg-set-kvps -d : -f ${HTUSERS_FILE} "${MY_NEW_HTUSERS_ENTRY}"
    if [ $? -ne 0 ] ; then
      echo "${PROGRAM}: Error='Unable to set password for ${CLIENT_ID}.'" 1>&2
      MY_ERROR_FLAG=1
    fi
  fi

  if [ x"${ENCODING}" = x"base64" ] ; then
    MY_ENCODED_PASSWORD=`echo "${MY_PASSWORD}" | perl -p -e 'use MIME::Base64; chomp; $_="0:".encode_base64($_);'`
  else
    MY_ENCODED_PASSWORD=${MY_PASSWORD}
  fi

  ####################################################################
  #
  # Create the profile tree.
  #
  ####################################################################

  MY_PROFILE_DIR="${WEBJOB_PROFILES_DIR}/${CLIENT_ID}"

  MY_ETC_DIR="${MY_PROFILE_DIR}/etc"

  MY_COMMANDS_DIR="${MY_PROFILE_DIR}/commands"

  MY_CONFIGS_DIR="${MY_PROFILE_DIR}/configs"

  MY_CONTENT_DIR="${MY_PROFILE_DIR}/content"

  MY_DSV_DIR="${MY_PROFILE_DIR}/dsv"

  MY_HOOKS_DIR="${MY_PROFILE_DIR}/hooks"

  MY_RUN_DIR="${MY_PROFILE_DIR}/run"

  MY_TRIGGERS_DIR="${MY_PROFILE_DIR}/triggers"

  MY_WORKERS_DIR="${MY_PROFILE_DIR}/workers"

  MY_DIRS="
${MY_PROFILE_DIR}
${MY_ETC_DIR}
${MY_COMMANDS_DIR}
${MY_CONFIGS_DIR}
${MY_CONTENT_DIR}
${MY_HOOKS_DIR}
${MY_TRIGGERS_DIR}
${MY_WORKERS_DIR}
"

  if [ ${LOCAL_PROFILE} -eq 1 ] ; then
    MY_DIRS="${MY_DIRS} ${MY_DSV_DIR} ${MY_RUN_DIR}"
  fi

  for MY_DIR in ${MY_DIRS} ; do
    if [ ! -d "${MY_DIR}" ] ; then
      echo "Creating ${MY_DIR}"
      mkdir -p ${MY_DIR}
      if [ $? -ne 0 ] ; then
        echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_DIR}.'" 1>&2
        MY_ERROR_FLAG=1
      fi
    else
      echo "Skipping ${MY_DIR} (already exists)"
    fi
  done

  if [ ${LOCAL_PROFILE} -eq 1 ] ; then
    chmod 700 ${MY_RUN_DIR}
  fi

  ####################################################################
  #
  # Create symlinks to support local profiles.
  #
  ####################################################################

  if [ ${LOCAL_PROFILE} -eq 1 ] ; then
    for MY_DIR in "bin" ; do
      MY_SYMLINK="${MY_PROFILE_DIR}/${MY_DIR}"
      if [ ! -L "${MY_SYMLINK}" ] ; then
        echo "Creating ${MY_SYMLINK}"
        if [ ${FORCE} -eq 1 ] ; then
          MY_LN_OPTS="-f"
        else
          MY_LN_OPTS=""
        fi
        ln -s ${MY_LN_OPTS} ${WEBJOB_SERVER_CLIENT_HOME}/${MY_DIR} ${MY_SYMLINK}
        if [ $? -ne 0 ] ; then
          echo "${PROGRAM}: Error='Encountered a problem while creating config symlink for ${CLIENT_ID}.'" 1>&2
          MY_ERROR_FLAG=1
        fi
      fi
    done
  fi

  ####################################################################
  #
  # Create config files.
  #
  ####################################################################

  WEBJOB_CLIENT_HOME_ESCAPED=`echo "${WEBJOB_CLIENT_HOME}" | perl -p -e 's,(\x5c),$1$1,g;'`

  for MY_EXT in "-a" "-b" ; do
    for MY_NAME in "upload" ; do
      MY_FILE=${MY_NAME}${MY_EXT}.cfg
      MY_CONFIG_FILE="${MY_ETC_DIR}/${MY_FILE}"
      MY_SKEL_CONFIG_FILE="${WEBJOB_SKEL_DIR}/${MY_NAME}-${PLATFORM}${MY_EXT}.cfg"
      if [ -f "${MY_SKEL_CONFIG_FILE}" ] ; then
        if [ ! -f ${MY_CONFIG_FILE} -o ${FORCE} -eq 1 -o ${FORCE_PASSWORD} -eq 1 ] ; then
          echo "Creating ${MY_CONFIG_FILE}"
          # Note that %sample_client_home must be replaced before %sample_client to prevent unwanted substitutions.
          # Note that %sample_server_cn   must be replaced before %sample_server to prevent unwanted substitutions.
          sed \
            -e "s#%sample_password#${MY_ENCODED_PASSWORD}#g;" \
            -e "s#%sample_client_home#${WEBJOB_CLIENT_HOME_ESCAPED}#g;" \
            -e "s#%sample_client#${CLIENT_ID}#g;" \
            -e "s#%sample_server_cn#${WEBJOB_SERVER_CN}#g;" \
            -e "s#%sample_server#${WEBJOB_SERVER}#g;" \
            ${MY_SKEL_CONFIG_FILE} > ${MY_CONFIG_FILE}
          if [ $? -ne 0 ] ; then
            echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_FILE} for ${CLIENT_ID}.'" 1>&2
            MY_ERROR_FLAG=1
          fi
        else
          echo "Skipping ${MY_CONFIG_FILE} (already exists)"
        fi
      fi
    done
  done

  for MY_EXT in "-a" "-b" ; do
    for MY_NAME in "upload" ; do
      MY_FILE=${MY_NAME}${MY_EXT}.ptr
      MY_CONFIG_FILE="${MY_ETC_DIR}/${MY_FILE}"
      MY_SKEL_CONFIG_FILE="${WEBJOB_SKEL_DIR}/${MY_NAME}-${PLATFORM}${MY_EXT}.ptr"
      if [ -f "${MY_SKEL_CONFIG_FILE}" ] ; then
        if [ ! -f ${MY_CONFIG_FILE} -o ${FORCE} -eq 1 ] ; then
          echo "Creating ${MY_CONFIG_FILE}"
          sed \
            -e "s#%sample_client_home#${WEBJOB_CLIENT_HOME_ESCAPED}#g;" \
            ${MY_SKEL_CONFIG_FILE} > ${MY_CONFIG_FILE}
          if [ $? -ne 0 ] ; then
            echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_FILE} for ${CLIENT_ID}.'" 1>&2
            MY_ERROR_FLAG=1
          fi
        else
          echo "Skipping ${MY_CONFIG_FILE} (already exists)"
        fi
      fi
    done
  done

  MY_NAME="upload"
  MY_FILE="${MY_NAME}.cfg"
  MY_CONFIG_FILE="${MY_ETC_DIR}/${MY_FILE}"
  CONFIG_POINTER=`webjob-mldbm-get-config-kvps -d ${WEBJOB_CLIENT_DB} -c ${CLIENT_ID} -o BeQuiet,ValuesOnly ConfigPointer | tr '[a-z]' '[A-Z]'`
  if [ X"${CONFIG_POINTER}" = X"B" ] ; then
    MY_POINTER_FILE="${MY_ETC_DIR}/${MY_NAME}-b.ptr"
  else
    CONFIG_POINTER="A"
    MY_POINTER_FILE="${MY_ETC_DIR}/${MY_NAME}-a.ptr"
  fi
  if [ ! -f ${MY_CONFIG_FILE} -o ${FORCE} -eq 1 ] ; then
    echo "Creating ${MY_CONFIG_FILE}"
    cp -p ${MY_POINTER_FILE} ${MY_CONFIG_FILE}
    if [ $? -ne 0 ] ; then
      echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_FILE} for ${CLIENT_ID}.'" 1>&2
      MY_ERROR_FLAG=1
    fi
  else
    echo "Skipping ${MY_CONFIG_FILE} (already exists)"
  fi

  ####################################################################
  #
  # Find the next timeslot, and write it to a timeslot file. Use a
  # default value of zero for local profiles. Also, do not use local
  # timeslot values when computing the timeslot for a given (remote)
  # client.
  #
  ####################################################################

  if [ ${LOCAL_PROFILE} -eq 1 -a -z "${TIMESLOT}" ] ; then
    TIMESLOT=0
  fi

  if [ -n "${TIMESLOT}" ] ; then
    MY_TIMESLOT=${TIMESLOT}
  else
    MY_TIMESLOT_FILES=""
    for MY_CLIENT in `webjob-jqd-list-members -G ${WEBJOB_GROUP_FILE} -i %all -e %all_local` ; do
      MY_TIMESLOT_FILE="${WEBJOB_PROFILES_DIR}/${MY_CLIENT}/workers/hourly.cfg"
      if [ -f "${MY_TIMESLOT_FILE}" ] ; then
        MY_TIMESLOT_FILES="${MY_TIMESLOT_FILES} ${MY_TIMESLOT_FILE}"
      fi
    done
    if [ -n "${MY_TIMESLOT_FILES}" ] ; then
      MY_TIMESLOT=`\
      {
        egrep -h '^Minute=[0-9][0-9]?$' ${MY_TIMESLOT_FILES} 2> /dev/null | awk -F= '{print $2}' 2> /dev/null;
        perl -e 'for ($i=0; $i<=29; $i++){printf("%d\n", $i)}' ;
      } | sort | uniq -c | sort -n -k 1 -k 2 | sed '1!d;' | awk '{print $2}'`
    fi
    echo "${MY_TIMESLOT}" | egrep "^([0-9]|[1-2][0-9])$" > /dev/null 2>&1
    if [ $? -ne 0 ] ; then
      MY_TIMESLOT="0"
    fi
  fi

  MY_DAY_OF_WEEK="*"
  MY_DAY_OF_MONTH="*"
  MY_MONTH="*"
  for MY_NAME in "hourly" "daily" ; do
    if [ X"${MY_NAME}" = X"hourly" ] ; then
      MY_MINUTE="${MY_TIMESLOT}"
      MY_HOUR="*"
    else
      MY_MINUTE=`expr ${MY_TIMESLOT} + 30`
      MY_HOUR="0"
    fi
    MY_FILE="${MY_NAME}.cfg"
    MY_WORKERS_FILE="${MY_WORKERS_DIR}/${MY_FILE}"
    MY_SKEL_WORKERS_FILE="${WEBJOB_SKEL_DIR}/worker-${MY_NAME}-${PLATFORM}.cfg"
    if [ -f "${MY_SKEL_WORKERS_FILE}" ] ; then
      if [ ! -f ${MY_WORKERS_FILE} -o ${FORCE} -eq 1 ] ; then
        echo "Creating ${MY_WORKERS_FILE}"
        # Note that %wjcd_home is intended for the end system, but it conflicts with %w, so it must be handled as a special case.
        sed \
          -e "s#%wjcd_home#%escaped_wjcd_home#g;" \
          -e "s#%M#${MY_MINUTE}#g;" \
          -e "s#%H#${MY_HOUR}#g;" \
          -e "s#%w#${MY_DAY_OF_WEEK}#g;" \
          -e "s#%d#${MY_DAY_OF_MONTH}#g;" \
          -e "s#%m#${MY_MONTH}#g;" \
          -e "s#%escaped_wjcd_home#%wjcd_home#g;" \
          ${MY_SKEL_WORKERS_FILE} > ${MY_WORKERS_FILE}
        if [ $? -ne 0 ] ; then
          echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_FILE} for ${CLIENT_ID}.'" 1>&2
          MY_ERROR_FLAG=1
        fi
      else
        echo "Skipping ${MY_WORKERS_FILE} (already exists)"
      fi
    fi
  done

  ####################################################################
  #
  # Change the group so the WWW user can access the client's files.
  #
  ####################################################################

  chgrp -f -R ${WWW_GROUP} ${MY_PROFILE_DIR}
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Encountered a problem while setting group ownerships for ${CLIENT_ID}.'" 1>&2
    MY_ERROR_FLAG=1
  fi

  ####################################################################
  #
  # Add the client to the 'all' group. If the profile is local, also
  # add the client to the 'all_local' group. Update any other groups
  # specified by the user.
  #
  ####################################################################

  echo "Updating ${WEBJOB_GROUP_FILE}"

  webjob-jqd-update-group -a -G ${WEBJOB_GROUP_FILE} -m merge -g all ${CLIENT_ID}
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Encountered a problem while updating the \"all\" group in ${WEBJOB_GROUP_FILE} for ${CLIENT_ID}.'" 1>&2
    MY_ERROR_FLAG=1
  fi

  if [ ${LOCAL_PROFILE} -eq 1 ] ; then
    webjob-jqd-update-group -a -G ${WEBJOB_GROUP_FILE} -m merge -g all_local ${CLIENT_ID}
    if [ $? -ne 0 ] ; then
      echo "${PROGRAM}: Error='Encountered a problem while updating the \"all_local\" group in ${WEBJOB_GROUP_FILE} for ${CLIENT_ID}.'" 1>&2
      MY_ERROR_FLAG=1
    fi
  fi

  for MY_JQD_GROUP in `echo ${JQD_GROUP_LIST} | sed 's/,/ /g;'` ; do
    webjob-jqd-update-group -a -G ${WEBJOB_GROUP_FILE} -m merge -g ${MY_JQD_GROUP} ${CLIENT_ID}
    if [ $? -ne 0 ] ; then
      echo "${PROGRAM}: Error='Encountered a problem while updating the \"${MY_JQD_GROUP}\" group in ${WEBJOB_GROUP_FILE} for ${CLIENT_ID}.'" 1>&2
      MY_ERROR_FLAG=1
    fi
  done

  ####################################################################
  #
  # Create the client's job queue.
  #
  ####################################################################

  if [ -d ${WEBJOB_JQD_DIR}/${CLIENT_ID} ] ; then
    echo "Updating ${WEBJOB_JQD_DIR}/${CLIENT_ID}"
  else
    echo "Creating ${WEBJOB_JQD_DIR}/${CLIENT_ID}"
  fi

  MY_OPTS="-q"
  if [ ${LOCK_PROFILE} -eq 1 ] ; then
    MY_OPTS="${MY_OPTS} -l"
  fi
  webjob-jqd-create-queue ${MY_OPTS} -d ${WEBJOB_JQD_DIR} -o ${WWW_OWNER} ${CLIENT_ID}
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Encountered a problem creating ${WEBJOB_JQD_DIR} for ${CLIENT_ID}.'" 1>&2
    MY_ERROR_FLAG=1
  fi

  ####################################################################
  #
  # Lower the umask. The next few tasks need to create world-readable
  # directories and files.
  #
  ####################################################################

  umask 022

  ####################################################################
  #
  # Create nph-config override directories.
  #
  ####################################################################

  for MY_NAME in "clients" ; do
    MY_OVERRIDE_DIR="${NPH_CONFIG_CONFIG_DIR}/${MY_NAME}/${CLIENT_ID}"
    if [ ! -d "${MY_OVERRIDE_DIR}" ] ; then
      echo "Creating ${MY_OVERRIDE_DIR}"
      mkdir -p ${MY_OVERRIDE_DIR}
      if [ $? -ne 0 ] ; then
        echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_OVERRIDE_DIR}.'" 1>&2
        MY_ERROR_FLAG=1
      fi
    else
      echo "Skipping ${MY_OVERRIDE_DIR} (already exists)"
    fi
  done

  ####################################################################
  #
  # Create nph-webjob override directories.
  #
  ####################################################################

  for MY_NAME in "clients" "queues" ; do
    MY_OVERRIDE_DIR="${NPH_WEBJOB_CONFIG_DIR}/${MY_NAME}/${CLIENT_ID}"
    if [ ! -d "${MY_OVERRIDE_DIR}" ] ; then
      echo "Creating ${MY_OVERRIDE_DIR}"
      mkdir -p ${MY_OVERRIDE_DIR}
      if [ $? -ne 0 ] ; then
        echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_OVERRIDE_DIR}.'" 1>&2
        MY_ERROR_FLAG=1
      fi
    else
      echo "Skipping ${MY_OVERRIDE_DIR} (already exists)"
    fi
  done

  ####################################################################
  #
  # Create nph-webjob queue override config file.
  #
  ####################################################################

  MY_FILE="${NPH_WEBJOB_CONFIG_DIR}/queues/${CLIENT_ID}/nph-webjob.cfg"
  if [ ! -f ${MY_FILE} -o ${FORCE} -eq 1 ] ; then
    echo "Creating ${MY_FILE}"
    cat > ${MY_FILE} <<EOF
JobQueueActive=Y
JobQueuePqActiveLimit=5
JobQueuePqAnswerLimit=5
JobQueueSqActiveLimit=1
JobQueueSqAnswerLimit=1
EOF
    if [ $? -ne 0 ] ; then
      echo "${PROGRAM}: Error='Encountered a problem while creating ${MY_FILE} for ${CLIENT_ID}.'" 1>&2
      MY_ERROR_FLAG=1
    fi
  else
    echo "Skipping ${MY_FILE} (already exists)"
  fi

  ####################################################################
  #
  # Restore the default umask.
  #
  ####################################################################

  umask 027

  ####################################################################
  #
  # Add the client to client.db, and set any client-specific KVPs.
  #
  ####################################################################

  echo "Updating ${WEBJOB_CLIENT_DB}"

  webjob-mldbm-create-client -q -d ${WEBJOB_CLIENT_DB} ${CLIENT_ID}
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Encountered a problem while updating ${WEBJOB_CLIENT_DB} for ${CLIENT_ID}.'" 1>&2
    MY_ERROR_FLAG=1
  fi

  MY_SECONDS=`perl -e 'print time();'`

  MY_KVPS="'ClientId=${CLIENT_ID}' 'ConfigPointer=${CONFIG_POINTER}' 'Created=${MY_SECONDS}' 'Hostname=${CLIENT_HOSTNAME}' 'OsClass=${OS_CLASS}' 'Password=${MY_ENCODED_PASSWORD}' 'Timeslot=${MY_TIMESLOT}' 'WebJobHome=${WEBJOB_CLIENT_HOME}'"
  if [ -n "${CLIENT_IP}" ] ; then
    MY_KVPS="${MY_KVPS} 'Ipv4Address=${CLIENT_IP}' 'HostAccessList=${CLIENT_IP}/32'"
  fi
  if [ ${LOCK_PROFILE} -eq 1 ] ; then
    MY_KVPS="${MY_KVPS} 'ProfileState=locked'"
  else
    MY_KVPS="${MY_KVPS} 'ProfileState=active'"
  fi
  if [ -n "${WEBJOB_REGISTRATION_CODE}" ] ; then
    MY_KVPS="${MY_KVPS} 'RegistrationCode=${WEBJOB_REGISTRATION_CODE}'"
  fi
  if [ ${#} -gt 0 ] ; then
    for MY_KVP in "${@}" ; do # NOTE: Quotes are required to retain embedded spaces.
      MY_KVPS="${MY_KVPS} '${MY_KVP}'"
    done
  fi
  eval webjob-mldbm-set-config-kvps -d ${WEBJOB_CLIENT_DB} -c ${CLIENT_ID} ${MY_KVPS}
  if [ $? -ne 0 ] ; then
    echo "${PROGRAM}: Error='Encountered a problem while updating ${WEBJOB_CLIENT_DB} for ${CLIENT_ID}.'" 1>&2
    MY_ERROR_FLAG=1
  fi

  ####################################################################
  #
  # Conditionally update the host access lists.
  #
  ####################################################################

  HOST_ACCESS_LIST=`webjob-mldbm-get-config-kvps -d ${WEBJOB_CLIENT_DB} -c ${CLIENT_ID} -o BeQuiet,ValuesOnly HostAccessList | sed 's/,/ /g;'`
  if [ -n "${HOST_ACCESS_LIST}" ] ; then
    for HOST_ACCESS_FILE in ${NPH_CONFIG_HOST_ACCESS_FILE} ${NPH_WEBJOB_HOST_ACCESS_FILE} ; do
      webjob-cfg-update-list -a -m merge -f ${HOST_ACCESS_FILE} -k ${CLIENT_ID} ${HOST_ACCESS_LIST}
      if [ $? -ne 0 ] ; then
        echo "${PROGRAM}: Error='Encountered a problem while updating ${HOST_ACCESS_FILE} for ${CLIENT_ID}.'" 1>&2
        MY_ERROR_FLAG=1
      fi
    done
  fi

######################################################################
#
# Shutdown and go home.
#
######################################################################

if [ ${MY_ERROR_FLAG} -eq 1 ] ; then
  echo "${PROGRAM}: Error='Encountered one or more errors. The profile for ${CLIENT_ID} is incomplete.'" 1>&2
  exit 3 # XER_PartialSuccess
else
  exit 0 # XER_OK
fi
