Add support for keyctl_alter()

Add support for keyctl_alter() in the library:

	long keyctl_alter(key_serial_t id,
			  const char *command,
			  const void *data,
			  size_t data_size);

and expose this through the keyctl command:

	keyctl alter <key> <type> <command> [<data>]
	echo <data> | keyctl palter <key> <type> <command>

Signed-off-by: David Howells <dhowells@redhat.com>
diff --git a/keyctl.c b/keyctl.c
index 7e5ce6f..12826bd 100644
--- a/keyctl.c
+++ b/keyctl.c
@@ -66,10 +66,13 @@
 static nr void act_keyctl_purge(int argc, char *argv[]);
 static nr void act_keyctl_invalidate(int argc, char *argv[]);
 static nr void act_keyctl_get_persistent(int argc, char *argv[]);
+static nr void act_keyctl_alter(int argc, char *argv[]);
+static nr void act_keyctl_palter(int argc, char *argv[]);
 
 const struct command commands[] = {
 	{ act_keyctl___version,	"--version",	"" },
 	{ act_keyctl_add,	"add",		"<type> <desc> <data> <keyring>" },
+	{ act_keyctl_alter,	"alter",	"<key> <type> <cmd> [<data>]" },
 	{ act_keyctl_chgrp,	"chgrp",	"<key> <gid>" },
 	{ act_keyctl_chown,	"chown",	"<key> <uid>" },
 	{ act_keyctl_clear,	"clear",	"<keyring>" },
@@ -83,6 +86,7 @@
 	{ act_keyctl_new_session, "new_session",	"" },
 	{ act_keyctl_newring,	"newring",	"<name> <keyring>" },
 	{ act_keyctl_padd,	"padd",		"<type> <desc> <keyring>" },
+	{ act_keyctl_palter,	"palter",	"<key> <type> <cmd>" },
 	{ act_keyctl_pinstantiate, "pinstantiate","<key> <keyring>" },
 	{ act_keyctl_pipe,	"pipe",		"<key>" },
 	{ act_keyctl_prequest2,	"prequest2",	"<type> <desc> [<dest_keyring>]" },
@@ -1627,6 +1631,112 @@
 
 /*****************************************************************************/
 /*
+ * Alter a key
+ */
+static void act_keyctl_alter(int argc, char *argv[])
+{
+	key_serial_t key;
+	size_t data_size, tsz, csz;
+	char *command, *data;
+	long ret;
+
+	if (argc < 4 || argc > 5)
+		format();
+
+	tsz = strlen(argv[2]);
+	csz = strlen(argv[3]);
+	if (!tsz || !csz ||
+	    tsz >= 4096 - 1 ||
+	    csz > 4096 - (1 + tsz + 1))
+		goto cmd_limit;
+
+	if (memchr(argv[2], ' ', tsz) != NULL)
+		format();
+
+	if (argc == 5) {
+		data = argv[4];
+		data_size = strlen(argv[4]);
+		if (data_size > 1024 * 1024)
+			goto data_limit;
+	} else {
+		data = NULL;
+		data_size = 0;
+	}
+
+	key = get_key_id(argv[1]);
+
+	command = malloc(4096);
+	if (!command)
+		error("malloc");
+
+	memcpy(command, argv[2], tsz);
+	command[tsz] = ' ';
+	memcpy(command + tsz + 1, argv[3] + 1, csz);
+
+	ret = keyctl_alter(key, command, data, data_size);
+	if (ret < 0)
+		error("keyctl_alter");
+
+	exit(0);
+
+cmd_limit:
+	fprintf(stderr, "Type and command strings are limited to 4KB-1 in total\n");
+	exit(2);
+
+data_limit:
+	fprintf(stderr, "The data is limited to 1MB in total\n");
+	exit(2);
+}
+
+/*****************************************************************************/
+/*
+ * Alter a key with data read from stdin.
+ */
+static void act_keyctl_palter(int argc, char *argv[])
+{
+	key_serial_t key;
+	size_t data_size, tsz, csz;
+	char *command, *data;
+	long ret;
+
+	if (argc != 4)
+		format();
+
+	tsz = strlen(argv[2]);
+	csz = strlen(argv[3]);
+	if (!tsz || !csz ||
+	    tsz >= 4096 - 1 ||
+	    csz > 4096 - (1 + tsz + 1))
+		goto cmd_limit;
+
+	if (memchr(argv[2], ' ', tsz) != NULL)
+		format();
+
+	data = grab_stdin(&data_size);
+
+	key = get_key_id(argv[1]);
+
+	command = malloc(4096);
+	if (!command)
+		error("malloc");
+
+	memcpy(command, argv[2], tsz);
+	command[tsz] = ' ';
+	memcpy(command + tsz + 1, argv[3] + 1, csz);
+
+	ret = keyctl_alter(key, command, data, data_size);
+	if (ret < 0)
+		error("keyctl_alter");
+
+	exit(0);
+
+cmd_limit:
+	fprintf(stderr, "Type and command strings are limited to 4KB-1 in total\n");
+	exit(2);
+}
+
+/*****************************************************************************/
+/*
  * parse a key identifier
  */
 static key_serial_t get_key_id(char *arg)
diff --git a/keyutils.c b/keyutils.c
index 8856c8a..359073c 100644
--- a/keyutils.c
+++ b/keyutils.c
@@ -234,6 +234,14 @@
 	return keyctl(KEYCTL_GET_PERSISTENT, uid, id);
 }
 
+long keyctl_alter(key_serial_t id,
+		  const char *command,
+		  const void *data,
+		  size_t data_size)
+{
+	return keyctl(KEYCTL_ALTER, id, command, data, data_size);
+}
+
 /*****************************************************************************/
 /*
  * fetch key description into an allocated buffer
diff --git a/keyutils.h b/keyutils.h
index b9ff7e8..b4aada6 100644
--- a/keyutils.h
+++ b/keyutils.h
@@ -98,6 +98,7 @@
 #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
 #define KEYCTL_INVALIDATE		21	/* invalidate a key */
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
+#define KEYCTL_ALTER			23	/* alter a key */
 
 /*
  * syscall wrappers
@@ -152,6 +153,10 @@
 				   key_serial_t ringid);
 extern long keyctl_invalidate(key_serial_t id);
 extern long keyctl_get_persistent(uid_t uid, key_serial_t id);
+extern long keyctl_alter(key_serial_t id,
+			 const char *command,
+			 const void *data,
+			 size_t data_size);
 
 /*
  * utilities
diff --git a/keyutils.spec b/keyutils.spec
index 3312f84..895e085 100644
--- a/keyutils.spec
+++ b/keyutils.spec
@@ -2,7 +2,7 @@
 %define verminor 5.9
 %define version %{vermajor}.%{verminor}
 %define libapivermajor 1
-%define libapiversion %{libapivermajor}.5
+%define libapiversion %{libapivermajor}.6
 
 # % define buildid .local
 
diff --git a/man/keyctl.1 b/man/keyctl.1
index 4d6cbe5..1abc089 100644
--- a/man/keyctl.1
+++ b/man/keyctl.1
@@ -90,6 +90,10 @@
 \fBkeyctl\fR purge -s <type> <desc>
 .br
 \fBkeyctl\fR get_persistent <keyring> [<uid>]
+.br
+\fBkeyctl\fR alter <key> <type> <cmd> [<data>]
+.br
+\fBkeyctl\fR palter <key> <type> <cmd>
 .SH DESCRIPTION
 This program is used to control the key management facility in various ways
 using a variety of subcommands.
@@ -745,6 +749,22 @@
 If a UID other than the process's real or effective UIDs is specified, then an
 error will be given if the process does not have the CAP_SETUID capability.
 .P
+(*) \fBAlter a key in a type-dependent fashion\fR
+.P
+\fBkeyctl\fR alter <key> <type> <cmd> [<data>]
+.br
+\fBkeyctl\fR palter <key> <type> <cmd>
+.P
+This command performs a type-dependent alteration of the nominated key.  The
+key to be operated on is supplied as the first regular argument, then the
+expected key type name and then the name of the command.
+.P
+In the \fBalter\fR variant of the command, supplementary data may be passed as
+an additional argument.
+.P
+The \fBpalter\fR variant of the command reads the supplementary data from stdin
+rather than taking it from the command line.
+.P
 .SH ERRORS
 .P
 There are a number of common errors returned by this program:
diff --git a/man/keyctl.3 b/man/keyctl.3
index 440c270..1bcdfcf 100644
--- a/man/keyctl.3
+++ b/man/keyctl.3
@@ -33,6 +33,8 @@
 .RE
 .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .SH KEYCTL FUNCTIONS
+.BR keyctl_alter (3)
+.br
 .BR keyctl_assume_authority (3)
 .br
 .BR keyctl_chown (3)
diff --git a/man/keyctl_alter.3 b/man/keyctl_alter.3
new file mode 100644
index 0000000..9259347
--- /dev/null
+++ b/man/keyctl_alter.3
@@ -0,0 +1,94 @@
+.\"
+.\" Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
+.\" Written by David Howells (dhowells@redhat.com)
+.\"
+.\" This program is free software; you can redistribute it and/or
+.\" modify it under the terms of the GNU General Public License
+.\" as published by the Free Software Foundation; either version
+.\" 2 of the License, or (at your option) any later version.
+.\"
+.TH KEYCTL_ALTER 3 "10 Mar 2014" Linux "Linux Key Management Calls"
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH NAME
+keyctl_alter \- Alter a key
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH SYNOPSIS
+.nf
+.B #include <keyutils.h>
+.sp
+.BI "long keyctl_alter(key_serial_t " key ", const char *" command ","
+.BI "                  const void *" data ", size_t " data_size ");"
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH DESCRIPTION
+.BR keyctl_alter ()
+allows the user to make some type-specific alteration to a
+.IR key .
+.P
+The caller must have
+.B write
+permission on the key to perform this.
+.P
+The
+.B command
+argument is a printable string that begins with the name of the target type,
+then a space then a command indicating the alteration to be performed.  The
+command string, including the NUL terminator, may not exceed 4KB in size.
+.P
+The
+.BR data " and " data_size
+arguments indicate an optional buffer of supplementary data for use by the
+command.  The supplementary data may not exceed 1MB in size.  It need not be
+NUL terminated.
+.P
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH RETURN VALUE
+On success
+.BR keyctl_alter ()
+returns 0.
+.P
+On error, the value
+.B -1
+will be returned and errno will have been set to an appropriate error.
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH ERRORS
+.TP
+.B ENOKEY
+The key specified is invalid.
+.TP
+.B EKEYEXPIRED
+The key specified has expired.
+.TP
+.B EKEYREVOKED
+The key specified had been revoked.
+.TP
+.B EACCES
+The key exists, but is not
+.B writable
+by the calling process.
+.TP
+.B EOPNOTSUPP
+The key type does not support the control operation.
+.P
+The key type may also return other errors in response to the given command.
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH LINKING
+This is a library function that can be found in
+.IR libkeyutils .
+When linking,
+.B -lkeyutils
+should be specified to the linker.
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.SH SEE ALSO
+.BR keyctl (1),
+.br
+.BR add_key (2),
+.br
+.BR keyctl (2),
+.br
+.BR request_key (2),
+.br
+.BR keyctl (3),
+.br
+.BR keyutils (7),
+.br
+.BR keyrings (7)
diff --git a/tests/keyctl/alter/bad-args/runtest.sh b/tests/keyctl/alter/bad-args/runtest.sh
new file mode 100644
index 0000000..eca6e70
--- /dev/null
+++ b/tests/keyctl/alter/bad-args/runtest.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+. ../../../prepare.inc.sh
+. ../../../toolbox.inc.sh
+
+
+# ---- do the actual testing ----
+
+result=PASS
+echo "++++ BEGINNING TEST" >$OUTPUTFILE
+
+# check that an empty key id fails correctly
+marker "CHECK EMPTY KEY ID"
+alter_key --fail "" some-type do-something
+expect_error EINVAL
+
+# check that an invalid key id fails correctly
+marker "CHECK INVALID KEY ID"
+alter_key --fail 0 some-type do-something
+expect_error EINVAL
+
+# check that an empty key type fails correctly
+marker "CHECK EMPTY KEY TYPE"
+alter_key --fail @s "" do-something
+expect_error EINVAL
+
+# check that an empty command fails correctly
+marker "CHECK EMPTY COMMAND"
+alter_key --fail @s some-type ""
+expect_error EINVAL
+
+# check that an empty data argument fails correctly
+marker "CHECK EMPTY DATA"
+alter_key --fail @s some-type do-something ""
+expect_error EINVAL
+
+echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE
+
+# --- then report the results in the database ---
+toolbox_report_result $TEST $result
diff --git a/tests/keyctl/alter/noargs/runtest.sh b/tests/keyctl/alter/noargs/runtest.sh
new file mode 100644
index 0000000..53c312b
--- /dev/null
+++ b/tests/keyctl/alter/noargs/runtest.sh
@@ -0,0 +1,31 @@
+#!/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 "ADD NO ARGS"
+expect_args_error keyctl alter
+
+# check that one argument fails correctly
+marker "ADD ONE ARG"
+expect_args_error keyctl alter @s
+
+# check that two arguments fail correctly
+marker "ADD TWO ARGS"
+expect_args_error keyctl alter @s keyring
+
+# check that five arguments fail correctly
+marker "ADD FIVE ARGS"
+expect_args_error keyctl alter @s keyring do-something some-data foo
+
+echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE
+
+# --- then report the results in the database ---
+toolbox_report_result $TEST $result
diff --git a/tests/keyctl/palter/bad-args/runtest.sh b/tests/keyctl/palter/bad-args/runtest.sh
new file mode 100644
index 0000000..6253cf6
--- /dev/null
+++ b/tests/keyctl/palter/bad-args/runtest.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+. ../../../prepare.inc.sh
+. ../../../toolbox.inc.sh
+
+
+# ---- do the actual testing ----
+
+result=PASS
+echo "++++ BEGINNING TEST" >$OUTPUTFILE
+
+# check that an empty key id fails correctly
+marker "CHECK EMPTY KEY ID"
+palter_key --fail some-data "" some-type do-something
+expect_error EINVAL
+
+# check that an invalid key id fails correctly
+marker "CHECK INVALID KEY ID"
+palter_key --fail some-data 0 some-type do-something
+expect_error EINVAL
+
+# check that an empty key type fails correctly
+marker "CHECK EMPTY KEY TYPE"
+palter_key --fail some-data @s "" do-something
+expect_error EINVAL
+
+# check that an empty command fails correctly
+marker "CHECK EMPTY COMMAND"
+palter_key --fail some-data @s some-type ""
+expect_error EINVAL
+
+# check that an empty data argument fails correctly
+marker "CHECK EMPTY DATA"
+palter_key --fail "" @s some-type do-something
+expect_error EINVAL
+
+echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE
+
+# --- then report the results in the database ---
+toolbox_report_result $TEST $result
diff --git a/tests/keyctl/palter/noargs/runtest.sh b/tests/keyctl/palter/noargs/runtest.sh
new file mode 100644
index 0000000..bd394b6
--- /dev/null
+++ b/tests/keyctl/palter/noargs/runtest.sh
@@ -0,0 +1,31 @@
+#!/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 "ADD NO ARGS"
+expect_args_error keyctl palter
+
+# check that one argument fails correctly
+marker "ADD ONE ARG"
+expect_args_error keyctl palter @s
+
+# check that two arguments fail correctly
+marker "ADD TWO ARGS"
+expect_args_error keyctl palter @s keyring
+
+# check that five arguments fail correctly
+marker "ADD FOUR ARGS"
+expect_args_error keyctl palter @s keyring do-something foo
+
+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 88608a3..d0e4e14 100644
--- a/tests/toolbox.inc.sh
+++ b/tests/toolbox.inc.sh
@@ -1053,6 +1053,53 @@
 
 ###############################################################################
 #
+# Alter a key
+#
+###############################################################################
+function alter_key ()
+{
+    my_exitval=0
+    if [ "x$1" = "x--fail" ]
+    then
+	my_exitval=1
+	shift
+    fi
+
+    echo keyctl alter "$@" >>$OUTPUTFILE
+    keyctl alter "$@" >>$OUTPUTFILE 2>&1
+    if [ $? != $my_exitval ]
+    then
+	failed
+    fi
+}
+
+###############################################################################
+#
+# Alter a key, piping in the data
+#
+###############################################################################
+function palter_key ()
+{
+    my_exitval=0
+    if [ "x$1" = "x--fail" ]
+    then
+	my_exitval=1
+	shift
+    fi
+
+    data="$1"
+    shift
+
+    echo echo -n $data \| keyctl palter "$@" >>$OUTPUTFILE
+    echo -n $data | keyctl palter "$@" >>$OUTPUTFILE 2>&1
+    if [ $? != $my_exitval ]
+    then
+	failed
+    fi
+}
+
+###############################################################################
+#
 # Make sure we sleep at least N seconds
 #
 ###############################################################################
diff --git a/version.lds b/version.lds
index 5f07463..27653a9 100644
--- a/version.lds
+++ b/version.lds
@@ -61,3 +61,9 @@
 	find_key_by_type_and_desc;
 
 } KEYUTILS_1.4;
+
+KEYUTILS_1.6 {
+	/* management functions */
+	keyctl_control;
+
+} KEYUTILS_1.5;