aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2019-09-03 11:04:42 +0100
committerDavid Howells <dhowells@redhat.com>2020-07-06 18:00:28 +0100
commit5ac409b01cc4fcb69d903408da90df2d34d4f32a (patch)
tree274a8d817a2b968c816113594638f77886d9beab
parent9419ac428efe8385cb2c19680d6b90c90e5335d1 (diff)
downloadkeyutils-5ac409b01cc4fcb69d903408da90df2d34d4f32a.tar.gz
Add the ability to supply filters to watches set with keyctl
Add the ability to supply filters to watches set with "keyctl watch" and "keyctl watch_session". Signed-off-by: David Howells <dhowells@redhat.com>
-rw-r--r--keyctl.c4
-rw-r--r--keyctl_watch.c101
-rw-r--r--man/keyctl.127
-rw-r--r--tests/keyctl/watch/bad-args/runtest.sh39
-rw-r--r--tests/keyctl/watch/noargs/runtest.sh27
-rw-r--r--tests/toolbox.inc.sh94
6 files changed, 269 insertions, 23 deletions
diff --git a/keyctl.c b/keyctl.c
index cc25ac0..b1e100e 100644
--- a/keyctl.c
+++ b/keyctl.c
@@ -139,10 +139,10 @@ static const struct command commands[] = {
{ act_keyctl_timeout, "timeout", "<key> <timeout>" },
{ act_keyctl_unlink, "unlink", "<key> [<keyring>]" },
{ act_keyctl_update, "update", "[-x] <key> <data>" },
- { act_keyctl_watch, "watch", "<key>" },
+ { act_keyctl_watch, "watch", "[-f<filters>] <key>" },
{ act_keyctl_watch_add, "watch_add", "<fd> <key>" },
{ act_keyctl_watch_rm, "watch_rm", "<fd> <key>" },
- { act_keyctl_watch_session, "watch_session", "[-n <name>] <notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...]" },
+ { act_keyctl_watch_session, "watch_session", "[-f<filters>] [-n <name>] <notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...]" },
{ act_keyctl_watch_sync, "watch_sync", "<fd>" },
{ act_keyctl_test, "--test", "..." },
{ NULL, NULL, NULL }
diff --git a/keyctl_watch.c b/keyctl_watch.c
index 191fe51..5415d4c 100644
--- a/keyctl_watch.c
+++ b/keyctl_watch.c
@@ -38,6 +38,16 @@ static key_serial_t session;
static int watch_fd;
static int debug;
+static struct watch_notification_filter filter = {
+ .nr_filters = 0,
+ .filters = {
+ /* Reserve a slot */
+ [0] = {
+ .type = WATCH_TYPE_KEY_NOTIFY,
+ },
+ },
+};
+
static inline bool after_eq(unsigned int a, unsigned int b)
{
return (signed int)(a - b) >= 0;
@@ -172,17 +182,6 @@ int consumer(FILE *log, FILE *gc, int fd, struct watch_queue_buffer *buf)
exit(0);
}
-static struct watch_notification_filter filter = {
- .nr_filters = 1,
- .__reserved = 0,
- .filters = {
- [0] = {
- .type = WATCH_TYPE_KEY_NOTIFY,
- .subtype_filter[0] = UINT_MAX,
- },
- },
-};
-
/*
* Open the watch device and allocate a buffer.
*/
@@ -199,7 +198,8 @@ static int open_watch(struct watch_queue_buffer **_buf)
if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1)
error("/dev/watch_queue(size)");
- if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1)
+ if (filter.nr_filters &&
+ ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1)
error("/dev/watch_queue(filter)");
page_size = sysconf(_SC_PAGESIZE);
@@ -213,18 +213,84 @@ static int open_watch(struct watch_queue_buffer **_buf)
}
/*
+ * Parse a filter character representation into a subtype number.
+ */
+static bool parse_subtype(struct watch_notification_type_filter *t, char filter)
+{
+ static const char filter_mapping[] =
+ "i" /* 0 NOTIFY_KEY_INSTANTIATED */
+ "p" /* 1 NOTIFY_KEY_UPDATED */
+ "l" /* 2 NOTIFY_KEY_LINKED */
+ "n" /* 3 NOTIFY_KEY_UNLINKED */
+ "c" /* 4 NOTIFY_KEY_CLEARED */
+ "r" /* 5 NOTIFY_KEY_REVOKED */
+ "v" /* 6 NOTIFY_KEY_INVALIDATED */
+ "s" /* 7 NOTIFY_KEY_SETATTR */
+ ;
+ const char *p;
+ unsigned int st_bits;
+ unsigned int st_index;
+ unsigned int st_bit;
+ int subtype;
+
+ p = strchr(filter_mapping, filter);
+ if (!p)
+ return false;
+
+ subtype = p - filter_mapping;
+ st_bits = sizeof(t->subtype_filter[0]) * 8;
+ st_index = subtype / st_bits;
+ st_bit = 1U << (subtype % st_bits);
+ t->subtype_filter[st_index] |= st_bit;
+ return true;
+}
+
+/*
+ * Parse filters.
+ */
+static void parse_watch_filter(char *str)
+{
+ struct watch_notification_filter *f = &filter;
+ struct watch_notification_type_filter *t0 = &f->filters[0];
+
+ f->nr_filters = 1;
+ t0->type = WATCH_TYPE_KEY_NOTIFY;
+
+ for (; *str; str++) {
+ if (parse_subtype(t0, *str))
+ continue;
+ fprintf(stderr, "Unknown filter character '%c'\n", *str);
+ exit(2);
+ }
+}
+
+/*
* Watch a key or keyring for changes.
*/
void act_keyctl_watch(int argc, char *argv[])
{
struct watch_queue_buffer *buf;
key_serial_t key;
- int wfd;
+ int wfd, opt;
- if (argc != 2)
+ while (opt = getopt(argc, argv, "f:"),
+ opt != -1) {
+ switch (opt) {
+ case 'f':
+ parse_watch_filter(optarg);
+ break;
+ default:
+ fprintf(stderr, "Unknown option\n");
+ exit(2);
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+ if (argc != 1)
format();
- key = get_key_id(argv[1]);
+ key = get_key_id(argv[0]);
wfd = open_watch(&buf);
if (keyctl_watch_key(key, wfd, 0x01) == -1)
@@ -356,12 +422,15 @@ void act_keyctl_watch_session(int argc, char *argv[])
FILE *log, *gc;
int wfd, tfd, opt, w, e = 0, e2 = 0;
- while (opt = getopt(argc, argv, "+dn:"),
+ while (opt = getopt(argc, argv, "+df:n:"),
opt != -1) {
switch (opt) {
case 'd':
debug = 1;
break;
+ case 'f':
+ parse_watch_filter(optarg);
+ break;
case 'n':
session_name = optarg;
break;
diff --git a/man/keyctl.1 b/man/keyctl.1
index f18f92d..2343762 100644
--- a/man/keyctl.1
+++ b/man/keyctl.1
@@ -115,13 +115,13 @@ keyctl \- key management facility control
.br
\fBkeyctl\fR pkey_decrypt <key> <pass> <datafile> <sigfile> [k=v]*
.br
-\fBkeyctl\fR watch <key>
+\fBkeyctl\fR watch [\-f<filters>] <key>
.br
\fBkeyctl\fR watch_add <fd> <key>
.br
\fBkeyctl\fR watch_rm <fd> <key>
.br
-\fBkeyctl\fR watch_session [-n <name>] \\
+\fBkeyctl\fR watch_session [\-f <filters>] [-n <name>] \\
<notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...]
.SH DESCRIPTION
This program is used to control the key management facility in various ways
@@ -954,9 +954,9 @@ keyctl pkey_verify $k 0 foo.hash foo.sig enc=pkcs1 hash=sha256
See asymmetric-key(7) for more information.
.SS Change notifications
-\fBkeyctl\fR watch <key>
+\fBkeyctl\fR watch [\-f<filters>] <key>
.br
-\fBkeyctl\fR watch_session [-n <name>] \\
+\fBkeyctl\fR watch_session [\-f <filters>] [-n <name>] \\
<notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...]
\fBkeyctl\fR watch_add <fd> <key>
.br
@@ -966,7 +966,24 @@ See asymmetric-key(7) for more information.
The
.B watch
command watches a single key, printing notifications to stdout until the key
-is destroyed.
+is destroyed. Filters can be employed to cut down the events that will be
+delivered. The
+.I filter
+string is a series of letters, each one of which enables a particular event
+subtype:
+.PP
+.RS
+.nf
+.BR i " - The key has been instantiated"
+.BR p " - The key has been updated"
+.BR l " - A link has been added to a keyring"
+.BR n " - A link has been removed from a keyring"
+.BR c " - A keyring has been cleared"
+.BR r " - A key has been revoked"
+.BR v " - A key has been invalidated"
+.BR s " - A key has had its attributes changed"
+.fi
+.RE
.PP
The output of the command looks like:
.PP
diff --git a/tests/keyctl/watch/bad-args/runtest.sh b/tests/keyctl/watch/bad-args/runtest.sh
new file mode 100644
index 0000000..8bbd8c0
--- /dev/null
+++ b/tests/keyctl/watch/bad-args/runtest.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+. ../../../prepare.inc.sh
+. ../../../toolbox.inc.sh
+
+
+# ---- do the actual testing ----
+
+result=PASS
+echo "++++ BEGINNING TEST" >$OUTPUTFILE
+
+# Attempt to watch an invalid key
+marker "CHECK WATCH INVALID KEY"
+watch_key --fail 0
+expect_error EINVAL
+
+# Add a user key to the session keyring for us to play with
+marker "ADD USER KEY"
+create_key --new=keyid user wibble stuff @s
+
+# Remove the key we just added
+marker "UNLINK KEY"
+unlink_key --wait $keyid @s
+
+# It should fail when we attempt to watch it
+marker "UPDATE UNLINKED KEY"
+watch_key --fail $keyid
+expect_error ENOKEY
+
+# Try a number of dodgy filters
+marker "CHECK DODGY FILTERS"
+watch_key --fail2 -fZ @s
+watch_key --fail2 -fZ -fQ @s
+watch_key --fail2 -f: @s
+
+echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE
+
+# --- then report the results in the database ---
+toolbox_report_result $TEST $result
diff --git a/tests/keyctl/watch/noargs/runtest.sh b/tests/keyctl/watch/noargs/runtest.sh
new file mode 100644
index 0000000..e2bc7d7
--- /dev/null
+++ b/tests/keyctl/watch/noargs/runtest.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+. ../../../prepare.inc.sh
+. ../../../toolbox.inc.sh
+
+
+# ---- do the actual testing ----
+
+result=PASS
+echo "++++ BEGINNING TEST" >$OUTPUTFILE
+
+# check that no arguments fails correctly
+marker "WATCH NO ARGS"
+expect_args_error keyctl watch
+
+# check that two arguments fail correctly
+marker "WATCH TWO ARGS"
+expect_args_error keyctl watch @s @s
+
+# Try a dodgy filter
+marker "CHECK BAD FILTER"
+expect_args_error keyctl watch_key -f 0
+
+echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE
+
+# --- then report the results in the database ---
+toolbox_report_result $TEST $result
diff --git a/tests/toolbox.inc.sh b/tests/toolbox.inc.sh
index 81d639c..609a6c7 100644
--- a/tests/toolbox.inc.sh
+++ b/tests/toolbox.inc.sh
@@ -1834,6 +1834,100 @@ function set_gc_delay()
###############################################################################
#
+# watch a key
+#
+###############################################################################
+function watch_key ()
+{
+ my_exitval=0
+ if [ "x$1" = "x--fail" ]
+ then
+ my_exitval=1
+ shift
+ elif [ "x$1" = "x--fail2" ]
+ then
+ my_exitval=2
+ shift
+ fi
+
+ echo keyctl watch "$@" >>$OUTPUTFILE
+ nice --adjustment=-3 keyctl watch "$@" >>$PWD/notify.log 2>>$OUTPUTFILE
+ if [ $? != $my_exitval ]
+ then
+ failed
+ fi
+}
+
+###############################################################################
+#
+# Check for a notification
+#
+# expect_notification [--filter=[i|p|l|n|c|r|v|s]] <keyid> <op> [<alt>]
+#
+###############################################################################
+function expect_notification ()
+{
+ local want
+
+ local filter=""
+ case "x$1" in
+ x--filter*)
+ case $1 in
+ --filter=) filter=;;
+ --filter=i) filter=inst;;
+ --filter=p) filter=upd;;
+ --filter=l) filter=link;;
+ --filter=n) filter=unlk;;
+ --filter=c) filter=clr;;
+ --filter=r) filter=rev;;
+ --filter=v) filter=inv;;
+ --filter=s) filter=attr;;
+ *)
+ echo "Unknown param $1 to expect_notification()" >&2
+ exit 2
+ ;;
+ esac
+ shift
+ ;;
+ esac
+
+ if [ $# = 2 ]
+ then
+ want="$1 $2"
+ op=$2
+ elif [ $# = 3 ]
+ then
+ want="$1 $2 $3"
+ op=$2
+ else
+ echo "Wrong parameters to expect_notification" >&2
+ exit 2
+ fi
+
+ if tail -3 $PWD/notify.log | grep "^${want}\$" >/dev/null
+ then
+ echo "Found notification '$*'" >>$OUTPUTFILE
+ if [ "$filter" != "" -a $op != "$filter" ]
+ then
+ echo "Notification '$want' should be filtered" >&2
+ failed
+ fi
+ else
+ echo "Notification '$*' not present" >>$OUTPUTFILE
+ if [ "$filter" = "" ]
+ then
+ echo "Missing notification '$want'" >&2
+ failed
+ elif [ $op = "$filter" ]
+ then
+ echo "Notification unexpectedly filtered '$want' $filter" >&2
+ failed
+ fi
+ fi
+}
+
+###############################################################################
+#
# Note the creation of a new key
#
# expect_new_key <variable_name> <keyring> [<expected_id>]