Cover V05, I09
Figure 1
Figure 2a
Figure 2b
Figure 3
Listing 1


Restricting Machine Access with the rma "shell"

Greg A. Wade

Many sites enforce restrictions as to which machines can be accessed by account holders in an NIS domain. These access policies are motivated by a wide range of concerns, some political and others technical, with the goal of protecting the CPU resources of one workgroup from utilization by others in the organization. Administrators may also wish to restrict unauthorized users from logging into systems that perform critical roles in the networked environment. Typically, these types of access restrictions are implemented by utilizing netgroups.

Using NIS Netgroups to Restrict Access

Netgroups can be used essentially to make only portions of the NIS password map visible to the client. On the NIS server, netgroups containing user lists are added to the /etc/netgroup file, and the NIS netgroup map is populated with this information. No additional configuration or modification of the server machine is required. Figure 1 shows a segment of a netgroup file specifying two netgroups - staff with members tim and jill, and sales with brad, randy, and mark.

The password files of the client machines are then modified. These files will typically contain +:*:0:0::: as the final entry. A user id of + indicates that the entire NIS passwd map should be appended to the local passwd file. The disabled password for the "user" + is used to maintain security on some older systems, and the user and group ids of 0 are included so the line has the format of a valid entry. Inclusion of the entire NIS map is not desired, so the +:*:0:0::: entry is removed and replaced with entries of the form +@netgroup, in which netgroup is the name of a netgroup containing user accounts with access to the machine. Figure 2a shows a portion of a passwd file from an NIS client that only allows logins from NIS users in the sales netgroup.

Similarly, you can exclude netgroups of users from the password file by retaining the +:*:0:0::: and including -@netgroup entries. Figure 2b shows a segment from a passwd file allowing logins from all NIS users except those in the staff netgroup. When excluding netgroups from the passwd file, ensure that the -@netgroup entries precede the +:*:0:0::: entry. This requirement is imposed by the routines for reading the passwd file. These routines stop their search of the file as soon as a match is found. As a result, -@netgroup entries placed after the +:*:0:0::: would never be processed.

One additional step is required on clients running Solaris 2.x. You must enable the +/- syntax for policy control in the password file by modifying the passwd line in /etc/nsswitch.conf. If the client is accessing an NIS server, then the line passwd: compat should be placed in the file. Clients accessing NIS+ servers should contain two lines: passwd: compat, and passwd_compat: nisplus.

Why rma?

Although netgroups are effective at prohibiting unauthorized users from logging into workstations, they are not appropriate for restricting logins to many types of servers. NIS servers, http servers serving user's WWW pages, mail hubs performing local delivery, and print servers performing per user accounting are all examples of servers that must be aware of all the users in the domain (i.e., they must have access to a complete password file). To overcome this limitation of NIS netgroups, the rma shell wrapper was created.

The original version of rma was created to restrict 25 undergraduate students at Southern Illinois University at Carbondale to the use of six Sun workstations located in a small lab operated by the Department of Computer Science. During the past four years rma has evolved and is now used to restrict undergraduate students from utilizing computing facilities reserved for graduate students and faculty. rma is also used to prevent users who are not members of systems staff from logging into the department's servers. See Listing 1 for the current version of the rma code.

rma stands for restricted machine access. This restriction is accomplished by moving the shells, typically found in /bin and /usr/bin, to a new location - the directory specified by the SHELL_DIR define in the program. /usr/bin/shells is usually a good choice. For security reasons, the statically linked copy of the Bourne shell usually found in /sbin should remain untouched, because it is customarily root's login shell. Then, the original shell names are linked to the rma binary, which should be copied to /usr/bin. This same procedure is used to install most wrapper programs.

Enforcing Access Restrictions

Enforcement of access restrictions is based on the system's hostname and the user's primary group id. These policies are specified by creating a file containing access lists, and the name and location of this file are determined by the definition of ACCESS_LIST. Each machine running rma must have an ACCESS_LIST that is owned by root with file permissions 644. The format of this file closely resembles a standard UNIX group file, which allows rma to utilize the fgetgrent() function to process the file. This feature greatly reduces the amount of code required for rma. However this similarity with the group file also brings one disadvantage: there is no method for adding comments to the ACCESS_LIST.

rma looks in the equivalent of the password field for a + or a - character to determine the type of access list given for a group. A + indicates an allow list (i.e., a list of machines that group members may use), whereas a - denotes a deny list, (i.e., a list of machines group members may not access). Figure 3 shows an ACCESS_LIST that 1) allows wheel and daemon group members to access any machine by denying access to no machines; 2) permits the staff group login access only to the machines named isis, bast, and horus; 3) restricts the sales group from logging into isis; and 4) prohibits users with a primary group of guest from logging into any machine by not granting access to any machines.

As in the previous example, the wheel and daemon groups, along with any other system-level groups, should be given access to all machines in a domain. Otherwise, root access may possibly be disabled, along with that of other administrative users. Although giving each machine an ACCESS_LIST that describes the entire domain may seem redundant, this method does ease administration. When changes are required, a new access file can be distributed to all machines with a utility such as rdist. This method of updating the access list helps to preserve the NIS paradigm of centralized management, which rma does not directly support.

After a user has successfully logged in, rma determines the system's hostname through the gethostname() system call and determines the user's primary group id through a call to getgid(). Next, the CanLogin() function is called to check this information against the access control file. If the user is permitted access, CanLogin() returns 1, and rma's Login() function is called to launch a shell for the user, otherwise KickOut() is called to terminate the user's current session.

Launching the Shell

The Login() function performs two primary tasks. First, it determines which shell to invoke and, second, whether to invoke it as a login shell or subshell. rma extracts the program name of the shell to be executed from the first argument, argv[0], passed in at run-time. Because the user's shell is linked to rma, this process produces the name of the user's real shell. This name is then combined with SHELL_PATH to generate the fully qualified name of the shell to execute. rma also determines whether the shell is to be run as the login shell by examining argv[0]. If the first character of argv[0], *argv[0], is a -, then rma has been started by the login process as a login shell, and in return must start the user's shell as a login shell. Alternatively, if *argv[0] is not -, then rma was started interactively (e.g., to execute a script, or by the rsh daemon to execute remote commands), and it should start the user's shell as a subshell.

When Login() starts a shell, it passes any command line parameters to the shell it is executing. This allows rsh's, as well as any software packages using the system() or popen() functions, to function properly.

Advanced Features

KickOut() is called when a user is denied access. By defining MAIL_TO as a valid email address, KickOut() will use /usr/ucb/Mail to send email when an attempted access violation occurs. The body of this message is empty, but the subject, "ACCESS VIOLATION!!," and the message headers state who attempted access, when the attempt was made, and on which machine. Some investigation with the last command will even tell where the login attempt originated from, because the user was allowed to complete the login process before they were "kicked off" the machine.

In addition to sending email, rma can run scripts before a user's login shell is started and after it terminates. These scripts are defined by LOGIN_SCRIPT and LOGOUT_SCRIPT, respectively. To run these scripts, rma first calls the function LoginScript(), which forks a child process to execute the LOGIN_SCRIPT, and waits for this child to terminate. Once this child process terminates, rma forks a second child, which executes the user's login shell, and again waits for that process to terminate. Upon termination of the second child process, the original rma process calls LogoutScript() to execute the LOGOUT_SCRIPT.

These scripts have a wide variety of uses. For example, the LOGIN_SCRIPT can be used to display system messages to users, and the LOGOUT_SCRIPT can be used to clean temporary and scratch directories upon logout. These scripts are not called when rma starts a subshell. Thus, the opening and closing of windows on a workstation, for example, would not cause the login or logout scripts to be executed.

Limitations and Concerns

rma does a good job restricting console logins, telnets, rlogins, and rsh's. Unfortunately, rma provides no facilities for restricting ftp access. The ftp daemon never attempts to execute the user's login shell. Contrary to ftp, su does start a shell. It executes the shell of the user being su'ed to. As a result, su'ing to an account that is prohibited from accessing a given machine generates an access violation. Careful readers of the rma code will notice a section of code to deal with an oddity of the SunOS su program. su on SunOS does not reset argv[0] to the name of the shell being executed, so a special check is performed in the Login() function. If su was the calling program, the name of the shell is extracted from the SHELL environment variable.


rma may look like a security tool, however, it is only intended to enforce access policies, because a clever user may be able to bypass rma's function. This problem has not arisen in any of the variants of rma I use. These include versions running on workstations and servers running SunOS 4.1.x and Solaris 2.x. The version of rma discussed here has been tested under SunOS 4.1.4 and Solaris 2.5. Although I have not tested them all, rma should compile on almost any UNIX variant having an ANSI C compiler with very minimal modifications.

About the Author

Greg Wade has a masters degree in computer science from Southern Illinois University at Carbondale (SIUC). He is currently employed by the Department of Computer Science at SIUC as the assistant lab director, and with the Universities Water Information Network (UWIN) as a computer information specialist. Mr. Wade also does independent consulting, and conducts research in information systems and spatial decision support systems.