summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE.txt21
-rw-r--r--Makefile50
-rw-r--r--README.md133
-rwxr-xr-xactivate-localuser.sh44
-rwxr-xr-xdetect-nssdir.sh5
-rw-r--r--exports13
-rw-r--r--localuser.c301
-rw-r--r--test-localuser.c57
9 files changed, 626 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ec3855a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+test-localuser
+libnss_localuser.so.2
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..cd52ccc
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+nss-localuser: provides localuser hostname throuh NSS
+
+Copyright 2018 IoT.bzh <jose.bollo@iot.bzh>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3045a7a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+#
+# Copyright 2018 IoT.bzh <jose.bollo@iot.bzh>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+.PHONY: all clean install deinstall
+
+auto-nssdir := $(shell ./detect-nssdir.sh)
+
+tst = test-localuser
+lib = libnss_localuser.so.2
+nssdir = $(auto-nssdir)
+nsslib = $(nssdir)/$(lib)
+
+all: $(lib) $(tst)
+
+clean:
+ test -f $(lib) && rm $(lib) || true
+ test -f $(tst) && rm $(tst) || true
+
+install: $(nsslib)
+
+deinstall:
+ test -f $(nsslib) && rm $(nsslib) || true
+
+$(lib): localuser.c
+ $(CC) $(CFLAGS) $< --PIC --pic --shared -Wl,--version-script=exports -o $@
+
+$(nsslib): $(lib)
+ install -d $(nssdir)
+ install $(lib) $(nsslib)
+
+$(tst): test-localuser.c
+ $(CC) $(CFLAGS) $< -o $@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..046c275
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+# nss-localuser
+
+- [License](#license)
+- [Overview](#overview)
+- [Install](#documentation)
+
+## License
+
+This code is published with the license MIT (see LICENSE.txt) for details.
+
+## Overview
+
+`nss-localuser` is a plugin for the GNU Name Service Switch (NSS)
+functionality of the GNU C Library (`glibc`) providing host name
+resolution for *"localuser"* family of virtual hostnames.
+
+The delivered NSS service defines one virtual host of name `localuser`
+that resolves to an IP address of the localhost loopback that integrates
+user ID.
+
+It is intended to enable distinct IP for distinct users.
+
+The name "localuser" is resolved to the IPv4 address:
+
+```
+127.x.y.z
+```
+
+where x.y.z encode the current user UID in such way that:
+
+```
+UID = 65536*(x - 128) + 256*y + z
+```
+
+Allowed UID are from 0 to 4194303 included.
+
+And so:
+
+```
+z = [0..255]
+y = [0..255]
+x = [128..191]
+```
+
+The names "localuser-${UID}", where UID is a decimal number, are resolved to addresses:
+
+```
+127.x.y.z
+
+z = UID % 255
+y = (UID >> 8) % 256
+x = ((UID >> 16) % 256) + 128
+```
+
+Examples:
+
+```
+localuser => 127.128.0.0 (when user has UID = 0)
+localuser => 127.128.3.233 (when user has UID = 1001)
+localuser-1024 => 127.128.4.0 (for any user)
+```
+
+The service also provides the reverse resolution.
+
+This module provides a value for IPv6: it translates to a IPv4-mapped IPv6 address
+because IPv6 lacks of loopback range.
+
+Example:
+
+```
+localuser-1024 => ::ffff:127.128.4.0
+```
+
+For details about NSS integration, see
+[Gnu libc documentation](https://www.gnu.org/software/libc/manual/html_node/Name-Service-Switch.html).
+
+## Install
+
+To install this file:
+
+```
+make all && sudo install
+```
+
+The installation directory is automatically detected by the tiny
+script detect-nssdir.sh.
+
+If the script detect-nssdir.sh gives the wrong result, just define the
+variable nssdir when calling make, as below:
+
+```
+make install nssdir=~/lib
+```
+
+## Configuration and activation
+
+### Manual setting
+
+To activate the NSS module localuser you have to edit
+`/etc/nsswitch.conf` and add `localuser` to the
+line starting with "`hosts:`".
+
+Your nsswitch file can then look as below:
+
+<pre># /etc/nsswitch.conf
+
+passwd: files sss systemd
+shadow: files sss
+group: files sss systemd
+
+hosts: <b>localuser</b> files dns myhostname
+
+ethers: files
+netmasks: files
+networks: files
+protocols: files
+services: files sss
+
+aliases: files nisplus</pre>
+
+### Scripted setting
+
+The script activate-localuser.sh can be used to activate,
+deactivate or query the status of localuser activation.
+
+It accepts 0, 1 or 2 arguments.
+
+<pre>usage: activate-localuser.sh [command [file]]
+
+command: on, yes, true, 1: activate
+ off, no, false, 0: deactivate
+ status, test, check, query: status (default)
+file: file to change (default /etc/nsswitch.conf)</pre>
diff --git a/activate-localuser.sh b/activate-localuser.sh
new file mode 100755
index 0000000..8f40116
--- /dev/null
+++ b/activate-localuser.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+action=$(echo ${1:-status} | tr '[a-z]' '[A-Z]')
+file=${2:-/etc/nsswitch.conf}
+
+# compute activated value
+activated=false
+if grep -q '^hosts:.*\<localuser\>' "${file}" 2>/dev/null; then
+ activated=true
+fi
+
+# compute activate value
+case ${action} in
+ ON|YES|TRUE|1) activate=true;;
+ OFF|NO|FALSE|0) activate=false;;
+ QUERY|TEST|CHECK|STATUS) ${activated} && echo ON || echo OFF; exit 0;;
+ *) echo "Bad command: $1" >&2; exit 1;;
+esac
+
+# exit if already set as desired
+[ "${activate}" = "${activated}" ] && exit 0
+
+# process
+if ${activate}; then
+ sedcmd='/^hosts:/s/hosts:[ \t]*/&localuser /'
+else
+ sedcmd='/^hosts:/s/localuser *//'
+fi
+if ! cp "${file}" "${file}~"; then
+ echo "Can't save file ${file} to ${file}~" >&2
+ exit 1
+fi
+if ! grep -q '^hosts:' "${file}"; then
+ if ! echo >> "${file}" || ! echo "hosts: " >> "${file}"; then
+ echo "Can't add host: to ${file}" >&2
+ cp "${file}~" "${file}" && rm "${file}~"
+ exit 1
+ fi
+fi
+if ! sed -i "${sedcmd}" "${file}"; then
+ echo "Can't process ${file}" >&2
+ cp "${file}~" "${file}" && rm "${file}~"
+ exit 1
+fi
diff --git a/detect-nssdir.sh b/detect-nssdir.sh
new file mode 100755
index 0000000..858c958
--- /dev/null
+++ b/detect-nssdir.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+x=$(ldd $SHELL|awk '$1~/^libc\./{print $3}')
+echo ---------------$x----------------- >&2
+[ -f "$x" ] && dirname "$x" || echo /usr/lib
diff --git a/exports b/exports
new file mode 100644
index 0000000..90d8bf9
--- /dev/null
+++ b/exports
@@ -0,0 +1,13 @@
+NSSLOCALUSER_0 {
+
+global:
+
+ _nss_localuser_gethostbyaddr_r;
+ _nss_localuser_gethostbyname_r;
+ _nss_localuser_gethostbyname2_r;
+
+local:
+
+ *;
+
+};
diff --git a/localuser.c b/localuser.c
new file mode 100644
index 0000000..f8265e9
--- /dev/null
+++ b/localuser.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2018 IoT.bzh <jose.bollo@iot.bzh>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/*
+ * localuser.c
+ * -----------
+ * This source file provides NSS (Name Service Switch -see [1]-) facilities
+ * for defining a virtual host of name localuser that resolves to an address
+ * of the localhost that integrate user ID.
+ *
+ * The name "localuser" is resolved to the IPv4 address 127.x.y.z
+ * where x.y.z resolves to the current user UID = 65536*(x - 128) + 256*y + z
+ *
+ * The name "localuser-UID" is resolved to the address 127.x.y.z
+ * where UID = 65536*(x - 128) + 256*y + z
+ *
+ * Allowed UID are from 0 to 4194303 included.
+ *
+ * Examples:
+ * localuser => 127.128.0.0 (when UID = 0)
+ * localuser => 127.128.3.233 (when UID = 1001)
+ * localuser-1024 => 127.128.4.0 (always)
+ *
+ * This module provides the reverse resolution.
+ *
+ * This module provides a value for IPv6: it translate to a IPv4-mapped IPv6 address
+ * because IPv6 lakes of loopback range.
+ *
+ * Example: localuser-1024 => ::ffff:127.128.4.0
+ *
+ * links
+ * -----
+ * [1] https://www.gnu.org/software/libc/manual/html_node/Name-Service-Switch.html
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <nss.h>
+
+/* string for "localuser" */
+static const char localuser[] = "localuser";
+static const char separator = '-';
+
+/* defines the length of adresses */
+static const int lenip4 = 4;
+static const int lenip6 = 16;
+
+/* masks for IPv4 adresses */
+static const uint32_t prefix_mask = 0xffc00000u; /* 255.192.0.0 */
+static const uint32_t prefix_value = 0x7f800000u; /* 127.128.0.0 */
+static const uint32_t locusr_mask = 0x003fffffu; /* 0.63.255.255 */
+
+/* return the IPv4 localuser address for 'uid' */
+static uint32_t get_localuser(uint32_t uid)
+{
+ uint32_t adr = (uint32_t)(prefix_value | (locusr_mask & uid));
+ return htonl(adr);
+}
+
+/* is 'ip' a localuser IPv4 address ? */
+static int is_localuser(uint32_t ip)
+{
+ return prefix_value == (ntohl(ip) & prefix_mask);
+}
+
+/* return the user of the localuser IPv4 'ip' */
+static uint32_t uid_of_localuser(uint32_t ip)
+{
+ return (ntohl(ip) & locusr_mask);
+}
+
+/* put in 'buffer' the IPv4 localuser address for 'uid' */
+static void getIPv4(uint32_t *buffer, uint32_t uid)
+{
+ buffer[0] = get_localuser(uid);
+}
+
+/* is 'buffer' pointing a localuser IPv4 address ? */
+static int isIPv4(const uint32_t *buffer)
+{
+ return is_localuser(buffer[0]);
+}
+
+/* return the user of the localuser IPv4 pointed by 'buffer' */
+static uint32_t uidIPv4(const uint32_t *buffer)
+{
+ return uid_of_localuser(buffer[0]);
+}
+
+/* put in 'buffer' the IPv6 localuser address for 'uid' */
+static void getIPv6(uint32_t *buffer, uint32_t uid)
+{
+ buffer[0] = 0;
+ buffer[1] = 0;
+ buffer[2] = htonl(0xffff);
+ buffer[3] = get_localuser(uid);
+}
+
+/* is 'buffer' pointing a localuser IPv6 address ? */
+static int isIPv6(const uint32_t *buffer)
+{
+ return buffer[0] == 0 && buffer[1] == 0
+ && buffer[2] == htonl(0xffff) && is_localuser(buffer[3]);
+}
+
+/* return the user of the localuser IPv6 pointed by 'buffer' */
+static uint32_t uidIPv6(const uint32_t *buffer)
+{
+ return uid_of_localuser(buffer[3]);
+}
+
+/* fill the output entry */
+static enum nss_status fillent(
+ const char *name,
+ int af,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop,
+ uint32_t uid)
+{
+ int alen = 1 + (int)strlen(name);
+ int len = af == AF_INET ? lenip4 : lenip6;
+
+ /* check the family */
+ if (af != AF_INET && af != AF_INET6) {
+ *errnop = EINVAL;
+ *h_errnop = NO_RECOVERY;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* fill aliases and addr_list */
+ if (buflen < 2 * sizeof result->h_aliases[0] + alen + len) {
+ *errnop = ERANGE;
+ *h_errnop = NO_RECOVERY;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* fill the result */
+ result->h_addrtype = af;
+ result->h_length = len;
+ result->h_addr_list = (char**)buffer;
+ result->h_addr_list[0] = (char*)&result->h_addr_list[2];
+ result->h_name = &result->h_addr_list[0][len];
+ result->h_aliases = &result->h_addr_list[1];
+ result->h_addr_list[1] = NULL;
+ (af == AF_INET ? getIPv4 : getIPv6)((uint32_t*)result->h_addr_list[0], uid);
+ memcpy(result->h_name, name, alen);
+
+ return NSS_STATUS_SUCCESS;
+}
+
+/* gethostbyname2 implementation for NSS */
+enum nss_status _nss_localuser_gethostbyname2_r(
+ const char *name,
+ int af,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop)
+{
+ int valid;
+ uint32_t uid;
+ const char *i;
+ char c;
+
+ /* test the name */
+ valid = !strncmp(name, localuser, sizeof localuser - 1);
+ if (valid) {
+ c = name[sizeof localuser - 1];
+ if (!c) {
+ /* terminated string: use current UID */
+ uid = (uint32_t)getuid();
+ } else if (c != separator) {
+ valid = 0;
+ } else {
+ /* has a uid specification */
+ i = &name[sizeof localuser];
+ c = *i;
+ valid = '0' <= c && c <= '9';
+ if (valid) {
+ uid = (uint32_t)(c - '0');
+ while ((c = *++i) && (valid = '0' <= c && c <= '9')) {
+ uid = (uid << 3) + (uid << 1) + (uint32_t)(c - '0');
+ }
+ if (valid)
+ valid = uid == (uid & locusr_mask);
+ }
+ }
+ }
+ if (!valid) {
+ *h_errnop = HOST_NOT_FOUND;
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* set default family to IPv4 */
+ if (af == AF_UNSPEC)
+ af = AF_INET;
+
+ /* fill the result */
+ return fillent(name, af, result, buffer, buflen, errnop, h_errnop, uid);
+}
+
+/* use gethostbyname2 implementation */
+enum nss_status _nss_localuser_gethostbyname_r(
+ const char *name,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop)
+{
+ return _nss_localuser_gethostbyname2_r(name,
+ AF_UNSPEC,
+ result,
+ buffer, buflen, errnop,
+ h_errnop);
+}
+
+/* get the name of the address */
+enum nss_status _nss_localuser_gethostbyaddr_r(
+ const void *addr,
+ int len,
+ int af,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop)
+{
+ char c, name[40 + sizeof localuser];
+ uint32_t uid, x;
+ int l, u;
+
+ /* set default family */
+ if (af == AF_UNSPEC) {
+ if (len == lenip4)
+ af = AF_INET;
+ else if (len == lenip6)
+ af = AF_INET6;
+ }
+
+ /* check whether the IP comforms to localuser */
+ if (af == AF_INET && len == lenip4 && isIPv4((const uint32_t*)addr)) {
+ /* yes, it's a IPv4, get the uid */
+ uid = uidIPv4((const uint32_t*)addr);
+ } else if (af == AF_INET6 && len == lenip6 && isIPv6((const uint32_t*)addr)) {
+ /* yes, it's a IPv6, get the uid */
+ uid = uidIPv6((const uint32_t*)addr);
+ } else {
+ /* no */
+ /* fail */
+ *errnop = EINVAL;
+ *h_errnop = NO_RECOVERY;
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* build the name */
+ memcpy(name, localuser, sizeof localuser - 1);
+ if (uid == (uint32_t)getuid())
+ name[sizeof localuser - 1] = 0;
+ else {
+ x = uid;
+ name[sizeof localuser - 1] = separator;
+ l = u = (int)sizeof localuser;
+ do {
+ name[u++] = (char)('0' + x % 10);
+ x /= 10;
+ } while(x);
+ name[u--] = 0;
+ while (u > l) {
+ c = name[u];
+ name[u--] = name[l];
+ name[l++] = c;
+ }
+ }
+ /* fill the result */
+ return fillent(name, af, result, buffer, buflen, errnop, h_errnop, uid);
+}
diff --git a/test-localuser.c b/test-localuser.c
new file mode 100644
index 0000000..ed27138
--- /dev/null
+++ b/test-localuser.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018 IoT.bzh <jose.bollo@iot.bzh>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdio.h>
+#include <netdb.h>
+
+void dumphostent(char *tag, char *arg, struct hostent *h)
+{
+ printf("\n----------------- %s %s\n", tag, arg);
+ if (h) {
+ printf("name: %s\n", h->h_name);
+ if (h->h_addrtype == AF_INET)
+ printf("ipv4: %d.%d.%d.%d\n",
+ (int)(unsigned char)h->h_addr_list[0][0],
+ (int)(unsigned char)h->h_addr_list[0][1],
+ (int)(unsigned char)h->h_addr_list[0][2],
+ (int)(unsigned char)h->h_addr_list[0][3]);
+ } else {
+ printf("NULL!\n");
+ }
+ printf("\n");
+}
+
+int main(int ac, char **av)
+{
+ struct hostent *h;
+
+ while (*++av) {
+ h = gethostbyname2(*av, AF_INET);
+ dumphostent("name->addr", *av, h);
+
+ if (h) {
+ h = gethostbyaddr(h->h_addr_list[0], h->h_length, h->h_addrtype);
+ dumphostent("addr->name", *av, h);
+ }
+ }
+ return 0;
+}
+