Initial commit
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6601409
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2015 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+Copyright (c) 2014 Laurent Ghigonis <laurent@gouloum.fr>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..439426f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+PREFIX ?= /usr
+DESTDIR ?=
+BINDIR ?= $(PREFIX)/bin
+LIBDIR ?= $(PREFIX)/lib
+MANDIR ?= $(PREFIX)/share/man
+
+all:
+	@echo "Run \"sudo make install\" to install ctmg"
+
+install:
+	@install -v -d "$(DESTDIR)$(BINDIR)/" && install -v -m 0755 ctmg.sh "$(DESTDIR)$(BINDIR)/ctmg"
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..322e72b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+## ctmg
+
+`ctmg` is an encrypted container manager for Linux using `cryptsetup` and various standard file system utilities. Containers have the extension `.ct` and are mounted at a directory of the same name, but without the extension. Very simple to understand, and very simple to implement; `ctmg` is a simple bash script.
+
+### Usage
+
+    Usage: ctmg [ new | delete | open | close | list ] [arguments...]
+      ctmg new    container_path container_size[units_suffix]
+      ctmg delete container_path
+      ctmg open   container_path
+      ctmg close  container_path
+      ctmg list
+
+### Examples
+
+#### Create a 100MiB encrypted container called "example"
+
+    zx2c4@thinkpad ~ $ ctmg create example 100MiB
+    [#] truncate -s 100MiB /home/zx2c4/example.ct
+    [#] cryptsetup --cipher aes-xts-plain64 --key-size 512 --hash sha512 --iter-time 5000 --batch-mode luksFormat /home/zx2c4/example.ct
+    Enter passphrase:
+    [#] chown 1000:1000 /home/zx2c4/example.ct
+    [#] cryptsetup luksOpen /home/zx2c4/example.ct ct_example
+    Enter passphrase for /home/zx2c4/example.ct:
+    [#] mkfs.ext4 -q -E root_owner=1000:1000 /dev/mapper/ct_example
+    [+] Created new encrypted container at /home/zx2c4/example.ct
+    [#] cryptsetup luksClose ct_example
+
+#### Open a container, add a file, and then close it
+
+    zx2c4@thinkpad ~ $ ctmg open example
+    [#] cryptsetup luksOpen /home/zx2c4/example.ct ct_example
+    Enter passphrase for /home/zx2c4/example.ct: 
+    [#] mkdir -p /home/zx2c4/example
+    [#] mount /dev/mapper/ct_example /home/zx2c4/example
+    [+] Opened /home/zx2c4/example.ct at /home/zx2c4/example
+    zx2c4@thinkpad ~ $ echo "super secret" > example/mysecretfile.txt
+    zx2c4@thinkpad ~ $ ctmg close example
+    [#] umount /home/zx2c4/example
+    [#] cryptsetup luksClose ct_example
+    [#] rmdir /home/zx2c4/example
+    [+] Closed /home/zx2c4/example.ct
+
+### Installation
+
+    # make install
+
+Or, use the package from your distribution:
+
+#### Gentoo
+
+    # emerge ctmg
+
+### Bug reports
+
+Report any bugs to <jason@zx2c4.com>.
diff --git a/ctmg.sh b/ctmg.sh
new file mode 100755
index 0000000..6231672
--- /dev/null
+++ b/ctmg.sh
@@ -0,0 +1,154 @@
+#!/bin/bash
+
+# Copyright (c) 2015 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+# Copyright (c) 2014 Laurent Ghigonis <laurent@gouloum.fr>.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+CT_FILE_SUFFIX=".ct"
+CT_MAPPER_PREFIX="ct_"
+
+trace() {
+	echo "[#] $@"
+	"$@"
+}
+
+die() {
+	echo "[!] $*" >&2
+	exit 1
+}
+
+yesno() {
+	[[ -t 0 ]] || return 0
+	local response
+	read -r -p "$1 [y/N] " response
+	[[ $response == [yY] ]] || exit 1
+}
+
+unwind() {
+	[[ $keep_open -eq 1 ]] && return
+
+	for i in {1..5}; do
+		echo -e "$(cut -d ' ' -f 2 /proc/mounts)" | fgrep -wq "$mount_path" || break
+		trace umount "$mount_path" && break
+		trace sleep $i
+	done
+	
+	for i in {1..5}; do
+		trace cryptsetup luksClose "$mapper_name"
+		[[ $? -eq 0 || $? -eq 4 ]] && break
+		trace sleep $i
+	done
+	
+	for i in {1..5}; do
+		[[ ! -d $mount_path ]] && break
+		trace rmdir "$mount_path" && break
+		trace sleep $i
+	done
+
+	keep_open=1
+	exit
+}
+
+initialize_container() {
+	# container_dir  = /home/myuser/
+	container_dir="$(readlink -f "$(dirname "$1")")"
+	# container_path = /home/myuser/bla.ct
+	container_path="$container_dir/$(basename "$1" "$CT_FILE_SUFFIX")$CT_FILE_SUFFIX"
+	# mount_path     = /home/myuser/bla/
+	mount_path="$(readlink -f "$container_dir/$(basename "$container_path" "$CT_FILE_SUFFIX")")"
+	# mapper_name    = ct_home-myuser-bla
+	mapper_name="$CT_MAPPER_PREFIX$(echo -n "${mount_path:1}" | tr -C '[:graph:]' '_' | tr '/' '-')"
+	# mapper_path    = /dev/mapper/ct_home-myuser-bla
+	mapper_path="/dev/mapper/$mapper_name"
+	
+	trap unwind INT TERM EXIT
+}
+
+cmd_usage() {
+	cat <<-_EOF
+	Usage: $PROGRAM [ new | delete | open | close | list ] [arguments...]
+	  $PROGRAM new    container_path container_size[units_suffix]
+	  $PROGRAM delete container_path
+	  $PROGRAM open   container_path
+	  $PROGRAM close  container_path
+	  $PROGRAM list
+	_EOF
+}
+
+cmd_new() {
+	[[ $# -ne 2 ]] && die "Usage: $PROGRAM new container_path container_size[units_suffix]"
+	initialize_container "$1"
+	local container_size="$2"
+	[[ -e $mapper_path ]] && { keep_open=1; die "$container_path is already open"; }
+	[[ -e $container_path ]] && yesno "$container_path already exists. Are you sure you want to continue?"
+	rm -f "$container_path"
+	trace truncate -s "$container_size" "$container_path" || { trace rm -f "$container_path"; die "Could not create $container_path"; }
+	trace cryptsetup --cipher aes-xts-plain64 --key-size 512 --hash sha512 --iter-time 5000 --batch-mode luksFormat "$container_path" || { trace rm -f "$container_path"; die "Could not create LUKS volume on $container_path"; }
+	trace chown ${SUDO_UID:-0}:${SUDO_GID:-0} "$container_path" || { trace rm -f "$container_path"; die "Could not set ownership of $container_path"; }
+	trace cryptsetup luksOpen "$container_path" "$mapper_name" || { trace rm -f "$container_path"; die "Could not open LUKS volume at $container_path"; }
+	trace mkfs.ext4 -q -E root_owner=${SUDO_UID:-0}:${SUDO_GID:-0} "$mapper_path" || { trace rm -f "$container_path"; die "Could not format ext4 on the LUKS volume at $container_path"; }
+	echo "[+] Created new encrypted container at $container_path"
+}
+
+cmd_open() {
+	[[ $# -ne 1 ]] && die "Usage: $PROGRAM open container_path"
+	initialize_container "$1"
+	[[ -f $container_path ]] || { keep_open=1; die "$container_path does not exist or is not a regular file"; }
+	[[ -e $mapper_path ]] && { keep_open=1; die "$container_path is already open"; }
+	trace cryptsetup luksOpen "$container_path" "$mapper_name" || die "Could not open LUKS volume at $container_path"
+	trace mkdir -p "$mount_path" || die "Could not create $mount_path directory"
+	trace mount "$mapper_path" "$mount_path" || die "Could not mount $container_path to $mount_path"
+	keep_open=1
+	echo "[+] Opened $container_path at $mount_path"
+}
+
+cmd_close() {
+	[[ $# -ne 1 ]] && die "Usage: $PROGRAM close container_path"
+	initialize_container "$1"
+	keep_open=1
+	echo -e "$(cut -d ' ' -f 2 /proc/mounts)" | fgrep -wq "$mount_path" && { trace umount "$mount_path" || die "Could not unmount $mount_path"; }
+	trace cryptsetup luksClose "$mapper_name"
+	[[ $? -eq 0 || $? -eq 4 ]] || die "Could not close LUKS mapping $mapper_name"
+	[[ -d $mount_path ]] && { trace rmdir "$mount_path" || echo "[-] Non-fatal: could not remove $mount_path directory" >&2; }
+	echo "[+] Closed $container_path"
+}
+
+cmd_delete() {
+	[[ $# -ne 1 ]] && die "Usage: $PROGRAM delete container_path"
+	yesno "Are you sure you want to delete $1?"
+	cmd_close "$@"
+	rm "$container_path" || die "Could not delete $container_path"
+	echo "[+] Deleted $container_path"
+}
+
+cmd_list() {
+	[[ $# -ne 0 ]] && die "Usage: $PROGRAM list"
+	local mount_points="$(sed -n "s:^/dev/mapper/${CT_MAPPER_PREFIX}[^ ]* \\([^ ]\\+\\).*:\\1:p" /proc/mounts)"
+	[[ -n $mount_points ]] && echo -e "$mount_points"
+}
+
+PROGRAM="$(basename "$0")"
+
+[[ $UID != 0 ]] && exec sudo -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " "$(readlink -f "$0")" "$@"
+
+case "$1" in
+	h|help|-h|--help) shift;	cmd_usage "$@" ;;
+	n|new|create) shift;		cmd_new "$@" ;;
+	d|del|delete) shift;		cmd_delete "$@" ;;
+	c|close) shift;			cmd_close "$@" ;;
+	l|list) shift;			cmd_list "$@" ;;
+	o|open) shift;			cmd_open "$@" ;;
+	*)				cmd_open "$@" ;;
+esac
+exit 0