Support backup/restore of FBE policies

Change-Id: Iba8ef20f57b0fb57bb9406c53148a806441d0b59
diff --git a/libtar/Android.mk b/libtar/Android.mk
index 838b441..14c19f7 100644
--- a/libtar/Android.mk
+++ b/libtar/Android.mk
@@ -5,16 +5,21 @@
 
 LOCAL_MODULE := libtar
 LOCAL_MODULE_TAGS := eng optional
-LOCAL_CFLAGS :=
-LOCAL_SRC_FILES = append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c
+LOCAL_SRC_FILES := append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c
 LOCAL_C_INCLUDES += $(LOCAL_PATH) \
-					external/zlib
+                    external/zlib
 LOCAL_SHARED_LIBRARIES += libz libc
 
 ifeq ($(TWHAVE_SELINUX), true)
-	LOCAL_C_INCLUDES += external/libselinux/include
-	LOCAL_SHARED_LIBRARIES += libselinux
-	LOCAL_CFLAGS += -DHAVE_SELINUX
+    LOCAL_C_INCLUDES += external/libselinux/include
+    LOCAL_SHARED_LIBRARIES += libselinux
+    LOCAL_CFLAGS += -DHAVE_SELINUX
+endif
+
+ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
+    LOCAL_SHARED_LIBRARIES += libe4crypt
+    LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
+    LOCAL_C_INCLUDES += bootable/recovery/crypto/ext4crypt
 endif
 
 include $(BUILD_SHARED_LIBRARY)
@@ -24,16 +29,21 @@
 
 LOCAL_MODULE := libtar_static
 LOCAL_MODULE_TAGS := eng optional
-LOCAL_CFLAGS = 
-LOCAL_SRC_FILES = append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c
+LOCAL_SRC_FILES := append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c
 LOCAL_C_INCLUDES += $(LOCAL_PATH) \
-					external/zlib
+                    external/zlib
 LOCAL_STATIC_LIBRARIES += libz libc
 
 ifeq ($(TWHAVE_SELINUX), true)
-	LOCAL_C_INCLUDES += external/libselinux/include
-	LOCAL_STATIC_LIBRARIES += libselinux
-	LOCAL_CFLAGS += -DHAVE_SELINUX
+    LOCAL_C_INCLUDES += external/libselinux/include
+    LOCAL_STATIC_LIBRARIES += libselinux
+    LOCAL_CFLAGS += -DHAVE_SELINUX
+endif
+
+ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
+    LOCAL_SHARED_LIBRARIES += libe4crypt
+    LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
+    LOCAL_C_INCLUDES += bootable/recovery/crypto/ext4crypt
 endif
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/libtar/append.c b/libtar/append.c
index 4be679c..4388297 100644
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -20,6 +20,7 @@
 #include <time.h>
 #include <sys/param.h>
 #include <sys/types.h>
+#include <stdbool.h>
 
 #ifdef STDC_HEADERS
 # include <stdlib.h>
@@ -34,6 +35,10 @@
 # include "selinux/selinux.h"
 #endif
 
+#ifdef HAVE_EXT4_CRYPT
+# include "ext4crypt_tar.h"
+#endif
+
 struct tar_dev
 {
 	dev_t td_dev;
@@ -122,6 +127,33 @@
 	}
 #endif
 
+#ifdef HAVE_EXT4_CRYPT
+	if (TH_ISDIR(t) && t->options & TAR_STORE_EXT4_POL)
+	{
+		if (t->th_buf.e4crypt_policy != NULL)
+		{
+			free(t->th_buf.e4crypt_policy);
+			t->th_buf.e4crypt_policy = NULL;
+		}
+
+		char e4crypt_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+		if (e4crypt_policy_get(realname, e4crypt_policy, EXT4_KEY_DESCRIPTOR_SIZE, 0))
+		{
+			char tar_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+			memset(tar_policy, 0, sizeof(tar_policy));
+			char policy_hex[EXT4_KEY_DESCRIPTOR_HEX];
+			policy_to_hex(e4crypt_policy, policy_hex);
+			if (lookup_ref_key(e4crypt_policy, &tar_policy)) {
+				printf("found policy '%s' - '%s' - '%s'\n", realname, tar_policy, policy_hex);
+				t->th_buf.e4crypt_policy = strdup(tar_policy);
+			} else {
+				printf("failed to lookup tar policy for '%s' - '%s'\n", realname, policy_hex);
+				return -1;
+			}
+		} // else no policy found, but this is not an error as not all dirs will have a policy
+	}
+#endif
+
 	/* check if it's a hardlink */
 #ifdef DEBUG
 	puts("tar_append_file(): checking inode cache for hardlink...");
diff --git a/libtar/block.c b/libtar/block.c
index 5d3c9d8..2fd61bb 100644
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -25,6 +25,10 @@
 #define SELINUX_TAG "RHT.security.selinux="
 #define SELINUX_TAG_LEN 21
 
+// Used to identify e4crypt_policy in extended ('x')
+#define E4CRYPT_TAG "TWRP.security.e4crypt="
+#define E4CRYPT_TAG_LEN 22
+
 /* read a header block */
 /* FIXME: the return value of this function should match the return value
 	  of tar_block_read(), which is a macro which references a prototype
@@ -119,6 +123,11 @@
 	if (t->th_buf.selinux_context != NULL)
 		free(t->th_buf.selinux_context);
 #endif
+#ifdef HAVE_EXT4_CRYPT
+	if (t->th_buf.e4crypt_policy != NULL) {
+		free(t->th_buf.e4crypt_policy);
+	}
+#endif
 
 	memset(&(t->th_buf), 0, sizeof(struct tar_header));
 
@@ -283,6 +292,57 @@
 	}
 #endif
 
+#ifdef HAVE_EXT4_CRYPT
+	if(TH_ISPOLHEADER(t))
+	{
+		sz = th_get_size(t);
+
+		if(sz >= T_BLOCKSIZE) // Not supported
+		{
+#ifdef DEBUG
+			printf("    th_read(): Policy header is too long!\n");
+#endif
+		}
+		else
+		{
+			char buf[T_BLOCKSIZE];
+			i = tar_block_read(t, buf);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+
+			// To be sure
+			buf[T_BLOCKSIZE-1] = 0;
+
+			int len = strlen(buf);
+			char *start = strstr(buf, E4CRYPT_TAG);
+			if(start && start+E4CRYPT_TAG_LEN < buf+len)
+			{
+				start += E4CRYPT_TAG_LEN;
+				char *end = strchr(start, '\n');
+				if(end)
+				{
+				    t->th_buf.e4crypt_policy = strndup(start, end-start);
+#ifdef DEBUG
+				    printf("    th_read(): E4Crypt policy detected: %s\n", t->th_buf.e4crypt_policy);
+#endif
+				}
+			}
+		}
+
+		i = th_read_internal(t);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+#endif
+
 	return 0;
 }
 
@@ -457,6 +517,59 @@
 	}
 #endif
 
+#ifdef HAVE_EXT4_CRYPT
+	if((t->options & TAR_STORE_EXT4_POL) && t->th_buf.e4crypt_policy != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using e4crypt_policy %s\n",
+		       t->th_buf.e4crypt_policy);
+#endif
+		/* save old size and type */
+		type2 = t->th_buf.typeflag;
+		sz2 = th_get_size(t);
+
+		/* write out initial header block with fake size and type */
+		t->th_buf.typeflag = TH_POL_TYPE;
+
+		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
+		//                                                       size   newline
+		sz = E4CRYPT_TAG_LEN + EXT4_KEY_DESCRIPTOR_HEX + 3  +    1;
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		if(sz >= T_BLOCKSIZE) // impossible
+		{
+			errno = EINVAL;
+			return -1;
+		}
+
+		th_set_size(t, sz);
+		th_finish(t);
+		i = tar_block_write(t, &(t->th_buf));
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		memset(buf, 0, T_BLOCKSIZE);
+		snprintf(buf, T_BLOCKSIZE, "%d "E4CRYPT_TAG"%s\n", (int)sz, t->th_buf.e4crypt_policy);
+		i = tar_block_write(t, &buf);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		/* reset type and size to original values */
+		t->th_buf.typeflag = type2;
+		th_set_size(t, sz2);
+	}
+#endif
+
 	th_finish(t);
 
 #ifdef DEBUG
diff --git a/libtar/extract.c b/libtar/extract.c
index 6a63ff7..ba29a77 100644
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -32,6 +32,10 @@
 # include "selinux/selinux.h"
 #endif
 
+#ifdef HAVE_EXT4_CRYPT
+# include "ext4crypt_tar.h"
+#endif
+
 const unsigned long long progress_size = (unsigned long long)(T_BLOCKSIZE);
 
 static int
@@ -492,7 +496,7 @@
 			}
 			else
 			{
-#ifdef DEBUG
+#if 1 //def DEBUG
 				puts("  *** using existing directory");
 #endif
 				return 1;
@@ -507,6 +511,28 @@
 		}
 	}
 
+#ifdef HAVE_EXT4_CRYPT
+	if(t->th_buf.e4crypt_policy != NULL)
+	{
+#ifdef DEBUG
+		printf("tar_extract_file(): restoring EXT4 crypt policy %s to dir %s\n", t->th_buf.e4crypt_policy, realname);
+#endif
+		char binary_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+		if (!lookup_ref_tar(t->th_buf.e4crypt_policy, &binary_policy)) {
+			printf("error looking up proper e4crypt policy for '%s' - %s\n", realname, t->th_buf.e4crypt_policy);
+			return -1;
+		}
+		char policy_hex[EXT4_KEY_DESCRIPTOR_HEX];
+		policy_to_hex(binary_policy, policy_hex);
+		printf("restoring policy %s > '%s' to '%s'\n", t->th_buf.e4crypt_policy, policy_hex, realname);
+		if (!e4crypt_policy_set(realname, binary_policy, EXT4_KEY_DESCRIPTOR_SIZE, 0))
+		{
+			printf("tar_extract_file(): failed to restore EXT4 crypt policy %s to dir '%s' '%s'!!!\n", t->th_buf.e4crypt_policy, realname, policy_hex);
+			//return -1; // This may not be an error in some cases, so log and ignore
+		}
+	}
+#endif
+
 	return 0;
 }
 
diff --git a/libtar/libtar.h b/libtar/libtar.h
index 4a51375..ab5a3be 100644
--- a/libtar/libtar.h
+++ b/libtar/libtar.h
@@ -19,6 +19,11 @@
 
 #include "libtar_listhash.h"
 
+#ifdef HAVE_EXT4_CRYPT
+#define EXT4_KEY_DESCRIPTOR_SIZE 8
+#define EXT4_KEY_DESCRIPTOR_HEX 17
+#endif
+
 #ifdef __cplusplus
 extern "C"
 {
@@ -38,6 +43,7 @@
 
 /* extended metadata for next file - used to store selinux_context */
 #define TH_EXT_TYPE		'x'
+#define TH_POL_TYPE		'p'
 
 /* our version of the tar header structure */
 struct tar_header
@@ -64,6 +70,9 @@
 #ifdef HAVE_SELINUX
 	char *selinux_context;
 #endif
+#ifdef HAVE_EXT4_CRYPT
+	char *e4crypt_policy;
+#endif
 };
 
 
@@ -108,6 +117,7 @@
 #define TAR_IGNORE_CRC		64	/* ignore CRC in file header */
 #define TAR_STORE_SELINUX	128	/* store selinux context */
 #define TAR_USE_NUMERIC_ID	256	/* favor numeric owner over names */
+#define TAR_STORE_EXT4_POL	512	/* store ext4 crypto policy */
 
 /* this is obsolete - it's here for backwards-compatibility only */
 #define TAR_IGNORE_MAGIC	0
@@ -204,6 +214,7 @@
 #define TH_ISLONGNAME(t)	((t)->th_buf.typeflag == GNU_LONGNAME_TYPE)
 #define TH_ISLONGLINK(t)	((t)->th_buf.typeflag == GNU_LONGLINK_TYPE)
 #define TH_ISEXTHEADER(t)	((t)->th_buf.typeflag == TH_EXT_TYPE)
+#define TH_ISPOLHEADER(t)	((t)->th_buf.typeflag == TH_POL_TYPE)
 
 /* decode tar header info */
 #define th_get_crc(t) oct_to_int((t)->th_buf.chksum, sizeof((t)->th_buf.chksum))