libtar: store SELinux file context in tar archives

* Using RedHat's "RHT.security.selinux=" tag in extended
  tar header (type 'x')

Signed-off-by: Vojtech Bocek <vbocek@gmail.com>

Change-Id: I102e492e4fa5b8a08fae4d34eb36386862509b4d
diff --git a/libtar/Android.mk b/libtar/Android.mk
index b73a41f..7e0fafb 100644
--- a/libtar/Android.mk
+++ b/libtar/Android.mk
@@ -11,5 +11,11 @@
 					external/zlib
 LOCAL_SHARED_LIBRARIES += libz libc
 
+ifneq ($(wildcard external/libselinux/Android.mk),)
+	LOCAL_C_INCLUDES += external/libselinux/include
+	LOCAL_SHARED_LIBRARIES += libselinux
+	LOCAL_CFLAGS += -DHAVE_SELINUX
+endif
+
 include $(BUILD_SHARED_LIBRARY)
 
diff --git a/libtar/append.c b/libtar/append.c
index 05024b9..3a8bfc6 100644
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -90,6 +90,31 @@
 #endif
 	th_set_path(t, (savename ? savename : realname));
 
+#ifdef HAVE_SELINUX
+	/* get selinux context */
+	if(t->options & TAR_STORE_SELINUX)
+	{
+		if(t->th_buf.selinux_context != NULL)
+		{
+			free(t->th_buf.selinux_context);
+			t->th_buf.selinux_context = NULL;
+		}
+
+		security_context_t selinux_context = NULL;
+		if(getfilecon(realname, &selinux_context) >= 0)
+		{
+			t->th_buf.selinux_context = strdup(selinux_context);
+			freecon(selinux_context);
+		}
+		else
+		{
+#ifdef DEBUG
+			perror("Failed to get selinux context");
+#endif
+		}
+	}
+#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 89e5e3d..1cfc0e4 100644
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -21,6 +21,10 @@
 
 #define BIT_ISSET(bitmask, bit) ((bitmask) & (bit))
 
+// Used to identify selinux_context in extended ('x')
+// metadata. From RedHat implementation.
+#define SELINUX_TAG "RHT.security.selinux="
+#define SELINUX_TAG_LEN 21
 
 /* read a header block */
 int
@@ -101,6 +105,11 @@
 		free(t->th_buf.gnu_longname);
 	if (t->th_buf.gnu_longlink != NULL)
 		free(t->th_buf.gnu_longlink);
+#ifdef HAVE_SELINUX
+	if (t->th_buf.selinux_context != NULL)
+		free(t->th_buf.selinux_context);
+#endif
+
 	memset(&(t->th_buf), 0, sizeof(struct tar_header));
 
 	i = th_read_internal(t);
@@ -203,6 +212,57 @@
 		}
 	}
 
+#ifdef HAVE_SELINUX
+	if(TH_ISEXTHEADER(t))
+	{
+		sz = th_get_size(t);
+
+		if(sz >= T_BLOCKSIZE) // Not supported
+		{
+#ifdef DEBUG
+			printf("    th_read(): Extended 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, SELINUX_TAG);
+			if(start && start+SELINUX_TAG_LEN < buf+len)
+			{
+				start += SELINUX_TAG_LEN;
+				char *end = strchr(start, '\n');
+				if(end)
+				{
+					t->th_buf.selinux_context = strndup(start, end-start);
+#ifdef DEBUG
+					printf("    th_read(): SELinux context xattr detected: %s\n", t->th_buf.selinux_context);
+#endif
+				}
+			}
+		}
+
+		i = th_read_internal(t);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+#endif
+
 #if 0
 	/*
 	** work-around for old archive files with broken typeflag fields
@@ -359,6 +419,59 @@
 		th_set_size(t, sz2);
 	}
 
+#ifdef HAVE_SELINUX
+	if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using selinux_context (\"%s\")\n",
+		       t->th_buf.selinux_context);
+#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_EXT_TYPE;
+
+		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
+		//                                                       size   newline
+		sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 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 "SELINUX_TAG"%s\n", sz, t->th_buf.selinux_context);
+		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/compat.h b/libtar/compat.h
index 70ac2f4..d086294 100644
--- a/libtar/compat.h
+++ b/libtar/compat.h
@@ -12,6 +12,10 @@
 # include <libgen.h>
 #endif
 
+#ifdef HAVE_SELINUX
+#include "selinux/selinux.h"
+#endif
+
 
 #if defined(NEED_BASENAME) && !defined(HAVE_BASENAME)
 
diff --git a/libtar/extract.c b/libtar/extract.c
index 613e29f..d19ba85 100644
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -154,6 +154,18 @@
 		printf("FAILED SETTING PERMS: %d\n", i);
 		return i;
 	}
+
+#ifdef HAVE_SELINUX
+	if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
+	{
+#ifdef DEBUG
+		printf("    Restoring SELinux context %s to file %s\n", t->th_buf.selinux_context, realname);
+#endif
+		if(setfilecon(realname, t->th_buf.selinux_context) < 0)
+			fprintf(stderr, "Failed to restore SELinux context %s!\n", strerror(errno));
+	}
+#endif
+
 /*
 	pathname_len = strlen(th_get_pathname(t)) + 1;
 	realname_len = strlen(realname) + 1;
diff --git a/libtar/libtar.h b/libtar/libtar.h
index e7a355a..91523d0 100644
--- a/libtar/libtar.h
+++ b/libtar/libtar.h
@@ -35,6 +35,9 @@
 #define GNU_LONGNAME_TYPE	'L'
 #define GNU_LONGLINK_TYPE	'K'
 
+/* extended metadata for next file - used to store selinux_context */
+#define TH_EXT_TYPE		'x'
+
 /* our version of the tar header structure */
 struct tar_header
 {
@@ -57,6 +60,9 @@
 	char padding[12];
 	char *gnu_longname;
 	char *gnu_longlink;
+#ifdef HAVE_SELINUX
+	char *selinux_context;
+#endif
 };
 
 
@@ -96,6 +102,7 @@
 #define TAR_CHECK_MAGIC		16	/* check magic in file header */
 #define TAR_CHECK_VERSION	32	/* check version in file header */
 #define TAR_IGNORE_CRC		64	/* ignore CRC in file header */
+#define TAR_STORE_SELINUX	128	/* store selinux context */
 
 /* this is obsolete - it's here for backwards-compatibility only */
 #define TAR_IGNORE_MAGIC	0
@@ -177,6 +184,7 @@
 			 || S_ISFIFO((mode_t)oct_to_int((t)->th_buf.mode)))
 #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)
 
 /* decode tar header info */
 #define th_get_crc(t) oct_to_int((t)->th_buf.chksum)