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:
Code:
#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
Code:
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
Code:
/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:
Code:
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:
Code:
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,