From d2d9e83bd9bdc3a091d6b9f1ccaec012fba2f0f4 Mon Sep 17 00:00:00 2001 From: José Bollo Date: Fri, 19 Jan 2018 14:58:27 +0100 Subject: Initial commit of nss-localuser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. Change-Id: I2a33dc23518ce3f4581f5c37df9476dbbc58f262 Signed-off-by: José Bollo --- .gitignore | 2 + LICENSE.txt | 21 ++++ Makefile | 50 +++++++++ README.md | 133 ++++++++++++++++++++++ activate-localuser.sh | 44 ++++++++ detect-nssdir.sh | 5 + exports | 13 +++ localuser.c | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++ test-localuser.c | 57 ++++++++++ 9 files changed, 626 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100755 activate-localuser.sh create mode 100755 detect-nssdir.sh create mode 100644 exports create mode 100644 localuser.c create mode 100644 test-localuser.c 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 + +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 +# +# 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: + +
# /etc/nsswitch.conf
+
+passwd:     files sss systemd
+shadow:     files sss
+group:      files sss systemd
+
+hosts:      localuser files dns myhostname
+
+ethers:     files
+netmasks:   files
+networks:   files
+protocols:  files
+services:   files sss
+
+aliases:    files nisplus
+ +### 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. + +
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)
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:.*\' "${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 + * + * 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 +#include +#include +#include +#include +#include + +/* 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 + * + * 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 +#include + +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; +} + -- cgit 1.2.3-korg