Add pam_cap.so "default=<IAB>" module argument support

Add a new optional argument to pam_cap.so. This argument substitutes
for a line like this in the capability.conf file:

  <IAB>       *

That is, it supplies the default <IAB> 3-tuple of capability vectors.
Any * value in the prevailing capability.conf file overrides this default.
However, the admin can supply arguments like this:

  auth pam_cap.so autoauth config=/dev/null default=^cap_wake_alarm

to grant everyone who executes it the ambient capability cap_wake_alarm.

This addresses:

  https://bugzilla.kernel.org/show_bug.cgi?id=213611

However, see:

  https://bugzilla.kernel.org/show_bug.cgi?id=212945

for issues limiting PAM application support for ambient capabilities in
general at present.

Signed-off-by: Andrew G. Morgan <[email protected]>
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index 56604fd..ce63f16 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -27,8 +27,9 @@
 testlink: test.c pam_cap.o
 	$(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
 
-test: pam_cap.so
-	make testlink
+test: pam_cap.so test_pam_cap
+	$(MAKE) testlink
+	./test_pam_cap
 
 sudotest: test test_pam_cap
 	sudo ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index 5b523f5..fb93ed9 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -25,6 +25,7 @@
 #   config=<file> - override the default config for the module with file
 #   keepcaps      - workaround for applications that setuid without this
 #   autoauth      - if you want pam_cap.so to always succeed for the auth phase
+#   default       - provide a failback IAB value if there is no '*' rule
 
 ## user 'morgan' gets the CAP_SETFCAP inheritable capability (commented out!)
 #cap_setfcap		morgan
@@ -35,14 +36,15 @@
 ## 'everyone else' gets no inheritable capabilities (restrictive config)
 none  *
 
-## if there is no '*' entry, all users not explicitly mentioned will
-## get all available inheritable capabilities. This is a permissive
-## default, and possibly not what you want... On first reading, you
-## might think this is a security problem waiting to happen, but it
-## defaults to not being so in this sample file! Further, by 'get', we
-## mean 'get in their IAB sets'. That is, if you look at a random
-## process, even one run by root, you will see it has no IAB
-## capabilities (by default):
+## if there is no '*' entry, and no "default=<iab>" pam_cap.so module
+## argument to fallback on, all users not explicitly mentioned will
+## get all currently available inheritable capabilities. This is a
+## permissive default, and possibly not what you want... On first
+## reading, you might think this is a security problem waiting to
+## happen, but it defaults to not being so in this sample file!
+## Further, by 'get', we mean 'get in their IAB sets'. That is, if you
+## look at a random process, even one run by root, you will see it has
+## no IAB capabilities (by default):
 ##
 ##   $ /sbin/capsh --decode=$(grep CapInh /proc/1/status|awk '{print $2}')
 ##   0000000000000000=
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 2eeab87..162e1f5 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -32,12 +32,16 @@
 #define CAP_FILE_BUFFER_SIZE    4096
 #define CAP_FILE_DELIMITERS     " \t\n"
 
+/*
+ * pam_cap_s is used to summarize argument values in a parsed form.
+ */
 struct pam_cap_s {
     int debug;
     int keepcaps;
     int autoauth;
     const char *user;
     const char *conf_filename;
+    const char *fallback;
 };
 
 /*
@@ -77,7 +81,7 @@
     return 0;
 }
 
-/* obtain the inheritable capabilities for the current user */
+/* obtain the desired IAB capabilities for the current user */
 
 static char *read_capabilities_for_user(const char *user, const char *source)
 {
@@ -201,7 +205,11 @@
 					   ? cs->conf_filename:USER_CAP_FILE );
     if (conf_caps == NULL) {
 	D(("no capabilities found for user [%s]", cs->user));
-	goto cleanup_cap_s;
+	if (cs->fallback == NULL) {
+	    goto cleanup_cap_s;
+	}
+	conf_caps = strdup(cs->fallback);
+	D(("user [%s] received fallback caps [%s]", cs->user, conf_caps));
     }
 
     ssize_t conf_caps_length = strlen(conf_caps);
@@ -286,6 +294,8 @@
 	    pcs->keepcaps = 1;
 	} else if (!strcmp(*argv, "autoauth")) {
 	    pcs->autoauth = 1;
+	} else if (!strncmp(*argv, "default=", 8)) {
+	    pcs->fallback = 8 + *argv;
 	} else {
 	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
 	}
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
index 452a27f..4c09a5d 100644
--- a/pam_cap/test_pam_cap.c
+++ b/pam_cap/test_pam_cap.c
@@ -5,6 +5,11 @@
  * it.
  */
 
+#define _DEFAULT_SOURCE
+
+#include <unistd.h>
+#include <sys/types.h>
+
 #include "./pam_cap.c"
 
 const char *test_groups[] = {
@@ -121,12 +126,106 @@
     cap_free(prev);
 }
 
+struct vargs {
+    struct pam_cap_s cs;
+    const char *args[5];
+};
+
+static int test_arg_parsing(void) {
+    static struct vargs vs[] = {
+	{
+	    { 1, 0, 0, NULL, NULL, NULL },
+	    { "debug", NULL }
+	},
+	{
+	    { 0, 1, 0, NULL, NULL, NULL },
+	    { "keepcaps", NULL }
+	},
+	{
+	    { 0, 0, 1, NULL, NULL, NULL },
+	    { "autoauth", NULL }
+	},
+	{
+	    { 1, 0, 1, NULL, NULL, NULL },
+	    { "autoauth", "debug", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, "/over/there", NULL },
+	    { "config=/over/there", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, NULL, "^cap_setfcap" },
+	    { "default=^cap_setfcap", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, NULL, NULL },
+	    { NULL }
+	}
+    };
+    int i;
+
+    for (i=0; ; i++) {
+	int argc;
+	const char **argv;
+	struct vargs *v;
+
+	v = &vs[i];
+	argv = v->args;
+
+	for (argc = 0; argv[argc] != NULL; argc++);
+
+	struct pam_cap_s cs;
+	parse_args(argc, argv, &cs);
+
+	if (cs.debug != v->cs.debug) {
+	    printf("test_arg_parsing[%d]: debug=%d, wanted debug=%d\n",
+		   i, cs.debug, v->cs.debug);
+	    return 1;
+	}
+	if (cs.keepcaps != v->cs.keepcaps) {
+	    printf("test_arg_parsing[%d]: keepcaps=%d, wanted keepcaps=%d\n",
+		   i, cs.keepcaps, v->cs.keepcaps);
+	    return 1;
+	}
+	if (cs.autoauth != v->cs.autoauth) {
+	    printf("test_arg_parsing[%d]: autoauth=%d, wanted autoauth=%d\n",
+		   i, cs.autoauth, v->cs.autoauth);
+	    return 1;
+	}
+	if (cs.conf_filename != v->cs.conf_filename &&
+	    strcmp(cs.conf_filename, v->cs.conf_filename)) {
+	    printf("test_arg_parsing[%d]: conf_filename=[%s], wanted=[%s]\n",
+		   i, cs.conf_filename, v->cs.conf_filename);
+	    return 1;
+	}
+	if (cs.fallback != v->cs.fallback &&
+	    strcmp(cs.fallback, v->cs.fallback)) {
+	    printf("test_arg_parsing[%d]: fallback=[%s], wanted=[%s]\n",
+		   i, cs.fallback, v->cs.fallback);
+	    return 1;
+	}
+
+	if (argc == 0) {
+	    break;
+	}
+    }
+    return 0;
+}
+
 /*
  * args: user a b i config-args...
  */
 int main(int argc, char *argv[]) {
     unsigned long int before[3], change[3], after[3];
 
+    if (test_arg_parsing()) {
+	printf("failed to parse arguments\n");
+	exit(1);
+    }
+    if (read_capabilities_for_user("morgan", "/dev/null") != NULL) {
+	printf("/dev/null is not a valid config file\n");
+    }
+
     /*
      * Start out with a cleared inheritable set.
      */
@@ -134,6 +233,12 @@
     cap_clear_flag(orig, CAP_INHERITABLE);
     cap_set_proc(orig);
 
+    if (getuid() != 0) {
+	cap_free(orig);
+	printf("test_pam_cap: OK! (Skipping privileged tests (uid!=0))\n");
+	exit(0);
+    }
+
     change[A] = strtoul(argv[2], NULL, 0);
     change[B] = strtoul(argv[3], NULL, 0);
     change[I] = strtoul(argv[4], NULL, 0);