[3/4] nfs-utils: Add support for further ${variable} expansions in nfs.conf
diff mbox series

Message ID d4ff596fa989dafec4f15fc11f47b5c1088e9f0d.camel@redhat.com
State New
Headers show
Series
  • nfs-utils: nfs.conf features to enable use of machine-id as nfs4_unique_id
Related show

Commit Message

Alice Mitchell July 10, 2020, 4:42 p.m. UTC
This adds support for substituting in the systems machine_id as well as random generated uuid or hostname, 
and caches the results
---
 support/nfs/conffile.c | 268 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 257 insertions(+), 11 deletions(-)

Comments

Steve Dickson July 15, 2020, 5:42 p.m. UTC | #1
Hello,

On 7/10/20 12:42 PM, Alice Mitchell wrote:
> This adds support for substituting in the systems machine_id as well as random generated uuid or hostname, 
> and caches the results
Just curious... should the nfs.conf man page be updated to explain this new support?

steved.
> ---
>  support/nfs/conffile.c | 268 +++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 257 insertions(+), 11 deletions(-)
> 
> diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
> index cbeef10d..58c03911 100644
> --- a/support/nfs/conffile.c
> +++ b/support/nfs/conffile.c
> @@ -40,6 +40,7 @@
>  #include <sys/stat.h>
>  #include <netinet/in.h>
>  #include <arpa/inet.h>
> +#include <linux/if_alg.h>
>  #include <ctype.h>
>  #include <fcntl.h>
>  #include <stdio.h>
> @@ -110,12 +111,66 @@ struct conf_binding {
>    char *tag;
>    char *value;
>    int is_default;
> +  char *cache;
>  };
>  
>  LIST_HEAD (conf_bindings, conf_binding) conf_bindings[256];
>  
> +typedef char * (*expand_fn_t)(void);
> +struct expansion_types {
> +	const char *name;
> +	expand_fn_t func;
> +};
> +
> +typedef struct {
> +	uint8_t bytes[16];
> +} id128_t;
> +
> +/*
> + * Application ID for use with generating a machine-id string
> + */
> +static id128_t nfs_appid = {.bytes = {0xff,0x3b,0xf0,0x0f,0x34,0xa6,0x43,0xc5, \
> +                                       0x93,0xdd,0x16,0xdc,0x7c,0xeb,0x88,0xc8}};
> +
>  const char *modified_by = NULL;
>  
> +static __inline__ char
> +hexchar(int x) {
> +	static const char table[16] = "0123456789abcdef";
> +	return table[x & 15];
> +}
> +
> +static __inline__ int
> +unhexchar(char h)
> +{
> +	if (h >= '0' && h <= '9')
> +		return h - '0';
> +	if (h >= 'a' && h <= 'f')
> +		return h - 'a' + 10;
> +	if (h >= 'A' && h <= 'F')
> +		return h - 'A' + 10;
> +	return -1;
> +}
> +
> +static char *
> +tohexstr(const unsigned char *data, int len)
> +{
> +	int i;
> +	char *result = NULL;
> +
> +	result = calloc(1, (len*2)+1);
> +	if (!result) {
> +		xlog(L_ERROR, "malloc error formatting string");
> +		return NULL;
> +	}
> +
> +	for (i = 0; i < len; i++) {
> +		result[i*2] = hexchar(data[i] >> 4);
> +		result[i*2+1] = hexchar(data[i] & 0x0F);
> +	}
> +	return result;
> +}
> +
>  static __inline__ uint8_t
>  conf_hash(const char *s)
>  {
> @@ -128,6 +183,201 @@ conf_hash(const char *s)
>  	return hash;
>  }
>  
> +static int
> +id128_from_string(const char s[], id128_t *ret)
> +{
> +	id128_t t;
> +	unsigned int n, i;
> +	for (n=0, i=0; n<16; ) {
> +		int a, b;
> +		a = unhexchar(s[i++]);
> +		if (a < 0)
> +			return 1;
> +		b = unhexchar(s[i++]);
> +		if (b < 0)
> +			return 1;
> +
> +		t.bytes[n++] = (a << 4) | b;
> +	}
> +	if (s[i] != 0)
> +		return 1;
> +	if (ret)
> +		*ret = t;
> +	return 0;
> +}
> +
> +/*
> + * cryptographic hash (sha256) data into a hex encoded string
> + */
> +static char *
> +strhash(unsigned char *key, size_t keylen, unsigned char *data, size_t dlen)
> +{
> +	union {
> +		struct sockaddr sa;
> +		struct sockaddr_alg alg;
> +	} sa;
> +	int sock = -1;
> +	int hfd = -1;
> +	uint8_t digest[129];
> +	int n;
> +	char *result = NULL;
> +
> +	memset(&sa, 0, sizeof(sa));
> +	sa.alg.salg_family = AF_ALG;
> +	strcpy((char *)sa.alg.salg_type, "hash");
> +	strcpy((char *)sa.alg.salg_name, "hmac(sha256)");
> +
> +	sock = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
> +	if (sock < 0) {
> +		xlog(L_ERROR, "error creating socket");
> +		goto cleanup;
> +	}
> +
> +	if (bind(sock, (struct sockaddr *)&sa.sa, sizeof(sa)) < 0) {
> +		xlog(L_ERROR, "error opening khash interface");
> +		goto cleanup;
> +	}
> +
> +	if (key && keylen > 0) {
> +		if (setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, keylen) < 0) {
> +			xlog(L_ERROR, "Error setting key: %s", strerror(errno));
> +			goto cleanup;
> +		}
> +	}
> +
> +	hfd = accept4(sock, NULL, 0, SOCK_CLOEXEC);
> +	if (hfd < 0) {
> +		xlog(L_ERROR, "Error initiating khash: %s", strerror(errno));
> +		goto cleanup;
> +	}
> +
> +	n = send(hfd, data, dlen, 0);
> +	if (n < 0) {
> +		xlog(L_ERROR, "Error updating khash: %s", strerror(errno));
> +		goto cleanup;
> +	}
> +
> +	n = recv(hfd, digest, sizeof(digest), 0);
> +	if (n < 0) {
> +		xlog(L_ERROR, "Error fetching khash: %s", strerror(errno));
> +		goto cleanup;
> +	}
> +
> +	result = tohexstr(digest, n);
> +cleanup:
> +	if (sock != -1)
> +		close(sock);
> +	if (hfd != -1)
> +		close(hfd);
> +	if (hfd != -1)
> +		close(hfd);
> +
> +	return result;
> +}
> +
> +/*
> + * Read one line of content from a file
> + */
> +static char *
> +read_oneline(const char *filename)
> +{
> +	char *content = conf_readfile(filename);
> +	char *end;
> +
> +	if (content == NULL)
> +		return NULL;
> +
> +	/* trim to only the first line */
> +	end = strchr(content, '\n');
> +	if (end != NULL)
> +		*end = '\0';
> +	end = strchr(content, '\r');
> +	if (end != NULL)
> +		*end = '\0';
> +
> +	return content;
> +}
> +
> +static char *
> +expand_machine_id(void)
> +{
> +	char *key = read_oneline("/etc/machine-id");
> +	id128_t mid;
> +	char * result = NULL;
> +	size_t idlen = 0;
> +
> +	if (key == NULL)
> +		return NULL;
> +
> +	idlen = strlen(key);
> +	if (!id128_from_string(key, &mid)) {
> +		result = strhash(mid.bytes, sizeof(mid), nfs_appid.bytes, sizeof(nfs_appid));
> +		if (result && strlen(result) > idlen)
> +			result[idlen]=0;
> +	}
> +	free(key);
> +	return result;
> +}
> +
> +static char *
> +expand_random_uuid(void)
> +{
> +	return read_oneline("/proc/sys/kernel/random/uuid");
> +}
> +
> +static char *
> +expand_hostname(void)
> +{
> +	int maxlen = HOST_NAME_MAX + 1;
> +	char * hostname = calloc(1, maxlen);
> +
> +	if (!hostname)
> +		return NULL;
> +	if ((gethostname(hostname, maxlen)) == -1) {
> +		free(hostname);
> +		return NULL;
> +	}
> +	return hostname;
> +}
> +
> +static struct expansion_types  var_expansions[] = {
> +	{ "machine_id", expand_machine_id },
> +	{ "machine-id", expand_machine_id },
> +	{ "random-uuid", expand_random_uuid },
> +	{ "hostname", expand_hostname },
> +};
> +
> +/* Deal with more complex variable substitutions */
> +static char *
> +expand_variable(const char *name)
> +{
> +	size_t len;
> +
> +	if (name == NULL || name[0] != '$')
> +		return NULL;
> +
> +	len = strlen(name);
> +	if (name[1] == '{' && name[len-1] == '}') {
> +		char *varname = strndupa(&name[2], len-3);
> +
> +		for (size_t i=0; i<sizeof(var_expansions); i++) {
> +			if (!strcasecmp(varname, var_expansions[i].name)) {
> +				return var_expansions[i].func();
> +			}
> +		}
> +		xlog_warn("get_conf: Unknown variable ${%s}", varname);
> +	} else {
> +		/* expand $name from [environment] section,
> +		* or from environment
> +		*/
> +		char *env = getenv(&name[1]);
> +		if (env == NULL || *env == 0)
> +			env = conf_get_section("environment", NULL, &name[1]);
> +		return env;
> +	}
> +	return NULL;
> +}
> +
>  /*
>   * free all the component parts of a conf_binding struct
>   */
> @@ -143,6 +393,8 @@ static void free_confbind(struct conf_binding *cb)
>  		free(cb->tag);
>  	if (cb->value)
>  		free(cb->value);
> +	if (cb->cache)
> +		free(cb->cache);
>  	free(cb);
>  }
>  
> @@ -782,7 +1034,7 @@ char *
>  conf_get_section(const char *section, const char *arg, const char *tag)
>  {
>  	struct conf_binding *cb;
> -retry:
> +
>  	cb = LIST_FIRST (&conf_bindings[conf_hash (section)]);
>  	for (; cb; cb = LIST_NEXT (cb, link)) {
>  		if (strcasecmp(section, cb->section) != 0)
> @@ -794,19 +1046,13 @@ retry:
>  		if (strcasecmp(tag, cb->tag) != 0)
>  			continue;
>  		if (cb->value[0] == '$') {
> -			/* expand $name from [environment] section,
> -			 * or from environment
> -			 */
> -			char *env = getenv(cb->value+1);
> -			if (env && *env)
> -				return env;
> -			section = "environment";
> -			tag = cb->value + 1;
> -			goto retry;
> +			if (!cb->cache)
> +				cb->cache = expand_variable(cb->value);
> +			return cb->cache;
>  		}
>  		return cb->value;
>  	}
> -	return 0;
> +	return NULL;
>  }
>  
>  /*
>

Patch
diff mbox series

diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
index cbeef10d..58c03911 100644
--- a/support/nfs/conffile.c
+++ b/support/nfs/conffile.c
@@ -40,6 +40,7 @@ 
 #include <sys/stat.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <linux/if_alg.h>
 #include <ctype.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -110,12 +111,66 @@  struct conf_binding {
   char *tag;
   char *value;
   int is_default;
+  char *cache;
 };
 
 LIST_HEAD (conf_bindings, conf_binding) conf_bindings[256];
 
+typedef char * (*expand_fn_t)(void);
+struct expansion_types {
+	const char *name;
+	expand_fn_t func;
+};
+
+typedef struct {
+	uint8_t bytes[16];
+} id128_t;
+
+/*
+ * Application ID for use with generating a machine-id string
+ */
+static id128_t nfs_appid = {.bytes = {0xff,0x3b,0xf0,0x0f,0x34,0xa6,0x43,0xc5, \
+                                       0x93,0xdd,0x16,0xdc,0x7c,0xeb,0x88,0xc8}};
+
 const char *modified_by = NULL;
 
+static __inline__ char
+hexchar(int x) {
+	static const char table[16] = "0123456789abcdef";
+	return table[x & 15];
+}
+
+static __inline__ int
+unhexchar(char h)
+{
+	if (h >= '0' && h <= '9')
+		return h - '0';
+	if (h >= 'a' && h <= 'f')
+		return h - 'a' + 10;
+	if (h >= 'A' && h <= 'F')
+		return h - 'A' + 10;
+	return -1;
+}
+
+static char *
+tohexstr(const unsigned char *data, int len)
+{
+	int i;
+	char *result = NULL;
+
+	result = calloc(1, (len*2)+1);
+	if (!result) {
+		xlog(L_ERROR, "malloc error formatting string");
+		return NULL;
+	}
+
+	for (i = 0; i < len; i++) {
+		result[i*2] = hexchar(data[i] >> 4);
+		result[i*2+1] = hexchar(data[i] & 0x0F);
+	}
+	return result;
+}
+
 static __inline__ uint8_t
 conf_hash(const char *s)
 {
@@ -128,6 +183,201 @@  conf_hash(const char *s)
 	return hash;
 }
 
+static int
+id128_from_string(const char s[], id128_t *ret)
+{
+	id128_t t;
+	unsigned int n, i;
+	for (n=0, i=0; n<16; ) {
+		int a, b;
+		a = unhexchar(s[i++]);
+		if (a < 0)
+			return 1;
+		b = unhexchar(s[i++]);
+		if (b < 0)
+			return 1;
+
+		t.bytes[n++] = (a << 4) | b;
+	}
+	if (s[i] != 0)
+		return 1;
+	if (ret)
+		*ret = t;
+	return 0;
+}
+
+/*
+ * cryptographic hash (sha256) data into a hex encoded string
+ */
+static char *
+strhash(unsigned char *key, size_t keylen, unsigned char *data, size_t dlen)
+{
+	union {
+		struct sockaddr sa;
+		struct sockaddr_alg alg;
+	} sa;
+	int sock = -1;
+	int hfd = -1;
+	uint8_t digest[129];
+	int n;
+	char *result = NULL;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.alg.salg_family = AF_ALG;
+	strcpy((char *)sa.alg.salg_type, "hash");
+	strcpy((char *)sa.alg.salg_name, "hmac(sha256)");
+
+	sock = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
+	if (sock < 0) {
+		xlog(L_ERROR, "error creating socket");
+		goto cleanup;
+	}
+
+	if (bind(sock, (struct sockaddr *)&sa.sa, sizeof(sa)) < 0) {
+		xlog(L_ERROR, "error opening khash interface");
+		goto cleanup;
+	}
+
+	if (key && keylen > 0) {
+		if (setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, keylen) < 0) {
+			xlog(L_ERROR, "Error setting key: %s", strerror(errno));
+			goto cleanup;
+		}
+	}
+
+	hfd = accept4(sock, NULL, 0, SOCK_CLOEXEC);
+	if (hfd < 0) {
+		xlog(L_ERROR, "Error initiating khash: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	n = send(hfd, data, dlen, 0);
+	if (n < 0) {
+		xlog(L_ERROR, "Error updating khash: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	n = recv(hfd, digest, sizeof(digest), 0);
+	if (n < 0) {
+		xlog(L_ERROR, "Error fetching khash: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	result = tohexstr(digest, n);
+cleanup:
+	if (sock != -1)
+		close(sock);
+	if (hfd != -1)
+		close(hfd);
+	if (hfd != -1)
+		close(hfd);
+
+	return result;
+}
+
+/*
+ * Read one line of content from a file
+ */
+static char *
+read_oneline(const char *filename)
+{
+	char *content = conf_readfile(filename);
+	char *end;
+
+	if (content == NULL)
+		return NULL;
+
+	/* trim to only the first line */
+	end = strchr(content, '\n');
+	if (end != NULL)
+		*end = '\0';
+	end = strchr(content, '\r');
+	if (end != NULL)
+		*end = '\0';
+
+	return content;
+}
+
+static char *
+expand_machine_id(void)
+{
+	char *key = read_oneline("/etc/machine-id");
+	id128_t mid;
+	char * result = NULL;
+	size_t idlen = 0;
+
+	if (key == NULL)
+		return NULL;
+
+	idlen = strlen(key);
+	if (!id128_from_string(key, &mid)) {
+		result = strhash(mid.bytes, sizeof(mid), nfs_appid.bytes, sizeof(nfs_appid));
+		if (result && strlen(result) > idlen)
+			result[idlen]=0;
+	}
+	free(key);
+	return result;
+}
+
+static char *
+expand_random_uuid(void)
+{
+	return read_oneline("/proc/sys/kernel/random/uuid");
+}
+
+static char *
+expand_hostname(void)
+{
+	int maxlen = HOST_NAME_MAX + 1;
+	char * hostname = calloc(1, maxlen);
+
+	if (!hostname)
+		return NULL;
+	if ((gethostname(hostname, maxlen)) == -1) {
+		free(hostname);
+		return NULL;
+	}
+	return hostname;
+}
+
+static struct expansion_types  var_expansions[] = {
+	{ "machine_id", expand_machine_id },
+	{ "machine-id", expand_machine_id },
+	{ "random-uuid", expand_random_uuid },
+	{ "hostname", expand_hostname },
+};
+
+/* Deal with more complex variable substitutions */
+static char *
+expand_variable(const char *name)
+{
+	size_t len;
+
+	if (name == NULL || name[0] != '$')
+		return NULL;
+
+	len = strlen(name);
+	if (name[1] == '{' && name[len-1] == '}') {
+		char *varname = strndupa(&name[2], len-3);
+
+		for (size_t i=0; i<sizeof(var_expansions); i++) {
+			if (!strcasecmp(varname, var_expansions[i].name)) {
+				return var_expansions[i].func();
+			}
+		}
+		xlog_warn("get_conf: Unknown variable ${%s}", varname);
+	} else {
+		/* expand $name from [environment] section,
+		* or from environment
+		*/
+		char *env = getenv(&name[1]);
+		if (env == NULL || *env == 0)
+			env = conf_get_section("environment", NULL, &name[1]);
+		return env;
+	}
+	return NULL;
+}
+
 /*
  * free all the component parts of a conf_binding struct
  */
@@ -143,6 +393,8 @@  static void free_confbind(struct conf_binding *cb)
 		free(cb->tag);
 	if (cb->value)
 		free(cb->value);
+	if (cb->cache)
+		free(cb->cache);
 	free(cb);
 }
 
@@ -782,7 +1034,7 @@  char *
 conf_get_section(const char *section, const char *arg, const char *tag)
 {
 	struct conf_binding *cb;
-retry:
+
 	cb = LIST_FIRST (&conf_bindings[conf_hash (section)]);
 	for (; cb; cb = LIST_NEXT (cb, link)) {
 		if (strcasecmp(section, cb->section) != 0)
@@ -794,19 +1046,13 @@  retry:
 		if (strcasecmp(tag, cb->tag) != 0)
 			continue;
 		if (cb->value[0] == '$') {
-			/* expand $name from [environment] section,
-			 * or from environment
-			 */
-			char *env = getenv(cb->value+1);
-			if (env && *env)
-				return env;
-			section = "environment";
-			tag = cb->value + 1;
-			goto retry;
+			if (!cb->cache)
+				cb->cache = expand_variable(cb->value);
+			return cb->cache;
 		}
 		return cb->value;
 	}
-	return 0;
+	return NULL;
 }
 
 /*