#!/bin/bash

# Copyright (c) 2007 Torbjörn Svensson <azoff@se.linux.org>.
#
# Based on ideas from:
# * http://www.sourcentral.org/luks/iso9660/easy_iso_crypt.sh
# * http://gentoo-wiki.com/HOWTO_Burn_Encrypted_Optical_Media_With_Luks
#
# 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 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

ISODIR=/home/backups
MOUNTDIR=/mnt/cdrom
ISO=/home/tmp/image.iso
DVDr=/dev/hda
KEYFILE=/home/key
LOG=/home/backup.log
MAILTO=root@localhost

MAXSIZE=$(( 4700 *2 ))k
CRYPT="-s 256 -c aes"
MAPPER="crypt1"
MKISOFS="-allow-leading-dots -joliet-long -iso-level 3 -l -J -r"


TMP1=$(mktemp -p /home/tmp tmp1.XXXXXXXX)
TMP2=$(mktemp -p /home/tmp tmp2.XXXXXXXX)

function getLoop() {
	local LOOP=
	for i in /dev/loop*; do
		losetup $i > /dev/null 2>&1 
		if [ $? -eq 1 ]; then 
			LOOP=$i
			break
		fi
	done

	if [ -z "$LOOP" ] ; then 
		echo "No free loop device"
		return 1
	fi
	LOOPDEV=$LOOP
}

function freeDMs() {
	for f in $(diff -u $1 $2 | grep '^+' | cut -d: -f1 | cut -d+ -f2); do
		dmsetup remove $f
	done	
}

function main() {
	# for logging
	(
		# Only allow root to run this script
		if [ $(whoami) != root ]; then 
			echo "This script must be run as root."
			return 1
		fi

		# Removing old ISO images if it exist
		if [ -a $ISO ]; then 
			rm -v $ISO
		fi

		# Check size limits..
		if (( $(du -kcs $ISODIR | head -n 1 | cut -d/ -f1) > 4583184 )); then
			echo "Maximum size limit of 4583184k for DVDr media excedded"
			return 3
		fi

		# Create empty dvd
		echo "Creating dvd-space (image)"
		dd if=/dev/urandom of=$ISO bs=512 count=2048
		dd if=/dev/urandom of=$ISO bs=512 count=1 seek=$MAXSIZE

		# workarround for break
		getLoop
		if [ $? -eq 1 ]; then
			return 1
		fi
		dmsetup status > $TMP1

		echo "Setting up crypto"

		# assign loopdev
		losetup $LOOPDEV $ISO

		# create LUKS overhead
		cryptsetup -q $CRYPT luksFormat $LOOPDEV $KEYFILE

		# refetch status, created temp-dev (FIXME!) and remove it
		dmsetup status > $TMP2
		freeDMs $TMP1 $TMP2

		# open the real fs (decrypt)
		cryptsetup luksOpen $LOOPDEV $MAPPER --key-file $KEYFILE

		# create the iso
		echo "Starting to copy files from $ISODIR"
		SIZE=$(mkisofs $MKISOFS $ISODIR | dd of=/dev/mapper/$MAPPER bs=512 2>&1 | grep "records out" | cut -f1 -d+)
		SIZE_VOL1=$(blockdev --getsize /dev/mapper/$MAPPER)
		SIZE_LOOP=$(blockdev --getsize $LOOPDEV)
		SIZE=$(( $SIZE + ($SIZE_LOOP - $SIZE_VOL1) ))

		# close fs
		cryptsetup luksClose $MAPPER

		# remove temp-dev (FIXME!)
		dmsetup status > $TMP2
		freeDMs $TMP1 $TMP2

		# unassign loopdev
		losetup -d $LOOPDEV

		# truncate the iso to needed space
		echo "Shrinking $ISO to minimize data needed to be burned"
		dd if=$ISO of=$ISO bs=512 count=0 skip=$SIZE seek=$SIZE >& /dev/null



		# test the img
		echo "Verify data in image"


		# workarround for break
		getLoop
		if [ $? -eq 1 ]; then
			return 1
		fi
		losetup $LOOPDEV $ISO

		dmsetup status > $TMP2
		freeDMs $TMP1 $TMP2

		# open fs
		cryptsetup luksOpen $LOOPDEV $MAPPER --key-file $KEYFILE

		# mount it
		mount /dev/mapper/$MAPPER $MOUNTDIR

		# see if all got over correctly
		diff -urN $MOUNTDIR $ISODIR > /dev/null
		DIFF_RET=$?


		echo "Umouting and removing crypto"
		umount $MOUNTDIR

		# close fs
		cryptsetup luksClose $MAPPER

		# remove temp-dev (FIXME!)
		dmsetup status > $TMP2
		freeDMs $TMP1 $TMP2

		# unassign loopdev
		losetup -d $LOOPDEV

		# burn or not? only do this if passed first test
		if [ $DIFF_RET -ne 0 ]; then
			echo "Warning, doesn't match structure! Will not burn, please inspect."
			echo "Image can be found $ISO"
			return 3
		else
			# Format dvd
			echo "Removing old info on dvd"
			dvd+rw-format -lead-out $DVDr

			# Burn
			echo "Burning..."
			growisofs -dvd-compat -Z $DVDr=$ISO


			echo "Checking dvd"

			# open fs
			cryptsetup luksOpen $DVDr $MAPPER --key-file $KEYFILE

			# mount it
			mount /dev/mapper/$MAPPER $MOUNTDIR

			# see if all got over correctly
			diff -urN $MOUNTDIR $ISODIR > /dev/null
			DIFF_RET=$?
			
			# umount
			umount $MOUNTDIR

			# close fs
			cryptsetup luksClose $MAPPER

			# remove temp-dev (FIXME!)
			dmsetup status > $TMP2
			freeDMs $TMP1 $TMP2
			
			if [ $DIFF_RET -ne 0 ]; then
				echo "Warning! Content on DVD does not match filesystem, please inspect."
				return 2
			fi

			echo "Removing image"
			rm $ISO
		fi

		# clean temp files
		rm $TMP1 $TMP2

	) >& $LOG

	# success
	return 0
}




# Run the script
SUBJECT=
case "$(main)" in
	1)
		SUBJECT="Warning"
		;;
	2)
		SUBJECT="Error"
		;;
	3)
		SUBJECT="Critical"
		;;
esac

if [ ! -z "$SUBJECT" ]; then
	cat $LOG | mail -s "Backup on $(hostname) exited with status *$SUBJECT*!" $MAILTO 
fi

exit 0


