Dear fellow system tinkerers,
an admin friend has asked me the following question:
On a number of network elements and servers, he's using remote authentication against a TACACS+ server. On some such proprietary appliances, the authentication only asks a local auth DB if the remote method returns "auth server unavailable" (unreachable, timed out). If the remote auth server is available, and returns a negative response, the box in question takes that at face value and does not try to auth against a local user database.
Now... on a Linux-based box, he would like to achieve the same behavior. And he cannot seem to be able to. The Linux OS first tries the remote auth, but upon a clear negative response (server responds "authentication failure, those are not valid credentials") it goes ahead and just tries the local database too.
I've come to understand that the remote TACACS+ auth is arranged by a nice PAM module called pam_tacplus. Reading about PAM in general, it seems to dawn on me that pam_tacplus is not to blame. Rather, the observed behavior is just the way PAM as a whole works. If that's the case, a direct solution would probably have to involve adding a configurable global option to the PAM codebase, and maybe specific PAM config-file keywords/syntax, to modify the behavior in the desired direction.
Any further notes on this topic are welcome :-)
...based on further reading, let me suggest an answer. Any additions or corrections are obviously and warmly welcome.
First of all I've found this ages old intro into PAM. Probably hasn't lost any of its sharp wit during the years.
If I take a peek under the hood, the auth module return values are documented in official linux-pam documentation.
In the list, I can see a promising retval called PAM_AUTHINFO_UNAVAIL .
Actually defined in libpam/include/security/_pam_types.h .
There appears to be a conflicting definition of PAM_AUTHINFO_UNAVAIL in
libpam/include/security/_pam_compat.h
-- probably safe to ignore.
The source file libpam/pam_dispatch.c contains a key function called _pam_dispatch_aux() that actually walks the stack of registered authentication modules, and acts upon their return values. And, it turns out that it does not do much based on the "retval" directly, possibly containing
PAM_AUTHINFO_UNAVAIL
. It does respond directly to some particular "inside special use" values ofretval
, but has no special handling forPAM_AUTHINFO_UNAVAIL
.retval
is not a return value of_pam_dispatch_aux()
itself: rather,retval
is just a local variable declared inside the for(;;) loop that walks the stack of registered auth modules, where the block-localretval
collects the particular module's return value. The stackwalking loop actually makes important decisions based upon other variables: one of them derived, calledaction
, and the other one is a struct member calledimpression
"returned" by the modules by reference. So perhaps it might be influenced by the module's own code after all?Actually it turns out that
action
is taken from a "lookup table of actions", indexed by theretval
- ahaa!Is the table of actions defined by the module? But how, if the _PAM_ACTION_* macros are a #defined in libpam/pam_private.h ?
Turns out that the actions[] table is initialized in a generic way by a function called
_pam_parse_conf_file()
inlibpam/pam_handlers.c
, that does a direct string comparison (matching) on keywords such asrequired
,requisite
,optional
,sufficient
.And, the whole table currently has 32 positions (see the macro definition quoted above) that get wholesale initialized to _PAM_ACTION_UNDEF before parsing the config entry for the particular module. Individual "strength keywords" then remap individual
retval
s to particular desired actions.So: it seems fairly obvious to e.g. take inspiration in this:
and add something like
And, apparently pam_tacplus helps us on that path.
It would certainly be nice if the same effect could be achieved by existing PAM config file syntax, without having to hack the source code.