0

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 :-)

2
  • You mean like sufficient? Dec 4, 2020 at 20:44
  • Does sufficient cut both ways?
    – frr
    Dec 5, 2020 at 5:01

1 Answer 1

0

...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 .

#define PAM_SUCCESS 0
#define PAM_AUTHINFO_UNAVAIL 9
#define _PAM_RETURN_VALUES 32 

There appears to be a conflicting definition of PAM_AUTHINFO_UNAVAIL in libpam/include/security/_pam_compat.h

# define PAM_AUTHINFO_UNAVAIL     12

-- 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 of retval, but has no special handling for PAM_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-local retval collects the particular module's return value. The stackwalking loop actually makes important decisions based upon other variables: one of them derived, called action, and the other one is a struct member called impression "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 the retval - ahaa!

action = h->actions[cached_retval];

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() in libpam/pam_handlers.c, that does a direct string comparison (matching) on keywords such as required,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 retvals to particular desired actions.

So: it seems fairly obvious to e.g. take inspiration in this:

} else if (!strcasecmp("required", tok)) {
    D(("*PAM_F_REQUIRED*"));
    actions[PAM_SUCCESS] = _PAM_ACTION_OK;
    actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_OK;
    actions[PAM_IGNORE] = _PAM_ACTION_IGNORE;
    _pam_set_default_control(actions, _PAM_ACTION_BAD);
} else if (!strcasecmp("sufficient", tok)) {
    D(("*PAM_F_SUFFICIENT*"));
    actions[PAM_SUCCESS] = _PAM_ACTION_DONE;
    actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_DONE;
    _pam_set_default_control(actions, _PAM_ACTION_IGNORE);
}

and add something like

} else if (!strcasecmp("reqd_if_avail", tok)) {
    D(("*PAM_F_REQD_IF_AVAIL*"));
    actions[PAM_SUCCESS] = _PAM_ACTION_OK;
    actions[PAM_AUTHINFO_UNAVAIL] = _PAM_ACTION_IGNORE; // !!!!!!!!!!
    actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_OK;     // ?
    actions[PAM_IGNORE] = _PAM_ACTION_IGNORE;
    _pam_set_default_control(actions, _PAM_ACTION_BAD); // !!!
}

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.