#!/usr/local/bin/cbsd
#v11.1.12
MYARG="mode"
MYOPTARG="backupfile device file key master_size"
MYDESC="cbsd geli helper"
ADDHELP="
${H3_COLOR}Description:${N0_COLOR}

Script for manage GELI-based crypto-FS.

${H3_COLOR}# Options:${N0_COLOR}

 ${N2_COLOR}backupfile=${N0_COLOR}  - alternative path to backup for init for storing backup;
 ${N2_COLOR}device=${N0_COLOR}      - path to device;
 ${N2_COLOR}file=${N0_COLOR}        - path to image;
 ${N2_COLOR}key=${N0_COLOR}         - path to master key file;
 ${N2_COLOR}mode=${N0_COLOR}        - available action:
                * initmaster
                * init
                * attach
                * detach
                * info

${H3_COLOR}# Working with GELI assumed the following scenario:${N0_COLOR}

  Encryption be only selected data. Since the encryption operation is dependent primarily on your CPU resource consumption will always be higher,
  and I/O — lower than when working with data on the encrypted partition. Instead of encrypting the entire jail, you can choose to encrypt only important path inside the jail,
  for example: /root, /home/user, /var/db/mysql, /usr/local/etc etc, while the logs, database, or all of the third party software that you installed via pkg/ports — can be left unencrypted.
  Also, besides the encryption of these jails, you may want to encrypt the directory \$workdir/.rssh, which keeps cbsd ID_RSA keys remote machines and \$workdir/.ssh which is your own key to the user cbsd.
  Each encrypted device is assigned their own password/passphrase to decrypt. However, they will all be stored on the system image of CBSD, which in turn will be encrypted master- key.
  In other words, as you trust your own server, if necessary, to include one or another encrypted partition, CBSD itself will apply the correct key when using of encrypted image.
  At the same keys for their decryption is stored in a file, which in turn is encrypted with your password and activated manually.

${H3_COLOR}# For common understanding, we denote the following conventions:${N0_COLOR}

  \${dbdir} - directory \$workdir/var/db, where workdir — is path to initialized of CBSD. For example: /usr/jails/var/db
  MASTER_GELI - your password encrypted image, which CBSD will look for clues to the rest geli attach operations. Located on the file system as a file \${dbdir}/master_geli.img

${W1_COLOR}!Attention!${N0_COLOR} Removing or defacing this file would make it impossible to connect all remaining crypted images automatically and mount these images will
only be possible if you remember what the image which password you assigned to the  cbsd geli mode=init file= stage. In this regard, make regular backups of the file.

  key directory -  mounted image of MASTER_GELI file into \${dbdir}/geli directory. To avoid possible damage due to incorrect unmount (server failure, loss of power, improper shutdown),
   the resource is connected in Read-only mode and re-mount in rw only for modifications.

${H3_COLOR}# CBSD GELI initialization${N0_COLOR}

To start working with an encrypted partition on the node, you must create MASTER_GELI file and come up with a password for it. Use a strong password, because it gives access to all other keys. To use the command of initialization: cbsd geli mode=initmaster.

% cbsd geli mode=initmaster

Secondary running this command will give the question to enter the password, or the command will work without any output, if the file already initialized and mounted automatically at \$dbdir/geli. Successful initialization file contents of the file mounted to the directory \$dbdir/geli, let the rest of the scripts access to keys for other images.

Note:

By default, the image is created for storage of keys equal to 5Mb (master_size= params) with AES-XTS algorithm. This setting is in the file \${workdir}/etc/defaults/geli.conf and can be changed via the corresponding entries in \$workdir/etc/geli.conf

${H3_COLOR}# Initialization geli-images for CBSD use${N0_COLOR}

GELI Initialization of image should be before you start it up and mounting the write data. To initialize, use the command:

% cbsd geli mode=init file=/path/to/file

When you first start geli init file /path/to/file will perform the following steps:

 1) if path begins with \$workdir (eg, CBSD installed in /usr/jails, and cbsd init executed with settings mode=init file=/usr/jails/jails-system/jail1/root.img), then logic of script path \$workdir removed out of the way and this location is used to generate key file name via md5. Such an operation is needed for those cases, if you migrate from one jail to another node and various \$workdir — if not cut workdir, then md5 key names will be different. If the path is a file that is not in \$workdir, the path remains unchanged.
 2) cbsd geli script prompts you to password for the file in the file = variable and stores it in a file \$dbdir/geli/md5_from_result_of_step_1
 3) Executing of geli attach with this key, and displays the name of the resulting device, such as:

    /dev/md1.eli

    you can already format and fill the data that will be encrypted.

As is the case with initmaster, if you start a second init file — the system without question it presents, if the key already exists.

Using jails fstab to list of crypted images and   mount points

For example, that jail jail1 on startup mount crypted image in directory /home/web inside the environment, on the local fstab file of jails stored the name of the image file and the file system type sets to geli, for example:

webhome.img /home/web geli rw 0 0

In this case, the file webhome.img must me store in the directory /usr/jails/jails-system/jail1/ (when workdir = /usr/jails).

If you store the crypto image is not in /usr/jails/jails-system/\$jname/ directory, it is necessary to prescribe the full path starting with /. for example:

/data/images/webhome.img /home/web geli rw 0 0

However, storage for .img files of jails /usr/jails/jails-system/\$jname/ is recommended, because the directory jails-system/\$jname participates in the operations of migration, jimport and jexport. Otherwise, you will have to carry their own image file.

Keep in mind, when after booting server you have no init the CBSD geli (cbsd geli mode=initmaster), which after the password has access to the keys to decrypt the images and jails is starting, CBSD can not find a key to decrypt the image and the crypto directory is not be mounted. Therefore, as a rule, crypto jails should have a flag autostart is off(astart=0).

${H3_COLOR}# Examples${N0_COLOR}

cbsd geli mode=initmaster

"

EXTHELP="wf_geli"

. ${subrdir}/nc.subr
oname=
name=
omaster_size=
master_size=
. ${cbsdinit}

[ -n "${name}" ] && oname="${name}"

. ${system}
. ${mdtools}

readconf geli.conf

[ -n "${oname}" ] && name="${oname}"
[ -z "${name}" ] && name="master"

MASTER_FILE="${dbdir}/${name}_geli.img"
TRAP=""

change_passphrase()
{
	local _passfile _dev _pw="1" _npw
	local oldmodes=$( ${STTY_CMD} -g )

	[ -z "${1}" -o -z "${2}" ] && err 1 "${N1_COLOR}change_passphrase need for arguments${N0_COLOR}"
	_dev="${1}"
	_passfile="${2}"

	${ECHO} "${N1_COLOR}Enter passphrase for ${gelidev}: ${N0_COLOR}"

	while [ "${_pw}" != "${_npw}" ]; do
		printf "${BOLD}New Password: ${N0_COLOR}"
		${STTY_CMD} -echo
		read _pw
		printf "\n${BOLD}Retype New Password: ${N0_COLOR}"
		read _npw
		[ "${_pw}" = "${_npw}" ] || ${ECHO} "${N1_COLOR}Mismatch; try again, EOF to quit.${N0_COLOR}"
	done

	echo "${_pw}" > ${_passfile}
	${STTY_CMD} $oldmodes
	${GELI_CMD} init -J ${_passfile} ${_dev}
}

# $1 - path to dev/file
# printf md5-based key path
get_key()
{
	local _res _md5
	[ -z "${1}" ] && err 1 "${N1_COLOR}get_key need for arguments${N0_COLOR}"
	_res=$( echo $1 | ${SED_CMD} s:^${workdir}/::g )
	_md5=$( ${miscdir}/cbsd_md5 "${_res}" )
	printf "${gelidir}/${_md5}"
}


create_key()
{
	local _res _dev _keypath TRAP="true;"

	_dev="${1}"
	[ -z "${1}" ] && err 1 "${N1_COLOR}create_key need for arguments${N0_COLOR}"
	[ ! -f "${1}" -a ! -c "${1}" ] && err 1 "${N1_COLOR}No such device or file:${N2_COLOR} ${1}${N0_COLOR}"

	# if resource is file, mdconfiging them and work with it as device
	if [ -f "${1}" ]; then
		_res=$( eval find_md_by_img ${1} )
		if [ -n "${_res}" ]; then
			_md="${_res}"
		else
			_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${file} )
			[ $? -ne 0 ] && err 1 "${N1_COLOR}Error for: ${N2_COLOR}${MDCONFIG_CMD} -a -t vnode -f ${file}${N0_COLOR}"
			TRAP="${TRAP} ${MDCONFIG_CMD} -d -u ${_md};"
			trap "${TRAP}" HUP INT ABRT BUS TERM EXIT
		fi
		_dev="/dev/${_md}"
		#remove workdir part in path if exist
		_keypath=$( get_key "${1}" )
	else
		_keypath=$( get_key "${_dev}" )
	fi

	if [ ! -r "${_keypath}" ]; then
		trap "${TRAP} mountmaster_file \"ro\";" HUP INT ABRT BUS TERM EXIT
		mountmaster_file "rw"
		gelidev="${_dev}"
		change_passphrase ${_dev} ${_keypath}
	fi

	trap "" HUP INT ABRT BUS TERM EXIT
}

# geli init. if $1 is empty ${device} variable will be used
init_device()
{
	local _res _arg _tst _device
	printf "${BOLD}"
	_arg="-s 4096 -K ${key} -e ${ealgo}"

	if [ -n "${1}" ]; then
		_device=${1}
	else
		_device=${device}
	fi

	[ ! -c "${_device}" ] && err 1 "${N1_COLOR}No such or unaccesible/non block special device: ${N2_COLOR}${_device}${N0_COLOR}"

	if [ -n "${backupfile}" ]; then
		_arg="${_arg} -B ${backupfile}"
		_tst=$( dirname ${backupfile} )
		[ ! -d "${_tst}" ] && ${MKDIR_CMD} -p ${_tst}
	fi

	_res=$( ${GELI_CMD} init ${_arg} ${_device} 2>&1)

	printf "${N0_COLOR}"
	[ $? -ne 0 ] && err 1 "${N1_COLOR}error: ${N0_COLOR}${_res}"
	[ -n "${_res}" ] && err 0 "${N1_COLOR}${_res}${N0_COLOR}"
}

attach_file()
{
	local _res _md _keyfile

	# not zvol?
	if [ -f "${file}" ]; then
		_res=$( eval find_md_by_img ${file} )

		if [ -n "${_res}" ]; then
			_md="${_res}"
		else
			_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${file} )
		fi
	fi

	_keyfile=$( get_key "${file}" )

	# not zvol?
	if [ -f "${file}" ]; then
		attach_device "/dev/${_md}" "${_keyfile}"
	else
		attach_device "${file}" "${_keyfile}"
	fi
}

info_file()
{
	local _res _md
	_res=$( eval find_md_by_img ${file} )

	if [ -n "${_res}" ]; then
		_md="${_res}"
	else
		_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${file} )
		trap "${MDCONFIG_CMD} -d -u ${_md}" HUP INT ABRT BUS TERM EXIT
	fi

	info_device /dev/${_md}
}


init_file()
{
	local _res _md _file _dev

	if [ -n "${1}" ]; then
		_file=$1
	else
		_file=${file}
	fi

	_res=$( eval find_md_by_img ${_file} )

	mountmaster_file "rw"

	if [ -n "${_res}" ]; then
		_md="${_res}"
	else
		_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${_file} )
		trap "${MDCONFIG_CMD} -d -u ${_md}" HUP INT ABRT BUS TERM EXIT
	fi

	if [ -c "${_md}.eli" ]; then
		echo "${_md}.eli"
		return 0
	fi

	_dev=$( init_device /dev/${_md} )
	[ $? -eq 0 ] && echo "${_dev}"
}


mdattach_file()
{
	local _res _md _file _dev

	if [ -n "${1}" ]; then
		_file=$1
	else
		_file=${file}
	fi
	_res=$( eval find_md_by_img ${_file} )

	if [ -n "${_res}" ]; then
		_md="${_res}"
	else
		_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${_file} )
	fi

	echo ${_md}
	return 0
}


detach_file()
{
	local _res _md
	_res=$( eval find_md_by_img ${file} )

	[ -z "${_res}" ] && err 1 "${N1_COLOR}${file} not attached${N0_COLOR}"

	if [ -n "${_res}" ]; then
		_md="${_res}"
	else
		_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${file} )
	fi

	detach_device /dev/${_md}
}

# $1 - path to dev
# $2 - path to key
attach_device()
{
	local _res _device _keyfile

	[ -z "${1}" -o -z "${2}" ] && err 1 "${N1_COLOR}attach_device need for arg1 arg2${N0_COLOR}"

	_device="${1}"
	_keyfile="${2}"

	if [ -c "${_device}.eli" ]; then
		err 0 "${N2_COLOR}${_device}.eli${N0_COLOR}"
	fi
	printf "${BOLD}"
	_res=$( ${GELI_CMD} attach -j ${_keyfile} ${_device} 2>&1)
	[ $? -ne 0 ] && err 1 "${N1_COLOR}error: ${N0_COLOR}${_res}"
	printf "${N0_COLOR}"
	[ -c "${device}.eli" ] && err 0 "${device}.eli"
	err 0 "${N2_COLOR}${_device}.eli${N0_COLOR}"
}

info_device()
{
	local _res _device

	if [ -n "${1}" ]; then
		_device="${1}"
	else
		_device="${device}"
	fi

	[ -c "${_device}.eli" ] && err 0 "${N2_COLOR}${_device}.eli${N0_COLOR}"
	err 1 "${N1_COLOR}No geli${N0_COLOR}"
}


detach_device()
{
	local _res _device

	if [ -n "${1}" ]; then
		_device="${1}"
	else
		_device="${device}"
	fi
	[ ! -c "${_device}.eli" ] && err 0 "${N1_COLOR}geli: ${N1_COLOR}Device not attached: ${N2_COLOR}${_device}.eli${N0_COLOR}"
	_res=$( ${GELI_CMD} detach ${_device}.eli 2>&1)
	[ $? -ne 0 ] && err 1 "${N1_COLOR}error: ${N0_COLOR}${_res}"
}

check_gelidir()
{
	[ ! -d "${gelidir}" -o ! -r "${MASTER_FILE}" ] && err 1 "${N1_COLOR}Geli dir ${N2_COLOR}${gelidir}${N1_COLOR} is not initialized. Please use: ${N2_COLOR}cbsd geli mode=initmaster${N1_COLOR} as first step${N0_COLOR}"
	is_mounted ${gelidir} && return 0
	err 1 "${N1_COLOR}Geli dir ${N2_COLOR}${gelidir}${N1_COLOR} is not mounted. Please use: ${N2_COLOR}cbsd geli mode=initmaster${N1_COLOR} as first step${N0_COLOR}"
}

initmaster_attach_file()
{
	local _res
	[ -z "${1}" ] && return 1

	${ECHO} "${N1_COLOR}Attaching geli base image. Please use master password.${N0_COLOR}"
	_res=$( ${GELI_CMD} attach ${1} 2>&1 )
	[ ! -c "${1}.eli" ] && err 1 "${N1_COLOR}Not attached: ${_res}${N0_COLOR}"
}


initmaster_file()
{
	local _md _res

	[ -f "${MASTER_FILE}" ] && return 0
	TRAP="${RM_CMD} -f ${MASTER_FILE};"
	trap "${TRAP}" HUP INT ABRT BUS TERM EXIT
	[ -n "${omaster_size}" ] && master_size="${omaster_size}"
	${TRUNCATE_CMD} -s ${master_size} ${MASTER_FILE}
	${CHMOD_CMD} 0600 ${MASTER_FILE}
	_md=$( ${MDCONFIG_CMD} -a -t vnode -f ${MASTER_FILE} )
	TRAP="${MDCONFIG_CMD} -d -u ${_md}; ${TRAP}"
	trap "${TRAP}" HUP INT ABRT BUS TERM EXIT
	$ECHO "${BOLD}Initialization. Please set master password for geli base image${N0_COLOR}"
	_res=$( ${GELI_CMD} init -s 4096 /dev/${_md} 2>&1 )

	initmaster_attach_file /dev/${_md}

	TRAP="${GELI_CMD} detach /dev/${_md}.eli; ${TRAP}"
	trap "${TRAP}" HUP INT ABRT BUS TERM EXIT
	# now create filesystem on the image
	# Linux does not support postfix in bs=, e.g. bs=128k
	case "${platform}" in
		Linux)
			${DD_CMD} if=/dev/random of=/dev/${_md}.eli bs=128000 >/dev/null 2>&1 || true  ## << short write on character device exit with 1 err
			;;
		*)
			${DD_CMD} if=/dev/random of=/dev/${_md}.eli bs=128k >/dev/null 2>&1 || true  ## << short write on character device exit with 1 err
			;;
	esac
	${NEWFS_CMD} -U -m0 -n /dev/${_md}.eli >/dev/null 2>&1
	trap "" HUP INT ABRT BUS TERM EXIT
	#    # detach
	#    ${GELI_CMD} detach /dev/${_md}.eli
	#    ${MDCONFIG_CMD} -d -u ${_md}
	${ECHO} "${N1_COLOR}Init complete: ${N2_COLOR}${MASTER_FILE}${N0_COLOR}"
}

initmaster()
{
	[ ! -d "${gelidir}" -o ! -r "${MASTER_FILE}" ] && err 1 "${N1_COLOR}Geli dir ${N2_COLOR}${gelidir}${N1_COLOR} is not initialized. Please use: ${N2_COLOR}cbsd geli mode=initmaster${N1_COLOR} as first step${N0_COLOR}"
	[ "$( ${STAT_CMD} -f %Op ${gelidir} )" != "40600" ] && ${CHMOD_CMD} 0600 ${gelidir} && ${ECHO} "${N1_COLOR}Fixed permission for ${gelidir}${N0_COLOR}"
	[ "$( ${STAT_CMD} -f %Op ${MASTER_FILE} )" != "100600" ] && ${CHMOD_CMD} 0600 ${MASTER_FILE} && ${ECHO} "${N1_COLOR}Fixed permission for ${MASTER_FILE}${N0_COLOR}"
}

# $1 - mode
mountmaster_file()
{
	local _mode _mounted _res _md
	[ -z "${1}" ] && err 1 "usage: mountmaster_file mode"
	_mode="$1"
	[ "${_mode}" != "ro" -a "${_mode}" != "rw" ] && err 1 "${N1_COLOR}Please set ${N2_COLOR}'ro' ${N1_COLOR}or ${N2_COLOR}'rw'${N0_COLOR}"

	_md=$( mdattach_file ${MASTER_FILE} )

	if is_mounted ${gelidir}; then
		# test for read/write
		_res=$( ${MKTEMP_CMD} -q ${gelidir}/XXXXX )
		if [ $? -eq 0 ]; then
			_mounted="rw"
			${RM_CMD} -f ${_res}
		else
			_mounted="ro"
		fi
		[ "${_mounted}" = "${_mode}" ] && return 0
		# we need for re-mount in new mode
		${UMOUNT_CMD} ${gelidir}
	fi

	[ ! -c "/dev/${_md}.eli" ] && initmaster_attach_file /dev/${_md}

	if [ ! -c "/dev/${_md}.eli" ]; then
		${MDCONFIG_CMD} -d -u ${_md}
		err 1 "${N1_COLOR}No eli initialized for ${N2_COLOR}${MASTER_FILE}${N0_COLOR}"
	fi

	${MOUNT_CMD} -o${_mode} /dev/${_md}.eli ${gelidir}
}


# MAIN
[ ! -d "${gelidir}" ] && ${MKDIR_CMD} -m 0600 ${gelidir}

case "${mode}" in
	init)
		check_gelidir
		# [ -z "${key}" ] && err 1 "${N1_COLOR}Please specify ${N2_COLOR}key=${N0_COLOR}"
		if [ -n "${device}" ]; then
			create_key ${device}
			attach_file ${device}
			# init_device
		elif [ -n "${file}" ]; then
			create_key ${file}
			attach_file ${file}
		# init_file
		else
			err 1 "${N1_COLOR}Please specify ${N2_COLOR}file= ${N1_COLOR}or ${N2_COLOR}device=${N0_COLOR}"
		fi
		;;
	initmaster)
		initmaster_file
		mountmaster_file "ro"
		;;
	attach)
		check_gelidir
		[ -z "${key}" ] && err 1 "${N1_COLOR}Please specify ${N2_COLOR}key=${N0_COLOR}"
		[ ! -r "${key}" ] && err 1 "${N1_COLOR}No such key or file is unreadable: ${N2_COLOR}${key}${N0_COLOR}"
		if [ -n "${device}" ]; then
			attach_device
		elif [ -n "${file}" ]; then
			attach_file
		else
			err 1 "${N1_COLOR}Please specify ${N2_COLOR}file= ${N1_COLOR}or ${N2_COLOR}device=${N0_COLOR}"
		fi
		;;
	detach)
		if [ -n "${device}" ]; then
			detach_device
		elif [ -n "${file}" ]; then
			detach_file
		else
			err 1 "${N1_COLOR}Please specify ${N2_COLOR}file= ${N1_COLOR}or ${N2_COLOR}device=${N0_COLOR}"
		fi
		;;
	info)
		if [ -n "${device}" ]; then
			info_device
		elif [ -n "${file}" ]; then
			info_file
		else
			err 1 "${N1_COLOR}Please specify ${N2_COLOR}file= ${N1_COLOR}or ${N2_COLOR}device=${N0_COLOR}"
		fi
		;;
	list)
		if [ ! -r ${MASTER_FILE} ]; then
			${ECHO} "${N1_COLOR}${CIX_APP}: master file not initialized yed: ${N2_COLOR}${MASTER_FILE}${N0_COLOR}"
			${ECHO} "${N1_COLOR}${CIX_APP}: please use for init masterfile: ${N2_COLOR}cbsd geli mode=initmaster${N0_COLOR}"
			exit 1
		fi
		is_mounted ${gelidir} && err 0 "${N1_COLOR}${CIX_APP}: ${MASTER_FILE} mounted: ${N2_COLOR}${gelidir}${N0_COLOR}"
		err 1 "${N1_COLOR}${CIX_APP}: ${MASTER_FILE} not mounted${N0_COLOR}"
		;;
	*)
		err 1 "${N1_COLOR}Unknown mode${N0_COLOR}"
		;;
esac

exit 0
