-   Linux - Software (
-   -   Linux authentication with LDAP - select user's shell based on group membership (

slinx 07-28-2011 05:35 PM

Linux authentication with LDAP - select user's shell based on group membership
Hello, I'm supposed to set up LDAP authentication on over 200 linux servers, and I'd like to select the shell assigned to the user based on their group membership. For example, someone in the "Help Desk" group should get the /usr/local/bin/menu shell, while someone in the "Shop Systems" group should get the normal /bin/bash shell, and someone not in either group would have no login.

I could probably write a custom shell to do this, but I was wondering if there were any way to specify this using winbind?


Tinkster 07-28-2011 07:28 PM

I'm not aware of any schema extensions that tie loginshells to
netgroups; I'd write a perl-script and set the shell for the
individuals based on their group memberships. May have interesting
results if someone is part of several groups - you'd have to
make sure you process the groups in whatever order you see fit.


Nominal Animal 07-29-2011 01:54 AM

I've used the opposite approach to Tinkster on some servers: use Bash as the shell for all users, and modify /etc/bashrc to exec another shell if the user belongs to a specific group.

Because the exec'd program will replace the shell, there will be no extra processes lying around. It is a very clean and reliable solution. You can even allow SCP/SFTP connections at the same time, if you only do the exec for interactive/login shells. (Bash will only set PS1 if it is interactive. It will not be set if the user is using e.g. SCP or SFTP.)

You will need to edit /etc/shells, removing (or at least commenting out) all other shells. Otherwise users can just use the chsh command to change to some other shell. (Or, you can make sure your users cannot change the shell attribute in your centralized user database. It all depends on how you're set up.)

Since the common shell commands provide the groups the user belongs in as a string with spaces as separators, having spaces in the group names does cause problems. If you have groups named "Help Desk", "General Help", and "Desk Fixers", you cannot reliably check for "Help Desk" in the space separated list using the normal tools. For example, the list may contain "General Help Desk Fixers" but no "Help Desk"; this is not easy to resolve correctly.

To avoid all that mess, I wrote a little helper in C you could use:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>

/* Maximum number of group memberships. */
#define  MAX_GIDS  1024

gid_t    group[MAX_GIDS];
int      groups = 0;

int ismember(const char *const name)
        struct group    *gs;

        if (!name || !*name)
                return 0;

        gs = getgrnam(name);
        if (gs) {
                const gid_t        g = gs->gr_gid;
                int                i = groups;

                while (i-->0)
                        if (group[i] == g)
                                return 1;

        return 0;

int main(int argc, char *argv[])
        int        i;

        if (argc < 2 ||
            (argv[1] && argv[1][0] == '-' && argv[1][1] == 'h' && !argv[1][2]) ||
            (argv[1] && argv[1][0] == '-' && argv[1][1] == '-' &&
                        argv[1][2] == 'h' && argv[1][3] == 'e' &&
                        argv[1][4] == 'l' && argv[1][5] == 'p' && !argv[1][6])) {
                fprintf(stderr, "\nUsage: %s [ -h | --help ]\n      %s groupname(s)...\n", argv[0], argv[0]);
                fprintf(stderr, "\nThis program will return success (exit status 0) if and only if\n"
                                "the current user is a member of all named groups.\n\n");
                return 3;

        /* Effective group ID is always checked. */
        group[0] = getegid();

        /* Supplemental group memberships. */
        i = getgroups(MAX_GIDS - 1, (gid_t *)&(group[1]));
        if (i == -1)
                return 2;
        groups = 1 + i;

        /* Check group names specified on the command line. */
        for (i = 1; i < argc; i++)
                if (!ismember(argv[i]))
                        return 1;

        /* We are a member of all specified groups. */
        return 0;

If you save it as e.g. memberof.c, you can compile and install it as /usr/local/bin/memberof via

gcc -Wall -O3 -o memberof memberof.c
sudo install -m 0755 memberof /usr/local/bin/memberof

It is a very simple program you can use to check if the current user is a member of a group (or all specified groups). If I were you, I'd install it on all machines, then add

/usr/local/bin/memberof "Help Desk" && exec /usr/local/bin/menu
at the beginning of /etc/bashrc or /etc/bash.bashrc, depending on which one your distribution uses. One will always exist, since all Linux distributions install Bash; only the file name varies a bit.

If you do not want to use my program, you can rely on id (utility, /usr/bin/id), but do recall the issues with group names containing spaces. Based on the group name:

groups=" $(id -Gn) "
[ "${groups}" != "${groups/ Help Desk /}" ] && exec /usr/local/bin/menu
unset groups

Same but this time using the numeric group ID -- this avoids the issues with spaces, but maintenance is of course tedious:

groups=" $(id -G) "
# Group 2345 = Help Desk:  /usr/local/bin/menu
[ "${groups}" != "${groups/ 2345 /}" ] && exec /usr/local/bin/menu
unset groups

The above two generate a string with spaces around each group name or number, including the first and last ones. Then, Bash string manipulation is used to remove that one group from the list. If the list changes, then user is a member of that group, and the other program replaces the Bash shell. (My program first converts each group name to group ID number, then checks if the current user group list contains that ID. My program has no issues with any characters used in group names.)

Note that if a user is a member of multiple such groups, the shell they get to depends on the order of the checks you implement: first match wins. Personally, I'd start with the least privileged, so that accidentally adding group memberships will not bump a user to a different shell. (You need to remove the "lower" membership too, to bump up the user.)

Finally, /etc/shells and /etc/bashrc or /etc/bash.bashrc are of course local to each server you use. This allows you to specify different behaviours on different servers. Obviously you also need to keep the configuration in sync, if you want to have the same behaviour on different servers. Those files are fortunately "constant", same across all servers using the same distribution.

I hope you find this useful,

slinx 08-02-2011 04:28 PM

Thanks Nominal! I will try out your program. That might do the trick.

sundialsvcs 08-02-2011 07:50 PM

/me nods... Yes, it seems to me that a little "trampoline" program ought to do the trick. This program would be launched, perhaps as a profile script at login. It would look for another shell program, and, if it finds one, switches to it. Perhaps, if it does not, the trampoline can drop into some kind of a default. As long as it is both trouble-free and seamless, you ought to be in good shape.

All times are GMT -5. The time now is 06:17 PM.