#!/bin/bash
#
#  adduser			Interactive user adding program.
#
#  Copyright (C) 1996		Petri Mattila, Prihateam Networks
#				petri@prihateam.fi
#	
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2, or (at your option)
#  any later version.
#
# Changes:
#	220496	v0.01	Initial version
#	230496	v0.02	More checks, embolden summary
#	240496		Even more checks
#	250496 		Help with ?
#	040596	v0.03	Cleanups
#	050596	v0.04	Bug fixes, expire date checks
#	070596	v0.05	Iso-latin-1 names
#

## Defaults

# default groups
def_group="users"
def_other_groups=""

# default home directory
def_home_dir=/home/users

# default shell
def_shell=/bin/tcsh

# Default expiration date (mm/dd/yy)
def_expire=""

# default dates
def_pwd_min=0
def_pwd_max=90
def_pwd_warn=14
def_pwd_iact=14


# possible UIDs
uid_low=1000
uid_high=64000

# skel directory
skel=/etc/skel

# default mode for home directory
def_mode=711

# Regex, that the login name must meet, only ANSI characters
login_regex='^[0-9a-zA-Z_-]*$'

# Regex, that the user name must meet
# ANSI version
##name_regex='^[0-9a-zA-Z_-\ ]*$'
# ISO-LATIN-1 version
name_regex='^[0-9a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöùúûüýþÿ_-\ ]*$'

# set PATH
export PATH="/bin:/sbin:/usr/bin:/usr/sbin"

# Some special characters
case "$TERM" in
   vt*|ansi*|con*|xterm*|linux*)
	S='[1m'	# start embolden
	E='[m'	# end embolden
	;;
   *)	
	S=''
	E=''
	;;
esac


## Functions

check_root() {
	if test "$EUID" -ne 0
	then
		echo "You must be root to run this program."
		exit 1
	fi
}

check_user() {
	local usr pwd uid gid name home sh

	cat /etc/passwd | (
		while IFS=":" read usr pwd uid gid name home sh
		do
			if test "$1" = "${usr}"
			then
				return 1
			fi
		done
		return 0
	)
}

check_group() {
	local read grp pwd gid members

	cat /etc/group | (
		while IFS=":" read grp pwd gid members
		do
			if test "$1" = "${grp}"
			then
				return 1
			fi
		done
		return 0
	)
}

check_other_groups() {
	local grp check IFS

	check="$1"
	IFS=","

	set ${check}
	for grp
	do
		if check_group "${grp}"
		then
			echo "Group ${grp} does not exist."
			return 1
		fi
	done
	return 0
}		

check_uid() {
	local usr pwd uid gid name home sh
	
	cat /etc/passwd | (
		while IFS=":" read usr pwd uid gid name home sh
		do
			if test "$1" = "${uid}"
			then
				return 1
			fi
		done
		return 0
	)
}

read_yn() {
	local ans ynd
	
	ynd="$1"
	
	while :
	do
		read ans	
		case "${ans}" in
		      "") return ${ynd} ;;
		    [nN]) return 1 ;;
		    [yY]) return 0 ;;
		       *) echo -n "Y or N, please ? " ;;
		esac
	done
}

read_login() {
	echo
	while :
	do
		echo -n "Login: ${def_login:+[${def_login}] }"
		read login
		
		if test "${login}" = '?'
		then
			less /etc/passwd
			echo
			continue
		fi

		if test -z "${login}" -a -n "${def_login}"
		then
			login="${def_login}"
			echo "Using ${login}"
			return
		fi
		
		if test "${#login}" -gt 8
		then
			echo "Login must be at most 8 characters long"
			continue
		fi
		
		if test "${#login}" -lt 2
		then
			echo "Login must be at least 2 characters long"
			continue
		fi
		
		if ! expr "${login}" : "${login_regex}" &> /dev/null
		then
			echo "Please use letters, numbers and special characters _-,."
			continue
		fi
		
		if ! check_user "${login}"
		then
			echo "Username ${login} is already in use"
			continue
		fi
		
		def_login="${login}"
		return
	done
}

read_name () {
	echo
	while :
	do
		echo -n "Real name: ${def_name:+[${def_name}] }"
		read name
		
		if test "${name}" = '?'
		then
			less /etc/passwd
			echo
			continue
		fi

		if test -z "${name}" -a -n "${def_name}"
		then
			name="${def_name}"
			echo "Using ${name}"
		fi

		if test "${#name}" -gt 32
		then
			echo "Name should be at most 32 characters long"
			continue
		fi

		if ! expr "${name}" : "${name_regex}" &> /dev/null
		then
			echo "Please use letters, numbers, spaces and special characters ,._-"
			continue
		fi
		
		def_name="${name}"
		return
	done
}

read_home() {
	local x
	
	echo
	while :
	do
		echo -n "Home Directory: [${def_home_dir}/${login}] "
		read home
		
		if test -z "${home}"
		then
			home="${def_home_dir}/${login}"
			echo "Using ${home}"
		fi
		
		if ! expr "${home}" : '^[0-9a-zA-Z,._-\/]*$' &> /dev/null
		then
			echo "Please use letters, numbers, spaces and special characters ,._-/"
			continue
		fi
		
		x="$(basename ${home})"
		if test "${x}" != "${login}"
		then
			echo "Warning: you are about to use different login name and home directory."
		fi
		
		x="$(dirname ${home})"
		if ! test -d "${x}"
		then
			echo "Directory ${x} does not exist."
			echo "If you still want to use it, please make it manually."
			continue
		fi
		
		def_home_dir="${x}"
		return
	done
}

read_shell () {
	local x

	echo
	while :
	do
		echo -n "Shell: [${def_shell}] "
		read shell
		
		if test -z "${shell}"
		then
			shell="${def_shell}"
			echo "Using ${shell}"
		fi
		
		for x in $(cat /etc/shells)
		do
			if test "${x}" = "${shell}"
			then
				def_shell="${shell}"
				return
			fi
		done

		echo "Possible shells are:"
		cat /etc/shells
	done
}

read_group () {
	echo
	while :
	do
		echo -n "Group: [${def_group}] "
		read group
		
		if test -z "${group}"
		then
			group="${def_group}"
			echo "Using ${group}"
		fi
		
		if test "${group}" = '?'
		then
			less /etc/group
			echo
			continue
		fi

		if check_group "${group}"
		then
			echo "Group ${group} does not exist."
			continue
		fi
		
		def_group="${group}"
		return
	done
}

read_other_groups () {
	echo
	while :
	do
		echo -n "Other groups: [${def_og:-none}] "
		read other_groups
		
		if test "${other_groups}" = '?'
		then
			less /etc/group
			echo
			continue
		fi

		if test -z "${other_groups}"
		then
			if test -n "${def_og}"
			then
				other_groups="${def_og}"
				echo "Using ${other_groups}"
			else	
				echo "No other groups"
				return
			fi
		fi
		
		
		if ! check_other_groups "${other_groups}"
		then
			continue
		fi
		
		def_og="${other_groups}"
		return
	done
}

read_uid () {
	echo
	while :
	do
		echo -n "uid: [first free] "
		read uid
			
		if test -z "${uid}"
		then
			echo "Using first free UID."
			return
		fi
		
		if test "${uid}" = '?'
		then
			less /etc/passwd
			echo
			continue
		fi

		if ! expr "${uid}" : '^[0-9]+$' &> /dev/null
		then
			echo "Please use numbers only."
			continue
		fi
		if test "${uid}" -lt "${uid_low}"
		then
			echo "UID must be greater than ${uid_low}"
			continue
		fi
		if test "${uid}" -gt "${uid_high}"
		then
			echo "UID must be smaller than ${uid_high}"
			continue
		fi
		if ! check_uid "${uid}"
		then
			echo "UID ${uid} is already in use"
			continue
		fi
		
		return
	done
}

read_max_valid_days() {
	echo
	while :
	do
		echo -en "Maximum days between password changes: [${def_pwd_max}] "
		read max_days
		
		if test -z "${max_days}"
		then
			max_days="${def_pwd_max}"
			echo "Using ${max_days}"
			return
		fi
		
		if ! expr "${max_days}" : '^[0-9]+$' &> /dev/null
		then
			echo "Please use numbers only."
			continue
		fi
		if test "${max_days}" -lt 7
		then
			echo "Warning: you are using a value shorter than a week."
		fi
		
		def_pwd_max="${max_days}"
		return	
	done
}

read_min_valid_days() {
	echo
	while :
	do
		echo -en "Minimum days between password changes: [${def_pwd_min}] "
		read min_days
		
		if test -z "${min_days}"
		then
			min_days="${def_pwd_min}"
			echo "Using ${min_days}"
			return
		fi
		
		if ! expr "${min_days}" : '^[0-9]+$' &> /dev/null
		then
			echo "Please use numbers only."
			continue
		fi
		if test "${min_days}" -gt 7
		then
			echo "Warning: you are using a value longer than a week."
		fi
		
		def_pwd_min="${min_days}"
		return	
	done
}

read_warning_days() {
	echo
	while :
	do
		echo -en "Number of warning days before password expires: [${def_pwd_warn}] "
		read warn_days
		
		if test -z "${warn_days}"
		then
			warn_days="${def_pwd_warn}"
			echo "Using ${warn_days}"
		fi

		if ! expr "${warn_days}" : '^[0-9]+$' &> /dev/null
		then
			echo "Please use numbers only."
			continue
		fi
		if test "${warn_days}" -gt 14
		then
			echo "Warning: you are using a value longer than two week."
		fi
		
		def_pwd_warn="${warn_days}"
		return	
	done
}


read_inactive_days() {
	echo
	while :
	do
		echo -en "Number of usable days after expiration: [${def_pwd_iact}] "
		read iact_days
		
		if test -z "${iact_days}"
		then
			iact_days="${def_pwd_iact}"
			echo "Using ${iact_days}"
			return
		fi
		if ! expr "${iact_days}" : '^[0-9]+$' &> /dev/null
		then
			echo "Please use numbers only."
			continue
		fi
		if test "${iact_days}" -gt 14
		then
			echo "Warning: you are using a value that is more than two weeks."
		fi
		
		def_pwd_iact="${iact_days}"
		return	
	done
}

read_expire_date() {
	local ans
	
	echo
	while :
	do
		echo -en "Expire date of this account (mm/dd/yy): [${def_expire:-never}] "
		read ans
		
		if test -z "${ans}"
		then
			if test -z "${def_expire}"
			then
				ans="never"
			else
				ans="${def_expire}"
				echo "Using ${def_expire}"
			fi
		fi
		
		if test "${ans}" = "never"
		then
			echo "Account will never expire."
			def_expire=""
			expire=""
			return
		fi

		if ! expr "${ans}" : '^[0-9][0-9]/[0-9][0-9]/[0-9][0-9]$' &> /dev/null
		then
			echo "Please use format mm/dd/yy"
			continue
		fi
		
		if ! expire_date="$(date -d ${ans} '+%A, %B %d %Y')"
		then
			continue
		fi
		
		def_expire="${expire}"
		return	
	done
}

read_passwd_yn() {
	echo -en "\nDo you want to set password [Y/n] ? "
	if read_yn 0
	then
		set_pwd="YES"
	else
		set_pwd=""
	fi
}


print_values() {

clear
cat << EOM

Login:        ${S}${login}${E}
Group:        ${S}${group}${E}
Other groups: ${S}${other_groups:-[none]}${E}

Real Name:    ${S}${name}${E}

uid:          ${S}${uid:-[first free]}${E}
home:         ${S}${home}${E}
shell:        ${S}${shell}${E}

Account expiration date:                   ${S}${expire_date:-never}${E}
Minimum days between password changes:     ${S}${min_days}${E}
Maximum days between password changes:     ${S}${max_days}${E}
Number of usable days after expiration:    ${S}${iact_days}${E}
Number of warning days before expiration:  ${S}${warn_days}${E}

${S}${set_pwd:+Set password for this account.}${E}

EOM
}

set_user() {
	if ! useradd \
		-c "${name}" \
		-d "${home}" \
		-g "${group}" \
		-s "${shell}" \
		${expire:+-e ${expire}} \
		${uid:+-u ${uid}} \
		${other_groups:+-G ${other_groups}} \
		${login}
	then
		echo "Error ($?) in useradd...exiting..."
		exit 1
	fi
}

set_aging() {
	if ! passwd \
		-x ${max_days} \
		-n ${min_days} \
		-w ${warn_days} \
		-i ${iact_days} \
		${login}
	then
		echo "Error ($?) in setting password aging...exiting..." 
		exit 1
	fi
}

set_password() {
	if test -n "${set_pwd}"
	then
		echo
		passwd ${login}
		echo
	fi
}	

set_system() {
	if test -d "${home}"
	then
		echo "Directory ${home} already exists."
		echo "Skeleton files not copied."
		return
	fi
	
	echo -n "Copying skeleton files..."
	( 
	  mkdir ${home}
	  cd ${skel} && cp -af . ${home}
	  chmod ${def_mode} ${home}
	  chown -R ${login}:${group} ${home}
	)
	echo "done."

	## Add your own stuff here:
	echo -n "Setting up other files..."
	(
	  mailbox="/var/spool/mail/${login}"
	  touch ${mailbox}
	  chown "${login}:mail" ${mailbox}
	  chmod 600 ${mailbox}
	)
	echo "done."
}


read_values() {
	clear
	echo -e "\nPlease answer the following questions about the new user to be added."
	
	while :
	do
		read_login
		read_name
		read_group
		read_other_groups
		read_home
		read_shell
		read_uid
		read_expire_date
		read_max_valid_days
		read_min_valid_days
		read_warning_days
		read_inactive_days
		read_passwd_yn

		print_values
		
		echo -n "Is this correct [N/y] ? "
		read_yn 1 && return
	done
}


main() {
	check_root
	read_values
	set_user
	set_aging
	set_system
	set_password
}


## Run it 8-)
main

# End.
