diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | doc/nsssd-switch.html | 135 | ||||
-rw-r--r-- | src/nsssd/nsssd-switch.c | 83 | ||||
-rwxr-xr-x[l---------] | src/tests/test-nsssd-switch.baseline | 8 | ||||
-rw-r--r--[l---------] | src/tests/test-nsssd-switch.c | 84 | ||||
-rwxr-xr-x | src/tests/test-nsssd-switch.wrapper | 6 | ||||
-rwxr-xr-x | src/tests/test-switch.wrapper | 6 |
7 files changed, 294 insertions, 29 deletions
@@ -10,4 +10,3 @@ /nsssd-nslcd /nsssd-switch /test-* -/.test-* diff --git a/doc/nsssd-switch.html b/doc/nsssd-switch.html index 9f5672c..bd6f63b 100644 --- a/doc/nsssd-switch.html +++ b/doc/nsssd-switch.html @@ -36,7 +36,7 @@ configuration on the command line. <h2> Interface </h2> <pre> - s6-ipcserver -l0 /run/service/nsssd/s nsssd-switch <em>bitfield1</em> <em>backend1...</em> "" <em>bitfield2</em> <em>backend2...</em> "" ... + s6-ipcserver -l0 /run/service/nsssd/s nsssd-switch [ -t <em>timeout</em> ] <em>bitfield1</em> <em>backend1...</em> "" <em>bitfield2</em> <em>backend2...</em> "" ... </pre> <p> @@ -52,35 +52,132 @@ configuration on the command line. </pre> <ul> - <li> <tt>nsssd-switch</tt> + <li> <tt>nsssd-switch</tt> is normally spawned by a super-server like +<a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a>. +There is one instance of <tt>nsssd-switch</tt> per client connection. </li> + <li> <tt>nsssd-switch</tt> interprets its command line as a script that +configures all its backends. The script is a series of directives: + <ol> + <li> First a <em>bitfield</em> is read: it's a number between 0 and 7. +This number determines how the backend will behave in case of failure. </li> + <li> Then a <em>backend</em> is read: it is a full command-line, terminated +by an empty word. (In an <a href="//skarnet.org/software/execline/execlineb.html>execline</a> +script, the <em>backend</em> is a block, without the empty word.) The command +line is an implementation of the server side of the nsss protocol: for instance, +<tt>nsssd-unix ""</tt> declares a Unix backend with user, group and shadow +credentials in <tt>/etc/passwd</tt>, <tt>/etc/group</tt> and <tt>/etc/shadow</tt>. +</li> + </ol> </li> + <li> <tt>nsssd-switch</tt> spawns all its declared backends. </li> + <li> It then reads queries from its client. It transmits every query to +its backends, in the order given on the command line. A success means +that the answer is immediately returned to the client, and no further +backend is contacted. A failure can be handled in different ways, depending +on the type of failure and on the <em>bitfield</em> associated to the +<em>backend</em>. </li> + <li> <tt>nsssd-switch</tt> (and all the backends it spawned) exits when +the client connection is closed, or in some cases, after a timeout. </li> </ul> +<h2> Exit codes </h2> -<h2> Notes </h2> +<p> + These exit codes are not important because only the super-server can see them. +</p> + +<ul> + <li> 0: normal exit or timeout while waiting for a client query </li> + <li> 100: wrong usage </li> + <li> 111: system call failed or timeout during a nsss protocol exchange </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-t <em>timeout</em></tt> : enforce a limit of +<em>timeout</em> milliseconds when communicating with a backend. If a +backend fails to answer a query under <em>timeout</em> milliseconds, +<tt>nsssd-switch</tt> will return a failure code to the client, and +the backend will be considered permanently failed. The default is 0, +meaning no such timeout - backends can take as much time as they want +to answer queries. </li> +</ul> + +<h2> Environment variables </h2> <p> - nsssd-switch is not meant to be called directly; instead, it is expected to be run from -a script as a part of a "nsssd" -<a href="//skarnet.org/software/s6/localservice.html">local service</a>. + <tt>nsssd-switch</tt> can read a number <em>x</em> in the NSSSD_TIMEOUT +environment variable. If this variable is present and valid, it means that +<tt>nsssd-switch</tt> will die if <em>x</em> milliseconds elapse without +the client reading or writing during a nsss protocol exchange, which usually +means the client either is not speaking the protocol correctly or has become +unresponsive. It is a safety measure to avoid having <tt>nsssd</tt> processes +sticking around forever when a client is buggy. +</p> + +<p> + Note that the NSSSD_TIMEOUT variable refers to a timeout during an exchange +with the <em>client</em>, while the argument to the <tt>-t</tt> option refers +to a timeout enforced on the <em>backends</em>. </p> +<h2> Bitfields </h2> + <p> + A <em>bitfield</em> is a value between 0 and 7, representing 3 bits. If a +bit is 0, it means that the query resolution will <em>continue to the next +backend</em> if the corresponding failure condition is triggered. If the +bit is 1, it means that the failure will instantly be reported to the client +and the query will not be transmitted to the next backend in the chain. +</p> + +<ul> + <li> Bit 0: the backend is in a state of permanent failure: it failed to start, +it crashed, or it timed out. </li> + <li> Bit 1: the backend answered the query with a failure. </li> + <li> Bit 2: the requested entry was not found in the backend's database. </li> +</ul> + +<p> + So, for instance, a bitfield of 5 means: report failure to the client if the +current backend is in a failed state or if a requested entry cannot be found. +Proceed to the next backend if the current backend reports failure when +processing a query. +</p> + +<p> + This format allows the administrator to configure various fallback strategies. +Note that in case of success, the requested data is immediately returned to the +client. <tt>nsssd-switch</tt> does not provide the equivalent of the <tt>merge</tt> +directive in <tt>/etc/nsswitch.conf</tt>. +</p> + +<h2> Notes </h2> + +<ul> +<li> + <tt>nsssd-switch</tt> is not meant to be called directly; instead, it is expected to be run from +a script as a part of a "nsssd" +<a href="//skarnet.org/software/s6/localservice.html">local service</a>. +</li> + +<li> The <tt>examples/</tt> subdirectory of the nsss package provides examples on how to run such a service. The simplest way to do so, for testing purposes, is a command line such as: -</p> <pre>s6-ipcserver -l0 /run/service/nsssd/s nsssd-switch 0 nsssd-unix "" </pre> +</li> -<p> +<li> <tt>/run/service/nsssd/s</tt> is the default place where nsss's implementation of the <tt>pwd.h</tt>, <tt>grp.h</tt> and <tt>shadow.h</tt> functions expects the nsssd service to be. It can be changed at nsss build time by giving the <tt>--with-nsssd-socket=PATH</tt> option to configure. -</p> +</li> -<p> - nsssd-switch does not listen to the socket itself: it reads from its +<li> + <tt>nsssd-switch</tt> does not listen to the socket itself: it reads from its standard input and writes to its standard output. It relies on a superserver such as <a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a> @@ -88,18 +185,24 @@ to manage connections to the socket. An instance of nsssd-switch is run for every client connection. </p> -<p> +<li> If fine-grained authorizations are required (only allowing certain users and groups to connect to the service), the superserver can be configured to enforce them. -</p> +</li> -<p> - nsssd-switch does not need to run as root, provided it has all the +<li> + <tt>nsssd-switch</tt> does not need to run as root, provided it has all the permissions needed by the backends it spawns. It is recommended to create a <em>nsss</em> user and group, dedicated to the nsssd service, and run the superserver as this user and group. -</p> +</li> + +<li> + <tt>nsssd-switch</tt> is limited to 8 backends. If you need more, you can +chain another <tt>nsssd-switch</tt> invocation as the 8th backend, +which gives you another batch of 8. +</li> </body> </html> diff --git a/src/nsssd/nsssd-switch.c b/src/nsssd/nsssd-switch.c index 4906728..de670a7 100644 --- a/src/nsssd/nsssd-switch.c +++ b/src/nsssd/nsssd-switch.c @@ -18,7 +18,7 @@ #define USAGE "nsssd-switch bitfield1 backend1... \"\" bitfield2 backend2... \"\"" #define dieusage() strerr_dieusage(100, USAGE) -#define MAX_BACKENDS 16 +#define MAX_BACKENDS 8 static tain tto = TAIN_INFINITE_RELATIVE ; static stralloc storagesa = STRALLOC_ZERO ; @@ -148,6 +148,11 @@ int nsssd_pwd_rewind (void *handle) } tain_add_g(&deadline, &tto) ; if (nsss_switch_pwd_rewind_g(&a->tab[i].handle, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & 2) return 0 ; } return 0 ; @@ -168,6 +173,11 @@ int nsssd_pwd_get (void *handle, struct passwd *pw) storagesa.len = 0 ; errno = 0 ; if (nsss_switch_pwd_get_g(&a->tab[i].handle, pw, &storagesa, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -188,6 +198,11 @@ int nsssd_pwd_getbyuid (void *handle, struct passwd *pw, uid_t uid) storagesa.len = 0 ; errno = 0 ; if (nsss_switch_pwd_getbyuid_g(&a->tab[i].handle, pw, &storagesa, uid, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -208,6 +223,11 @@ int nsssd_pwd_getbyname (void *handle, struct passwd *pw, char const *name) storagesa.len = 0 ; errno = 0 ; if (nsss_switch_pwd_getbyname_g(&a->tab[i].handle, pw, &storagesa, name, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -225,7 +245,12 @@ void nsssd_pwd_end (void *handle) else continue ; } tain_add_g(&deadline, &tto) ; - nsss_switch_pwd_end_g(&a->tab[i].handle, &deadline) ; + if (nsss_switch_pwd_end_g(&a->tab[i].handle, &deadline)) continue ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } } } @@ -248,6 +273,11 @@ int nsssd_grp_rewind (void *handle) } tain_add_g(&deadline, &tto) ; if (nsss_switch_grp_rewind_g(&a->tab[i].handle, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & 2) return 0 ; } return 0 ; @@ -269,6 +299,11 @@ int nsssd_grp_get (void *handle, struct group *gr) genalloc_setlen(char *, &storagega, 0) ; errno = 0 ; if (nsss_switch_grp_get_g(&a->tab[i].handle, gr, &storagesa, &storagega, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -290,6 +325,11 @@ int nsssd_grp_getbygid (void *handle, struct group *gr, gid_t gid) genalloc_setlen(char *, &storagega, 0) ; errno = 0 ; if (nsss_switch_grp_getbygid_g(&a->tab[i].handle, gr, &storagesa, &storagega, gid, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -311,6 +351,11 @@ int nsssd_grp_getbyname (void *handle, struct group *gr, char const *name) genalloc_setlen(char *, &storagega, 0) ; errno = 0 ; if (nsss_switch_grp_getbyname_g(&a->tab[i].handle, gr, &storagesa, &storagega, name, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -331,6 +376,11 @@ int nsssd_grp_getlist (void *handle, char const *user, gid_t *gids, size_t n, si storagesa.len = 0 ; errno = 0 ; if (nsss_switch_grp_getlist_g(&a->tab[i].handle, user, gids, n, r, &storagesa, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -348,7 +398,12 @@ void nsssd_grp_end (void *handle) else continue ; } tain_add_g(&deadline, &tto) ; - nsss_switch_grp_end_g(&a->tab[i].handle, &deadline) ; + if (nsss_switch_grp_end_g(&a->tab[i].handle, &deadline)) continue ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } } } @@ -371,6 +426,11 @@ int nsssd_shadow_rewind (void *handle) } tain_add_g(&deadline, &tto) ; if (nsss_switch_shadow_rewind_g(&a->tab[i].handle, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & 2) return 0 ; } return 0 ; @@ -391,6 +451,11 @@ int nsssd_shadow_get (void *handle, struct spwd *sp) storagesa.len = 0 ; errno = 0 ; if (nsss_switch_shadow_get_g(&a->tab[i].handle, sp, &storagesa, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -411,6 +476,11 @@ int nsssd_shadow_getbyname (void *handle, struct spwd *sp, char const *name) storagesa.len = 0 ; errno = 0 ; if (nsss_switch_shadow_getbyname_g(&a->tab[i].handle, sp, &storagesa, name, &deadline)) return 1 ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } if (a->tab[i].flags & (errno ? 2 : 4)) return 0 ; } return 0 ; @@ -428,7 +498,12 @@ void nsssd_shadow_end (void *handle) else continue ; } tain_add_g(&deadline, &tto) ; - nsss_switch_pwd_end_g(&a->tab[i].handle, &deadline) ; + if (nsss_switch_pwd_end_g(&a->tab[i].handle, &deadline)) continue ; + if (errno == ETIMEDOUT) + { + nsss_switch_end(&a->tab[i].handle, NSSS_SWITCH_PWD | NSSS_SWITCH_GRP | NSSS_SWITCH_SHADOW) ; + a->tab[i].failed = 1 ; + } } } diff --git a/src/tests/test-nsssd-switch.baseline b/src/tests/test-nsssd-switch.baseline index 28f1c7a..31fba7e 120000..100755 --- a/src/tests/test-nsssd-switch.baseline +++ b/src/tests/test-nsssd-switch.baseline @@ -1 +1,7 @@ -test-switch.baseline
\ No newline at end of file +#!/bin/sh -e + +cat /etc/passwd +echo +id -u root +echo +cat /etc/group diff --git a/src/tests/test-nsssd-switch.c b/src/tests/test-nsssd-switch.c index 08323c4..e4289e4 120000..100644 --- a/src/tests/test-nsssd-switch.c +++ b/src/tests/test-nsssd-switch.c @@ -1 +1,83 @@ -test-switch.c
\ No newline at end of file +/* ISC license. */ + +#include <errno.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/lolstdio.h> +#include <skalibs/tai.h> +#include <nsss/pwd-def.h> +#include <nsss/grp-def.h> +#include <nsss/nsss-switch.h> + +#define S "./.test-nsssd-switch-socket" + +int main (void) +{ + nsss_switch_t a = NSSS_SWITCH_ZERO ; + stralloc sa = STRALLOC_ZERO ; + genalloc ga = GENALLOC_ZERO ; + tain deadline ; + PROG = "test-nsssd-switch" ; + tain_now_set_stopwatch_g() ; + tain_from_millisecs(&deadline, 10000) ; + tain_add_g(&deadline, &deadline) ; + + if (!nsss_switch_start_g(&a, NSSS_SWITCH_PWD, S, &deadline)) + strerr_diefu1sys(111, "nsss_switch_start") ; + + for (;;) + { + struct passwd pw ; + errno = 0 ; + if (!nsss_switch_pwd_get_g(&a, &pw, &sa, &deadline)) break ; + lolprintf("%s:%s:%d:%d:%s:%s:%s\n", pw.pw_name, pw.pw_passwd, (int)pw.pw_uid, (int)pw.pw_gid, pw.pw_gecos, pw.pw_dir, pw.pw_shell) ; + sa.len = 0 ; + } + if (errno) + strerr_diefu1sys(111, "nsss_switch_pwd_get") ; + if (!nsss_switch_pwd_end_g(&a, &deadline)) + strerr_diefu1sys(111, "nsss_switch_pwd_end") ; + lolprintf("\n") ; + + { + struct passwd pw ; + if (!nsss_switch_pwd_getbyname_g(&a, &pw, &sa, "root", &deadline)) + strerr_diefu1sys(111, "nsss_switch_pwd_getbyname") ; + lolprintf("%u\n\n", (unsigned int)pw.pw_uid) ; + sa.len = 0 ; + } + + if (!nsss_switch_start_g(&a, NSSS_SWITCH_GRP, S, &deadline)) + strerr_diefu1sys(111, "nsss_switch_start") ; + nsss_switch_end(&a, NSSS_SWITCH_PWD) ; + buffer_flush(buffer_1) ; + + for (;;) + { + struct group gr ; + char **p ; + errno = 0 ; + if (!nsss_switch_grp_get_g(&a, &gr, &sa, &ga, &deadline)) break ; + p = gr.gr_mem ; + lolprintf("%s:%s:%d:", gr.gr_name, gr.gr_passwd, (int)gr.gr_gid) ; + buffer_flush(buffer_1) ; + if (*p) + { + while (*p) lolprintf("%s,", *p++) ; + buffer_unput(buffer_1, 1) ; + } + buffer_put(buffer_1, "\n", 1) ; + sa.len = 0 ; + genalloc_setlen(char *, &ga, 0) ; + } + if (errno) + strerr_diefu1sys(111, "nsss_switch_grp_get") ; + if (!nsss_switch_grp_end_g(&a, &deadline)) + strerr_diefu1sys(111, "nsss_switch_grp_end") ; + nsss_switch_end(&a, NSSS_SWITCH_GRP) ; + + buffer_flush(buffer_1) ; + return 0 ; +} diff --git a/src/tests/test-nsssd-switch.wrapper b/src/tests/test-nsssd-switch.wrapper index 8e05937..509bf18 100755 --- a/src/tests/test-nsssd-switch.wrapper +++ b/src/tests/test-nsssd-switch.wrapper @@ -1,13 +1,13 @@ #!/bin/sh -e -S=./.test-switch-socket -F=./.test-switch-fifo +S=./.test-nsssd-switch-socket +F=./.test-nsssd-switch-fifo pid=0 cleanup () { kill $pid - rm -f $S + rm -f ${S} ${S}.lock } mkfifo $F diff --git a/src/tests/test-switch.wrapper b/src/tests/test-switch.wrapper index 5117400..387595f 100755 --- a/src/tests/test-switch.wrapper +++ b/src/tests/test-switch.wrapper @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/sh -ex S=./.test-switch-socket F=./.test-switch-fifo @@ -7,13 +7,13 @@ pid=0 cleanup () { kill $pid - rm -f $S + rm -f ${S} ${S}.lock } mkfifo $F head -n 1 < $F >/dev/null & pid=$! -s6-ipcserver -1 -- $S ./nsssd-unix > $F & +s6-ipcserver -1 -- ${S} ./nsssd-unix > $F & wait $pid pid=$! rm -f $F |